Linux进程控制
1. 进程终止
1.1. 进程终止的本质是回收资源
1.1 释放资源
- 内存资源:
- 释放进程的地址空间(
mm_struct
),包括代码段、数据段、堆、栈等,通过写时复制(CoW)共享的页会减少引用计数,若计数为 0 则释放物理内存。 - 销毁页表、页目录等内存管理结构。
- 释放进程的地址空间(
- 文件资源:
- 关闭所有打开的文件描述符(File Descriptor),对应文件对象(
struct file
)的引用计数减 1,若计数为 0 则释放文件对象(关闭文件)。 - 断开与管道、socket 等 IPC 资源的关联,清理相关内核结构。
- 关闭所有打开的文件描述符(File Descriptor),对应文件对象(
- 其他资源:
- 释放信号处理表、进程定时器、进程间通信(IPC)资源(如共享内存、信号量)等。
- 从所属进程组、会话中移除,更新进程组和会话的状态。
1.2 处理进程状态
- 进程状态从运行态 / 阻塞态等转为终止态(TASK_DEAD),不再参与调度。
- 保存进程的退出状态(
exit_status
),包括终止原因(正常返回值或信号编号),供父进程查询。
1.3 处理父进程与子进程的关系
- 通知父进程:通过信号
SIGCHLD
通知父进程 “子进程已终止”,父进程可通过wait()
或waitpid()
系列函数获取子进程的退出状态。 - 僵尸进程(Zombie Process)的产生:若父进程未及时调用
wait()
回收子进程,子进程的 PCB(进程控制块)会暂时保留(仅释放大部分资源),成为僵尸进程(状态为Z
),直到父进程回收或父进程退出。 - 孤儿进程(Orphan Process)的处理:若父进程先于子进程终止,子进程会被init 进程(PID=1,或 systemd 等现代初始化进程)收养,init 进程会负责调用
wait()
回收孤儿进程,避免其成为僵尸进程。
1.2 进程退出的三种情况
-
代码运行完毕,结果正确。
-
代码运行完毕,结果不正确。
-
代码异常终止。
1.3 进程常见退出方法
-
main函数返回值。
-
调用exit函数。
可以通过 echo $? 查看最近一个退出进程的退出码。
退出码0代表代码运行完毕,结果正确;非0代表代码运行完毕,结果不正确。
代码异常终止是通过操作系统发送信号终止的。
2. 进程等待
2.1 进程等待的必要性
-
防止僵尸进程问题,进而造成内存泄漏。
-
方便父进程管理子进程,通过进程等待可以知道交给子进程的任务完成的怎么样。
2.2 进程等待的系统调用
2.2.1 wait
等待任意一个子进程终止。
函数原型
#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int* status);
参数
- status: 输出型参数,获取子进程退出状态,不关心则可以设为
NULL
。
返回值
-
成功时:返回终止子进程的PID。
-
失败时:返回 -1,并设置 errno。
2.2.2 waitpid
#include<sys/types.h>
#include<sys/wait.h>pid_t waitpid(pid_t pid , int *status , int options);
参数
-
pid:
-
pid > 0
: 等待进程ID等于pid的特定子进程。 -
pid = -1
: 等待任意子进程,等同于wait
。 -
pid = 0
: 等待与调用进程同进程组的任意子进程。 -
pid < -1
: 等于进程组ID等于pid绝对值的任意子进程。
-
-
status: 输出型参数,获取子进程退出状态,不关心则可以设为
NULL
。 -
options:
-
默认为0,表示阻塞等待。
-
WNOHANG
: 非阻塞等待(通常配合循环进行使用)
-
返回值
-
成功时:返回终止子进程的PID。
-
失败时:返回 -1,并设置 errno。
-
如果指定了
WNOHANG
且没有子进程状态发生改变,返回0。
2.2.3 输出型参数status
-
传递 NULL,表示不关心子进程的退出状态信息。
-
否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
-
status不能简单的当作整形来看待,可以当作位图来看待。
-
可通过系统提供的宏解析status参数
-
WIFEXITED(status)
: 子进程正常终止为真。- 等价:
(status&0x7f) == 0
- 等价:
-
WEXITSTATUS(status)
: 获取子进程的退出码。- 等价:
(status >> 8)&0xff
- 等价:
-
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>int main () {pid_t id = fork();if (id == 0) {// child processint count = 5;while (count--) {printf("I am a child process , pid: %d , ppid: %d\n" , getpid() , getppid());sleep(1);}exit(100);} else if (id > 0) {// father processint status = 0;while (1) {pid_t res = waitpid(id , &status , WNOHANG);if (res > 0) {// if (WIFEXITED(status)) { // wifexitedif ((status&0x7f) == 0) { // printf("child process exit code: %d\n" , WEXITSTATUS(status)); // wexitstatusprintf("wait success , child process exit code: %d\n" , (status >> 8)&0xff); // wexitstatus} else {printf("signal code: %d\n" , status&0x7f);}break;} else if (res == 0) {printf("sleep 1s continuous wait\n");sleep(1);} else {perror("waitpid ");exit(1);}}} else {perror("fork ");}return 0;
}
3. 进程替换
3.1 概念
程序替换是指在进程运行过程中,替换当前进程的代码和数据,使其执行另一个完全不同的程序,但进程ID(PID)保持不变。
-
被替换的部分
-
代码段:新程序的代码指令完全替换原程序的代码指令。
-
数据段:包括初始化的全局变量、静态变量等,被新程序的数据覆盖。
-
堆和栈:原有堆和栈会被释放,新程序会重新初始化自己的堆和栈结构。
-
内存映射:原进程通过
mmap
映射的共享库或文件会被释放,新程序会加载自己的共享库和文件映射。
-
3.2 替换函数
#include <unistd.h>int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);int execve(const char *path, char *const argv[], char *const envp[]);
函数后缀含义
- l (list):参数以可变参数列表形式传递,最后一个参数必须是NULL。
-
v (vector):参数以字符串数组形式传递,数组最后一个元素必须是NULL。
-
p (path):在
PATH
环境变量指定的目录中查找可执行文件。 -
e (environment):可以传递自定义的环境变量数组。
返回值
- exec系列函数不用返回值判断,只要返回,就是失败。
3.2.1 execl
execl("/bin/ls" , "ls" , "-l" , NULL);
3.2.2 execlp
execlp("ls" , "ls" , "-l" , NULL);
3.2.3 execle
char *env[] = { "PATH=/bin" , "USER=test" , NULL };
execle("/bin/ls", "ls", "-l", NULL, env);
3.2.4 execv
char *argv[] = {"ls" , "-l" , NULL};
execv("/bin/ls", argv);
3.2.5 execvp
char *argv[] = {"ls" , "-l" , NULL};
execvp("ls", argv);
3.2.6 execvpe
char *argv[] = {"ls", "-l", NULL};
char *env[] = { "PATH=/bin" , "USER=test" , NULL };
execvpe("ls", argv, env);