Linux的奇妙冒险——进程pcb第二讲
进程控制
- 一.fork函数初识
- 二.进程替换
- 三.进程常见问题
- 1.退出码
- 2.exit函数
- 四.回收进程(进程等待)
- 1.wait/waitpid方法
- 2. 等待回收过程
- 3.子进程status
一.fork函数初识
fork()函数用于创建一个新进程,这个新进程被称为子进程。它是由父进程调用并创建的。
调用fork()后,父进程和子进程会各自得到一个返回值:
子进程中返回0,父进程返回子进程id,出错返回-1
具体用法:
通过检查返回值,进程可以判断自己是父进程还是子进程,从而执行不同的代码逻辑。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
如下给一段代码,在fork前只有父进程再走,在fork后变成两个进程
运行结果:
需要知道的是子进程是父进程的副本,但两者是独立的进程,可以并行执行。在执行代码时运用了写时拷贝,即只有在子进程需要修改数据时,系统分配对应的内存空间并通过页表映射。
具体实现我们放在虚拟内存章节再谈。
fork的常规用法:
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数
二.进程替换
替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
常见的替换函数
这几个函数差异在于第二个参数,以l结尾的是多个参数代表命令行参数,以v结束则是命令行参数指针表。
- l(list) : 表示参数采用列表
- v(vector) : 参数用数组
- p(path) : 有p自动搜索环境变量PATH
- e(env) : 表示自己维护环境变量
事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示。
函数解释
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。
做一个小实验通过进程替换执行pwd
六种方式皆可替换
三.进程常见问题
1.退出码
退出代码是程序在完成执行后返回给其调用者的数值代码。它用于指示程序执行是否成功或失败,以及在失败的情况下提供有关失败原因的一些信息。
通常情况下,退出代码为 0 表示程序成功完成执行,而任何非零值则表示程序在执行过程中遇到了某种错误。不同的非零退出代码可以表示不同类型的错误。
通过echo $?
可以查到上一条命令的退出码
2.exit函数
#include <unistd.h>
void _exit(int status);
参数:status 定义了进程的终⽌状态,⽗进程通过wait来获取该值
exit最后也会调⽤_exit, 但在调⽤_exit之前,还做了其他⼯作:
- 执⾏⽤户通过 atexit或on_exit定义的清理函数。
比特就业课 - 关闭所有打开的流,所有的缓存数据均被写⼊
- 调⽤_exit
return 退出:
return是⼀种更常⻅的退出进程⽅法。执⾏return n等同于执⾏exit(n),因为调⽤main的运⾏时函数会将main的返回值当做 exit的参数。
四.回收进程(进程等待)
1.wait/waitpid方法
- 1.pid=-1,等待任⼀个⼦进程。与wait等效。
pid>0.等待其进程ID与pid相等的⼦进程。 - 2.status: 输出型参数
WIFEXITED(status): 若为正常终⽌⼦进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED⾮零,提取⼦进程退码。(查看进程的退出码) - 3.options:默认为0,表⽰阻塞等待,WNOHANG: 若pid指定的⼦进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该⼦进程的ID。
2. 等待回收过程
-
如果⼦进程已经退出,调⽤wait/waitpid时,wait/waitpid会⽴即返回,并且释放资源,获得⼦
进程退出信息。 -
如果在任意时刻调⽤wait/waitpid,⼦进程存在且正常运⾏,则进程可能阻塞。
-
如果不存在该⼦进程,则⽴即出错返回
3.子进程status
wait和waitpid,都有⼀个status参数,该参数是⼀个输出型参数,由操作系统填充。
• 如果传递NULL,表⽰不关⼼⼦进程的退出状态信息。
• 否则,操作系统会根据该参数,将⼦进程的退出信息反馈给⽗进程。
• status不能简单的当作整形来看待,可以当作位图来看待,具体细节如下图(只研究status低16⽐特位)
status 主要告诉我们两件事之一:
1.进程是如何终止的? (正常退出还是被信号杀死?)
2.它的退出码(Exit Code)或导致其终止的信号(Signal)是什么?
在具体获得退出码和终止信号时,我们可以采用系统提供的宏函数
WIFEXITED(status)
#define WIFEXITED(status) (((status) & 0x7F) == 0)
// 0x7F 的二进制是 01111111,与 status 进行按位与操作后,
// 可以提取出低7位。如果结果为0,则正常退出。
WEXITSTATUS(status)
#define WEXITSTATUS(status) (((status) >> 8) & 0xFF)
// 1. (status >> 8): 将 status 右移 8 位,使高8位移动到低8位的位置。
// 2. & 0xFF: 与 0xFF(二进制 11111111)进行按位与操作,确保只取这8位,屏蔽掉其他可能的高位。