【Linux】linux进程控制(fork,exit,wait详解)
本文是小编巩固自身而作,如有错误,欢迎指出!
目录
一、进程创建
fork函数
在前文我们已经讲述过fork的大体功能:
fork()是 Linux/Unix 中创建新进程最核心的系统调用,作用是复制当前进程,产生一个几乎一模一样的子进程。
返回值规则
- 父进程:返回子进程的 PID(>0)
- 子进程:返回 0
- 创建失败:返回 -1
#include <stdio.h>
#include <unistd.h>
int main() {
printf("before fork\n");
pid_t pid = fork();
if (pid < 0) {
perror("fork failed");
return 1;
}
if (pid == 0) {
// 子进程
printf("I am child, pid=%d, ppid=%d\n", getpid(), getppid());
} else {
// 父进程
printf("I am parent, pid=%d, child pid=%d\n", getpid(), pid);
}
return 0;
}

循环fork创建子进程
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
// 创建 5 个子进程
#define PROC_NUM 5
int main() {
int i;
pid_t pid;
for (i = 0; i < PROC_NUM; i++) {
pid = fork(); // 创建子进程
if (pid < 0) {
perror("fork failed");
return 1;
}
// ==================================
// 子进程:执行任务,然后退出!
// ==================================
if (pid == 0) {
printf("【子进程】 pid=%d, 编号=%d, 父pid=%d\n",
getpid(), i, getppid());
// 子进程执行完任务就退出,不回到循环!
return 0;
}
// 父进程:什么都不做,继续下一次循环
}
// ==================================
// 父进程:等待所有子进程退出
// ==================================
for (i = 0; i < PROC_NUM; i++) {
wait(NULL);
}
printf("【父进程】所有子进程已回收\n");
return 0;
}

细心的同学会发现一个问题,我们在for循环每个子进程创建完毕后,也有一个return,这是什么原因呢?
因为子进程不能回到 for 循环,否则子进程也会执行
fork(),导致进程爆炸。
写时拷贝
通常父子进程代码共享,当父子进程不写入数据的时候,数据是共享的,当任意一方试图写入的时候,便另外开一份空间,以拷贝的方式拷贝一份数据进行修改,原有的空间的数据属于未进行修改的那一方,这样两方便各自有一份数据了
写时拷贝,就是:先共享,要改再复制。
更标准一点的定义:
多个调用者一开始共享同一份数据 / 内存,当任何一方要修改(写)数据时,系统才真正为它复制一份独立副本,让它在副本上修改,不影响其他人。
二、进程终止
正常退出(自愿退出)
(1)main 函数 return
- C/C++ 程序
main()返回0或其他值 - 等价于调用
exit(返回值)
(2)调用 exit () /_exit () /_Exit ()
exit():标准库函数,会做清理(刷新缓冲区、调用 atexit 函数)_exit()/_Exit():系统调用,直接退出,不清理
exit 和 _exit 的区别
1. exit ()(温柔退出)
执行流程:
- 调用 atexit() 注册的所有清理函数
- 刷新所有标准 IO 缓冲区(printf/fwrite 等数据会真正写入)
- 关闭所有打开的文件流
- 最后调用 _exit() 让内核终止进程
2. _exit ()(暴力退出)
执行流程:直接进入内核,立即终止进程
- 不刷新缓冲区
- 不执行清理函数
- 不关闭用户态文件流
- 内核直接回收进程资源
-
#include <stdio.h> #include <stdlib.h> #include <unistd.h> void clean() { printf("清理函数执行\n"); } int main() { atexit(clean); // 注册退出时执行的函数 printf("Hello World"); // 没有换行,数据在缓冲区 // 测试1:用 exit() —— 会输出、会执行清理函数 exit(0); // 测试2:用 _exit() —— 什么都不输出,直接退出 // _exit(0); }exit()输出:

_exit()输出:

exit(0):输出 Hello World + 清理函数执行_exit(0):无任何输出,缓冲区数据丢失

(3)最后一个线程退出
多线程程序中,主线程退出不代表进程退出;最后一个线程退出时进程才终止。
异常退出(出错退出)
- 除 0
- 非法内存访问(段错误 Segmentation fault)
- 总线错误
- 栈溢出
- 未捕获的异常(C++、Java、Python)
内核会向进程发送致命信号,进程无法忽略。
被信号杀死(最常见)
常见终止信号
SIGINT (2):Ctrl + CSIGQUIT (3):Ctrl + \SIGKILL (9):强制杀死,不可捕获、不可忽略SIGTERM (15):默认 kill 命令,可捕获SIGSEGV (11):段错误SIGABRT (6):调用 abort () 或 assert 失败
信号导致退出时,进程会被内核强制终止。
父进程退出,被 init/systemd 收养后清理
- 父进程先死,子进程变成孤儿进程
- 被 PID 1(init/systemd)收养
- 子进程退出后由 PID 1 自动回收
三、进程等待
进程等待的重要性
子进程退出,如果父进程对此漠不关心,那么就会造成僵尸进程,进而造成内存泄露的问题。
所以,进程等待就是:
父进程调用 wait /waitpid,等子进程退出,然后回收它的尸体(PCB),防止僵尸进程。
- 回收子进程资源,防止僵尸进程(Zombie)
- 获取子进程退出状态(正常退出?被杀死?退出码多少?)
- 父子进程同步(父进程等子进程干完再继续)
wait(阻塞等待任意子进程)
- wait的作用是等待当前进程的任意一个子进程的状态改变,即检测并等待进程状态变成Z僵尸状态,释放进程的资源,获取进程退出信息
- 返回值: 成功返回被等待进程pid,失败返回-1
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
void RunChild()
{
int cnt = 2;
while(cnt)
{
printf("i am child process, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
}
int main()
{
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 1;
}
else if(id == 0)
{
RunChild();
exit(0);
}
else
{
//id > 0
sleep(4)
int ret = waitpid(-1, NULL, 0);
if(ret == id)
{
printf("我是父进程,我等待子进程成功了, 子进程的pid: %d\n", ret);
}
else
{
printf("等待子进程失败\n");
}
sleep(2);
}
return 0;
}

完整执行时间线:
- 0 秒:fork 创建父子进程
- 0~2 秒:子进程跑,打印 2 次
- 第 2 秒:子进程执行完毕,调用
exit(0)- 子进程变成 僵尸进程(Z)
- 子进程资源释放,但 PCB 还在
- 2~4 秒:父进程还在 sleep,没回收子进程→ 这段时间子进程是僵尸进程!
- 第 4 秒:父进程 sleep 结束
- 第 4 秒:父进程调用
wait(NULL)- 回收僵尸子进程
- 打印等待成功
- 4~6 秒:父进程再 sleep (2),结束
waitpid(更强大:指定 PID、非阻塞)
让父进程等待指定的子进程,回收它,防止僵尸进程。
函数原型如下
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
pid:等待哪个子进程
pid == -1:等待任意子进程(等价wait)pid > 0:等待指定 PID的子进程pid == 0:等待同组的任意子进程pid < -1:等待指定进程组的任意子进程
最常用:pid = -1 或 子进程 PID
status:输出子进程退出状态
和 wait 一样,用宏解析:
WIFEXITED(status):是否正常退出WEXITSTATUS(status):获取退出码WIFSIGNALED(status):是否被信号杀死WTERMSIG(status):获取杀死信号
options:最关键!
0:阻塞等待(和 wait 一样)WNOHANG:非阻塞等待(没有子进程退出就立刻返回,不卡住)
返回值
> 0:成功回收的子进程 PID= 0:子进程还在运行(只有用 WNOHANG 才会返回)-1:出错(没有子进程了)
现在我们就看看和普通wait最主要的区别,waitpid可以非阻塞等待
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if (id == 0)
{
// 子进程
printf("子进程运行,3秒后退出\n");
sleep(3);
exit(0);
}
// 父进程:非阻塞轮询
while (1)
{
int status;
// WNOHANG = 非阻塞
pid_t ret = waitpid(id, &status, WNOHANG);
if (ret == 0)
{
// 子进程还在跑
printf("父进程:子进程还没退出,我继续干别的...\n");
sleep(1);
}
else if (ret == id)
{
// 子进程退出了
printf("父进程:检测到子进程退出,回收成功\n");
break;
}
}
return 0;
}

本次分享就到这里结束了,后续会继续更新,感谢阅读!
更多推荐



所有评论(0)