在操作系统中,进程控制(Process Control)是最核心、最基础的系统能力之一。我们运行的每一个程序、每一条命令,本质都是操作系统对进程的创建、调度、等待和销毁的完整生命周期管理。
一、进程控制核心概念
进程是操作系统资源分配和调度的基本单位,每个进程都有唯一的 PID(进程ID),并由 PCB(进程控制块)维护状态、资源、父子关系等信息。
Linux 下所有进程均由 父进程创建子进程,最终形成以 init/systemd 为根的进程树。进程的完整生命周期分为三步:
创建:父进程通过系统调用生成子进程
运行/终止:子进程执行业务逻辑,主动或被动退出
等待回收:父进程获取子进程退出状态,回收资源,避免僵尸进程
二、进程创建
进程创建在之前的学习我们就了解过一部分
fork
一个新的进程就是内核数据结构+代码,数据
二、进程创建
进程创建在之前的学习我们就了解过一部分
fork创建子进程
一个新的进程就是内核数据结构+代码,数据
fork 返回值三种情况
pid>0:当前是父进程,返回值为子进程 pid;
pid==0:当前是子进程;
pid<0:fork 失败(进程数达到系统上限、内存不足)
为什么我们要创建进程??
让子进程和父进程分流,子进程跑父进程的代码
(1)父子进程分流执行,分担任务
fork之后,父子进程共享同一份程序代码,但拥有独立进程调度流:
-
fork返回值区分逻辑:父进程返回子进程 pid,子进程返回 0;
-
父进程负责监听、调度、等待;子进程单独执行业务逻辑,两者互不阻塞。 典型场景:网络服务器,每来一条客户端连接就 fork 一个子进程处理读写。
完成进程替换
(2)配合 exec 系列函数,完成进程程序替换
单纯 fork 只能复制父进程代码,如果想让子进程运行全新可执行程序(比如 shell 执行外部命令),必须搭配exec族函数:
fork创建子进程,复刻父进程环境;
子进程内部调用execvp/execve,丢弃原有代码段,加载新程序; 父进程不受影响,继续原有逻辑。 场景:bash 输入ls,shell 先 fork 子进程,子进程 exec 替换为 ls 程序执行。
写时拷贝vs浅拷贝
浅拷贝

写时拷贝

操作系统会将进程fork出来之后的进程他们的数据变成只读
如果有任意一方要对数据来进行修改,那么os不会让其直接对该数据进行修改,会先将数据拷贝出来一份,然他再备份的那一块再进行改
三、进程终止
进程终止就是要释放代码和数据+内核数据结构
首先先释放代码和数据,然后再释放内核数据结构
为什么呢??
因为父进程是要知道子进程结束的的具体信息,可以分为以下三种

a,b,c都可以利用两个变量来记录这个信息
int sig
int exit_code
再main函数中return的就是进程退出码
查看进程终止信号
echo &?




进程退出码可以根据我们自己自定义也可以是用系统的strerror
1.查看系统自己的strerror


系统的退出码对应的信息
但是我们使用的比较多的还是自定义
依靠我们自定义的宏
对于C这种情况我们需要知道什么情况使其发生异常,怎么被kill
所以退出码对其并没有意义
| a | 代码结束,返回正确 | 无信号 | 无 |
| b | 代码结束,结果不符合预期 | 无信号 | 无 |
| c | 代码未跑完,进程异常结束 | 有致命信号 | 1. SIGINT(2):Ctrl+C 中断2. SIGSEGV(11):段错误3. SIGKILL(9):强制杀进程4. SIGFPE(8):除零崩溃 |
那么应该要怎么退出呢
exit vs _exit
这是我们之前学到的僵尸进程



子进程结束后,父进程没有对子进程进行回收,子进程会一直处于僵尸状态,当我再命令界面ctrl+c
会将全部的进程都杀死

四、进程等待
Wait
pid_t wait(int *wstatus);
wstatus:传出参数,保存子进程退出状态;传 NULL 表示不关心退出码。
阻塞等待:没有子进程退出,父进程一直等待.直到任意一个子进程结束,而且只能是任意的子进程不能说自己想要的子进程


执行 pid_t ret = fork(); 后,系统会生成两份独立进程,同一个 fork 返回两个不同值:
父进程:返回 新创建子进程的 PID(正整数)
子进程:返回 0
创建失败(内存不够、进程数上限):父子都返回 -1
ret接收了wait子进程的pid
Waitpid
#include <sys/wait.h> pid_t waitpid(pid_t pid, int *wstatus, int options);
参数 1:pid —— 指定要等待的子进程(核心区分点)
分 4 种取值规则:
pid > 0 只等待 PID = pid 这唯一一个子进程,其他子进程退出也不会触发返回。 例:waitpid(child_pid, &st, 0) 只等刚才 fork 出的子进程。
pid == 0 等待和当前父进程同进程组的任意子进程。
pid == -1 等待所有子进程,等价 wait(&st)。
pid < -1 取绝对值 abs(pid) 作为进程组 ID,等待该进程组下所有子进程。
参数 2:wstatus —— 接收子进程退出状态
-
传 NULL:不关心子进程退出码,丢弃状态;
-
传 int 变量地址:内核会把子进程退出信息存入该变量,配合宏解析
看是否正常终止,如果正常终止就就返回0
非正常终止要看退出码
参数 3:options —— 控制阻塞行为(位或组合)
0:默认阻塞模式,无特殊功能。
WNOHANG:非阻塞 没有子进程退出时,函数立刻返回0,父进程不会卡住,可以继续执行业务逻辑。
WUNTRACED 子进程收到 SIGSTOP/SIGTSTP 暂停时,waitpid 也会返回。
WCONTINUED 暂停的子进程收到 SIGCONT 恢复运行时,waitpid 也会返回。
看一下下面那个现象


status对应的不是退出码吗???
为什么这里显示不是1,而是256

当然我们也可以尝试用真的退出码
故意让被除数为0;


第 8 个信号:8) SIGFPE 含义:浮点运算异常(除零、浮点数溢出等)。

父进程通过waitpid是如何获得子进程的退出信息的??
子进程结束后不会立即退出,先是释放代码和数据,但是内核数据结构并没有释放,里面记录退出码和退出信号并且处于Z状态,父进程可以通过waitpid获取子进程的pid和一些信息




