当前位置: 首页 > news >正文

进程控制(Linux)

进程控制

fork

在Linux中,fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

返回值:
在子进程中返回0,父进程中返回子进程的PID,子进程创建失败返回-1。

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程。
  • 将父进程部分数据结构内容拷贝至子进程。
  • 添加子进程到系统进程列表当中。
  • fork返回,开始调度器调度。
    在这里插入图片描述

进程终止

进程退出场景

在我们写的程序中,代码运行会有三种情况:

  1. 代码运行完成,正确——代码结束
  2. 代码运行完毕,结果不正确
  3. 代码异常终止

main函数中的return的返回值为进程的退出码!

在这里插入图片描述

进过测试总结——return返回的值为main函数的退出码!

> [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gk5jqfJM-1685794022709)(G:\Mynote\image\image-20230526182653011.png)]
return的返回值含义(还有很多但想表达的是只有0为成功其他为错误原因):

在这里插入图片描述

代码异常终止

通俗的讲:代码跑了一半报错终止运行了——程崩溃!
崩溃后的程序它的返回值是没有意义的!例子:

在这里插入图片描述

进程退出的方式

  • main函数return,代表进程退出
  • 非main函数返回——函数返回
  • exit在任地方调用都是代表终止进程,参数都是退出码!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REiidq7G-1685794022710)(G:\Mynote\image\image-20230526193200792.png)]

  • exit与return是一样的可以退出进程
    在这里插入图片描述在这里插入图片描述

  • _exit终止进程,强制终止进程,不要进行进程的收尾工作,比如不是刷新缓冲区!exit()会刷新缓冲区(用户缓冲区)
    在这里插入图片描述

进程退出OS层面做了什么?

  • 系统层面上,少了一个进程:free PCB,free mm_struct,free页表和各种映射关系,代码+数据申请的空间也要给free了
    进程异常退出

情况一:向进程发生信号导致进程异常退出。

例如,在进程运行过程中向进程发生kill -9信号使得进程异常退出,或是使用Ctrl+C使得进程异常退出等。

情况二:代码错误导致进程运行时异常退出。

例如,代码当中存在野指针问题使得进程运行时异常退出,或是出现除0的情况使得进程运行时异常退出等。

进程等待

进程wait是什么?

fork() ;
子进程:帮助父进程完成某些任务
父进程:而父进程要想知道知道子进程帮我完成了什么任务就要用wait/waitpid等待子进程退出

为什么要父进程等待子进程?
1.通过获取子进程退出的性息,能够得知进程执行结果!
2.可以保证:时序问题,子进程先退出,父进程后退出。
3.进程退出的时候会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,来释放子进程占用的资源!

注意:一但进程变成僵尸进程那他就流弊了无敌了kill-9也杀不死他,应为没有办法杀一个死去的进程!

如何解决僵尸进程?

使用wait来回收子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if(id == 0){//childint cnt = 5;while(cnt){printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);cnt--;sleep(1);}exit(0);//子进程在这里结束}sleep(10);printf("fahter wait begin\n");pid_t ret = wait(NULL);if(ret > 0){printf("fahter wait :%d, success\n", ret);}else{printf("father wait failed!\n");}sleep(10);return 0;
}

效果如下:

在这里插入图片描述
总结:wait可以回收僵尸进程

在这里插入图片描述

status可以帮助我们收到子进程结束的三种反馈情况!

我们程序代码跑完结果对or结果不对是靠进程退出码来判定的,但是如何证明我们的代码是跑完了的呢,而不是“先帝创业未半而中道崩殂”的呢?——如果一个程序应为代码异常而终止的问题导致程序收到的其他信号。所以我们可以判断一个进程是否有收到信号来判断进程是否是异常终止的!下面的表应该怎么看呢?
在这里插入图片描述
我们通过一系列位操作,就可以根据status得到进程的退出码和退出信号。
解释:次底8位为退出状态——退出码,而底7位为终止信号!所以我们以后只需要判断退出码它的底7位为0它就没有收到信号

exitCode = (status >> 8) & 0xFF; //退出码
exitSignal = status & 0x7F; //退出信号

系统当中提供了两个宏来获取退出码和退出信号。

  1. WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
  2. WEXITSTATUS(status):用于获取进程的退出码。

exitNormal = WIFEXITED(status); //是否正常退出
exitCode = WEXITSTATUS(status); //获取退出码

需要注意的是,当一个进程非正常退出时,说明该进程是被信号所杀,那么该进程的退出码也就没有意义了。
进程等待的方法

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if(id == 0){//childint cnt = 5;while(cnt){printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);cnt--;sleep(1);}exit(0);}sleep(10);printf("fahter wait begin\n");// pid_t ret = wait(NULL);// pid_t ret = waitpid(id,NULL,0);等待指定一个进程// pid_t ret = waitpid(-1,NULL,0);//等待任意一个子进程int status = 0;pid_t ret = waitpid(-1,&status,0);if(ret > 0){ printf("fahter wait :%d, success, status exit code:%d ,status exit signal%d\n", ret,(status>>8)&0xFF, status & 0x7F);//看信号位与退出码}else{printf("father wait failed!\n");}sleep(10);return 0;

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{printf("i am a child process!,pid :%d ,ppid: %d \n",getpid(),getppid());exit(10);
}

结果:

i am a child process!,pid :20681 ,ppid: 19703

在这里插入图片描述

我们发现进程/a.out 的父进程居然是-bash——bash是所有启动进程的父进程!bash是如何得到进程的的退出结果的呢?一定是通过wait来获得进程的退出结果的! 所以我们可以使用echo可以查看子进程的退出码!

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if(id == 0){//childint cnt = 5;while(cnt){printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);cnt--;sleep(1);}exit(0);}sleep(5);printf("fahter wait begin\n");// pid_t ret = wait(NULL);// pid_t ret = waitpid(id,NULL,0);等待指定一个进程// pid_t ret = waitpid(-1,NULL,0);//等待任意一个子进程int status = 0;pid_t ret = waitpid(id,&status,0);if(ret>0){if(WIFEXITED(status))//没有收到任何的退出信号的{//正常结束的,获取对应的退出码!printf("exit code: %d\n",WEXITSTATUS(status));}else{printf("error, get s signal!\n");}}
}

waitpit

作用:waitpid会暂时停止进程的执行,直到有信号来到或子进程结束。
函数说明:

如果在调用 waitpid()时子进程已经结束,则 waitpid()会立即返回子进程结束状态值。
子进程的结束状态值会由参数 status 返回,而子进程的进程识别码也会一起返回。
如果不在意结束状态值,则参数 status 可以设成 NULL。

参数:
参数 pid 为欲等待的子进程识别码,其他数值意义如下:

  1. pid<-1 等待进程组识别码为 pid 绝对值的任何子进程。
  2. pid=-1 等待任何子进程,相当于 wait()。
  3. pid=0 等待进程组识别码与进程相同的任何子进程。
  4. pid>0 等待任何子进程识别码为 pid 的子进程。

参数 status:输出型参数,获取子进程的退出状态,不关心可设置为NULL。
参数 options:当设置为WNOHANG时,若等待的子进程没有结束,则waitpid函数直接返回0,不予以等待。若正常结束,则返回该子进程的pid。

理解waitpit

在这里插入图片描述

阻塞

阻塞与非阻塞

*阻塞通俗的讲就是你和你女朋友出去玩但是她要化妆你要等她一直等

非阻塞为你和你女朋友出去玩但是她要化妆你要等她,但过五分钟打电话问一下直到她下来。——这种方式为基于非阻塞的轮询方案!*

注:阻塞与非阻塞都是等待的一种方法

阻塞的本质:其实是进程的PCB被放入等待队列,将进程的状态改为S状态
返回的本质:进程的PCB从等待队列拿到R队列,从而被CPU调度。

非阻塞等待

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id = fork();if(id == 0){//childint cnt = 3;while(cnt){printf("child[%d] is running: cnt- is:%d\n",getpid(),cnt);cnt--;sleep(1);}exit(0);}int status = 0;while(1){pid_t ret = waitpid(id,&status,WNOHANG);                                                                                                                                                                  if(ret == 0)                            {                                                                   // 子进程没有退出,但是waitpid等待是成功的,需要父进程重新进行等待printf("父进程运行");}                                                                   else if (ret > 0)                                                   {                                                                   // 子进程退出,waitpid也成功l,获取到对应的结果了printf("获得子进程的退出码%d,子进程的退出信号%d",(status>>8)&0xFF,status&0x7F);break;}                                                     else                                               {                                                  // 等待进程失败perror("waitpid");break;}}sleep(1);
}

进程程序替换

用fork创建子进程后,子进程执行的是和父进程相同的程序(但有可能执行不同的代码分支),若想让子进程执行另一个程序,往往需要调用一种exec函数。

当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换(借尸还魂),并从新程序的启动例程开始执行。

在这里插入图片描述

看上图为子进程的他的地址空间(虚拟内存)和页表没有变化只有物理内存改变了通过修改物理内存的数据数据和代码从而改变子进程运行全新的内容(借尸还魂),但要注意的是原本子进程与父进程是公用一个空间(代码与数据)但子进程的数据被改变后会写实拷贝从此子进程与父进程的的数据不在有关联**(父亲是老师儿子开公司)**

有的人要问了如何替换呢??(小朋友你是否有很多问号?)

替换函数(exec系列函数)

替换函数有六种以exec开头的函数,它们统称为exec函数:

execl

int execl(const char *path, const char *arg, ...);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

例如,要执行的是ls程序。

execl("/usr/bin/ls", "ls", "-a", "-i", "-l", NULL);

也可以

execl("/usr/bin/ls", "ls", "-ali", NULL);

execlp

int execlp(const char *file, const char *arg, ...);

第一个参数是要执行程序的名字,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾。

例如,要执行的是ls程序。

execlp("ls", "ls", "-a", "-i", "-l", NULL);

execle

int execle(const char *path, const char *arg, ..., char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是可变参数列表,表示你要如何执行这个程序,并以NULL结尾,第三个参数是你自己设置的环境变量。

例如,你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

char* myenvp[] = { "MYVAL=qwe", NULL };
execle("./mycmd", "mycmd", NULL, myenvp);

execv

int execv(const char *path, char *const argv[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

例如,要执行的是ls程序。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };
execv("/usr/bin/ls", myargv);
12

execvp

int execvp(const char *file, char *const argv[]);

第一个参数是要执行程序的名字,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾。

例如,要执行的是ls程序。

char* myargv[] = { "ls", "-a", "-i", "-l", NULL };

execve

int execve(const char *path, char *const argv[], char *const envp[]);

第一个参数是要执行程序的路径,第二个参数是一个指针数组,数组当中的内容表示你要如何执行这个程序,数组以NULL结尾,第三个参数是你自己设置的环境变量。

例如,你设置了MYVAL环境变量,在mycmd程序内部就可以使用该环境变量。

char* myargv[] = { "mycmd", NULL };
char* myenvp[] = { "MYVAL=qwe", NULL };
execve("./mycmd", myargv, myenvp);

相关文章:

进程控制(Linux)

进程控制 fork 在Linux中&#xff0c;fork函数是非常重要的函数&#xff0c;它从已存在进程中创建一个新进程。新进程为子进程&#xff0c;而原进程为父进程。 返回值&#xff1a; 在子进程中返回0&#xff0c;父进程中返回子进程的PID&#xff0c;子进程创建失败返回-1。 …...

C Primer Plus第十四章编程练习答案

学完C语言之后&#xff0c;我就去阅读《C Primer Plus》这本经典的C语言书籍&#xff0c;对每一章的编程练习题都做了相关的解答&#xff0c;仅仅代表着我个人的解答思路&#xff0c;如有错误&#xff0c;请各位大佬帮忙点出&#xff01; 由于使用的是命令行参数常用于linux系…...

又名管道和无名管道

一、进程间通信&#xff08;IPC&#xff0c;InterProcess Communication&#xff09; 概念&#xff1a;就是进程和进程之间交换信息。 常用通信方式 无名管道&#xff08;pipe&#xff09; 有名管道 &#xff08;fifo&#xff09; 信号&#xff08;signal&#xff09; 共…...

操作系统复习4.1.0-文件管理结构

定义 一组有意义的信息的集合 属性 文件名、标识符、类型、位置、大小、创建时间、上次修改时间、文件所有者信息、保护信息 操作系统向上提供的功能 创建文件、删除文件、读文件、写文件、打开文件、关闭文件 这6个都是系统调用 创建文件 创建文件时调用Create系统调用…...

【嵌入式烧录/刷写文件】-2.6-剪切/保留Intel Hex文件中指定地址范围内的数据

案例背景&#xff1a; 有如下一段HEX文件&#xff0c;保留地址范围0x9140-0x91BF内的数据&#xff0c;删除地址范围0x9140-0x91BF外的数据。 :2091000058595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F70717273747576775F :2091200078797A7B7C7D7E7F808182838485868788898A…...

JavaScript表单事件(下篇)

目录 八、keydown: 当用户按下键盘上的任意键时触发。 九、keyup: 当用户释放键盘上的键时触发。 十、keypress: 当用户按下键盘上的字符键时触发。 十一、focusin: 当表单元素或其子元素获得焦点时触发。 十二、focusout: 当表单元素或其子元素失去焦点时触发。 十三、c…...

机器学习 | SVD奇异值分解

本文整理自哔哩哔哩视频&#xff1a;什么是奇异值分解SVD–SVD如何分解时空矩阵 &#x1f4da;奇异值分解是什么&#xff1f; M是原始矩阵&#xff0c;它可以是任意的矩阵&#xff0c;奇异值分解就是将它分解为三个矩阵相乘。U和V是方阵&#xff0c;∑是不规则矩阵&#xff0c;…...

chatgpt赋能python:Python取值:介绍

Python取值&#xff1a;介绍 Python是一种非常流行的高级编程语言&#xff0c;适用于各种任务&#xff0c;包括数据科学、机器学习、Web开发和自动化。它被广泛使用&#xff0c;因为它易于学习、易于使用、易于阅读和易于维护。Python中的取值对于程序员来说是一个极其有用的工…...

广播风暴的成因以及如何判断、解决

广播风暴&#xff08;broadcast storm&#xff09;简单的讲是指当广播数据充斥网络无法处理&#xff0c;并占用大量网络带宽&#xff0c;导致正常业务不能运行&#xff0c;甚至彻底瘫痪&#xff0c;这就发生了“广播风暴”。一个数据帧或包被传输到本地网段 &#xff08;由广播…...

Loki 日志收集系统

一.系统架构 二.组成部分 Loki 的日志堆栈由 3 个组件组成&#xff1a; promtail&#xff1a;用于采集日志、并给每条日志流打标签&#xff0c;每个节点部署&#xff0c;k8s部署模式下使用daemonset管理。 loki&#xff1a;用于存储采集的日志&#xff0c; 并根据标签查询日志流…...

uCOSii信号量的作用

uCOSii中信号量的作用&#xff1a; 在创建信号量时&#xff0c;Sem_EventOSSemCreate(1)用于分时复用共享资源&#xff1b; Sem_EventOSSemCreate(0)用于中断和任务间同步或任务之间的同步。 具体在使用时&#xff0c;需要灵活运用。在访问共享资源时&#xff0c;我喜欢用互…...

Android 13 版本变更总览

Android 13 总览 https://developer.android.google.cn/about/versions/13?hlzh-cn 文章基于官方资料上提取 Android 13 功能和变更列表 https://developer.android.google.cn/about/versions/13/summary?hlzh-cn 行为变更&#xff1a;所有应用 https://developer.andr…...

QT 设计ROS GUI界面订阅和发布话题

QT 设计ROS GUI界面订阅和发布话题 主要参考下面的博客 ROS项目开发实战&#xff08;三&#xff09;——使用QT进行ROS的GUI界面设计&#xff08;详细教程附代码&#xff01;&#xff01;&#xff01;&#xff09; Qt ROS 相关配置请看上一篇博客 首先建立工作空间和功能包&a…...

pandas数据预处理

pandas数据预处理 pandas及其数据结构pandas简介Series数据结构及其创建DataFrame数据结构及其创建 利用pandas导入导出数据导入外部数据导入数据文件 导出外部数据导出数据文件 数据概览及预处理数据概览分析利用DataFrame的常用属性利用DataFrame的常用方法 数据清洗缺失值处…...

Jupyter Notebook如何导入导出文件

目录 0.系统&#xff1a;windows 1.打开 Jupyter Notebook 2.Jupyter Notebook导入文件 3.Jupyter Notebook导出文件 0.系统&#xff1a;windows 1.打开 Jupyter Notebook 1&#xff09;下载【Anaconda】后&#xff0c;直接点击【Jupyter Notebook】即可在网页打开 Jupyte…...

Linux:/dev/tty、/dev/tty0 和 /dev/console 之间的区别

在Linux操作系统中&#xff0c;/dev/tty、/dev/tty0和/dev/console是三个特殊的设备文件&#xff0c;它们在终端控制和输入/输出过程中扮演着重要的角色。尽管它们看起来很相似&#xff0c;但实际上它们之间存在一些重要的区别。本文将详细介绍这三个设备文件之间的区别以及它们…...

Linux 上安装 PostgreSQL——Ubuntu

打开 PostgreSQL 官网 PostgreSQL: The worlds most advanced open source database&#xff0c;点击菜单栏上的 Download &#xff0c;可以看到这里包含了很多平台的安装包&#xff0c;包括 Linux、Windows、Mac OS等 。 Linux 我们可以看到支持 Ubuntu 和 Red Hat 等各个平台…...

合并两个有序链表(java)

leetcode 21题&#xff1a;合并两个有序链表 题目描述解题思路&#xff1a;链表的其它题型。 题目描述 leetcode21题&#xff1a;合并两个有序链表 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例&#xff1a; 输入&…...

KEYSIGHT是德DSOX4034A 示波器 350 MHz

KEYSIGHT是德DSOX4034A 示波器 350 MHz&#xff0c;是德4000 X 系列拥有一系列引以为傲的配置&#xff0c;包括采用了电容触摸屏技术的 12.1 英寸显示屏、InfiniiScan 区域触摸触发、100 万波形/秒捕获率、MegaZoom IV 智能存储器技术和标配分段存储器。 是德DSO-X4034A 主要特…...

局域网技术

共享信道的分配技术是局域网的核心技术&#xff0c;而这一技术又与网络的拓扑结构和传输介质有关。 拓扑结构&#xff1a; 1.总线型拓扑&#xff1a; 总线一种多点广播介质&#xff0c;所有的站点通过接口硬件连接到总线上。 传输介质主要是同轴电缆&#xff08;基带和宽带…...

国防科技大学计算机基础课程笔记02信息编码

1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制&#xff0c;因此这个了16进制的数据既可以翻译成为这个机器码&#xff0c;也可以翻译成为这个国标码&#xff0c;所以这个时候很容易会出现这个歧义的情况&#xff1b; 因此&#xff0c;我们的这个国…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件

今天呢&#xff0c;博主的学习进度也是步入了Java Mybatis 框架&#xff0c;目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学&#xff0c;希望能对大家有所帮助&#xff0c;也特别欢迎大家指点不足之处&#xff0c;小生很乐意接受正确的建议&…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数

高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

Java + Spring Boot + Mybatis 实现批量插入

在 Java 中使用 Spring Boot 和 MyBatis 实现批量插入可以通过以下步骤完成。这里提供两种常用方法&#xff1a;使用 MyBatis 的 <foreach> 标签和批处理模式&#xff08;ExecutorType.BATCH&#xff09;。 方法一&#xff1a;使用 XML 的 <foreach> 标签&#xff…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...

提升移动端网页调试效率:WebDebugX 与常见工具组合实践

在日常移动端开发中&#xff0c;网页调试始终是一个高频但又极具挑战的环节。尤其在面对 iOS 与 Android 的混合技术栈、各种设备差异化行为时&#xff0c;开发者迫切需要一套高效、可靠且跨平台的调试方案。过去&#xff0c;我们或多或少使用过 Chrome DevTools、Remote Debug…...

0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化

是不是受够了安装了oracle database之后sqlplus的简陋&#xff0c;无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话&#xff0c;配置.bahs_profile后也能解决上下翻页这些&#xff0c;但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可&#xff0c…...

LLaMA-Factory 微调 Qwen2-VL 进行人脸情感识别(二)

在上一篇文章中,我们详细介绍了如何使用LLaMA-Factory框架对Qwen2-VL大模型进行微调,以实现人脸情感识别的功能。本篇文章将聚焦于微调完成后,如何调用这个模型进行人脸情感识别的具体代码实现,包括详细的步骤和注释。 模型调用步骤 环境准备:确保安装了必要的Python库。…...