title:
date: 2024-07-07 12:57:17
tags: fork Linux
cover: https://picbed0521.oss-cn-shanghai.aliyuncs.com/blogpic/PixPin_2024-07-25_22-26-59.png
原文见我的网站: www.supdriver.top
认识fork()
头文件<unistd.h>
提供的fork()
函数用于从已有的原进程
创建一个新的子进程
,而原进程在关系式称为父进程
fork()的返回值
#include <unistd.h>
pid_t id = fork();
父子进程中fork()
函数的返回值(此处用变量id
储存)是不同的:
父进程里id
的值为子进程的PID
,其值>0
;子进程里id
值固定为0
id > 0
父进程id == 0
子进程id < 0
fork()失败
分流
利用父子进程中fork()
返回值的不同,可以用if...else...
进行分流,让父子进程执行不同的代码
fork()的过程
进程调用fork
,当控制转移到内核中的fork代码后,内核做
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
fork
返回,开始调度器调度
当一个进程调用fork之后,就有一对二进制代码相同的父子进程。而且它们都运行到相同的地方。但每个进程都可以独立地继续运行代码,并按代码分流至不同的代码段
但这两个进程谁先执行完全由调度器决定,而谁先结束,则由实际执行情况和调度器决定
拷贝的过程 与 写时拷贝
fork
时,子进程会将父进程虚拟内存的内容都复制一份在自己的虚拟内存中,但通过页表,二者映射到了同一块物理内存的区域,这样在二者都没有写入行为时,减少了物理内存中冗余的拷贝行为,有效提高了运行效率
但当父子进程中有一方发生写入行为时,就会触发写时拷贝
,此时物理内存中发生拷贝行为,但只拷贝进程映射的数据段
,而由于不发生进程替换时父子进程的代码段一定相同,物理内存中,代码段
映射部分并不发生拷贝
进程退出
进程退出的场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果不正确
- 代码异常终止
进程常见退出方法
main
函数返回exit
退出_exit
退出
异常退出: ctrl
+ c
查看退出码
在终端使用命令echo $?
可以查看退出码
注
尽管子进程返回的是int
,父进程只取退出码的最低8位
,所以以下三种情况
main
函数返回-1
exit(-1)
_exit(-1)
在终端输echo $?
可得退出码255
exit
和 _eixt
辨析
main
函数返回就不惜说了,来辨析一下头文件<stdlib.h>
提供的exit
和_exit
函数
_exit()
_exit
会直接终止进程并返回状态码,而不会执行用户定义的清理函数,也不会清理缓冲区
exit()
exit
实际上最后也会调用_exit
,但它会先执行一系列善后工作,顺序如下:
- 执行用户通过 atexit或on_exit定义的清理函数。
- 关闭所有打开的流,所有的缓存数据均被写入
- 调用
_exit
return
退出: 执行return n
等同于执行exit(n)
进程等待
进程等待的必要性
- 子进程退出后需要父进程来回收僵尸进程,防止产生其引发内存泄漏等问题
- 僵尸进程难以处理,
kill -9
也清理不掉 - 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
进程等待的方法
wait
方法
#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int*status);
参数
:显然status
是输出型参数,获取子进程的退出状态
,若不关心,可传参NULL
返回值
: 若成功,则返回被等待进程的pid
,若失败,则返回-1
waitpid
方法
返回值:
当正常返回的时候waitpid返回收集到的子进程的进程ID;
如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;参数:
pid
:
pid == -1
,等待任一个子进程。与wait等效。
pid > 0
.等待其进程ID与pid相等的子进程。
status
:
输出型参数传参&status
: 将子进程的状态码
存入status
以下两个宏函数用于处理状态码
:
WIFEXITED(status)
: 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status)
: 若WIFEXITED非零,提取子进程退出码
。(查看进程的退出码
)
options
:
WNOHANG
: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。
0
: 阻塞等待指定pid
的进程
阻塞等待
option
== 0
时waitpid
采用阻塞等待,父进程会阻塞等待到子进程退出
示例代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
int status;
pid_t id = fork();
if(id == 0)
{
for(int i = 1;i<=5;i++)
{
printf("child running [%d]s\n",i);
sleep(1);
}
printf("child exited\n");
exit(0);
}
else
{
waitpid(id,&status,0);
if(WIFEXITED(status))
printf("father wait success, exit code: %d\n",WEXITSTATUS(status));
else
printf("child failed to exit\n");
}
return 1;
}
非阻塞轮询
option
== WNOHANG
时,waitpid
采用非阻塞等待,若等不到子进程退出,就会继续执行后面的代码,所以一般加上while
等循环用于轮询,二者共同构成非阻塞轮询
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
//非阻塞轮询
int main()
{
int status;
pid_t id = fork();
if(id == 0)
{
sleep(5);
printf("child exited\n");
exit(0);
}
else
{
while(1)//轮询
{
pid_t rid = waitpid(id,&status,WNOHANG);
if(rid > 0)
{
if(WIFEXITED(status))
printf("father wait success, exit code: %d\n",WEXITSTATUS(status));
else
printf("child failed to exit\n");
break;
}
else if(rid == 0)
{
printf("等待子进程中...\n");
sleep(1);
}
else//rid<0
{
printf("wait failed\n");
break;
}
}
}
return 1;
}