初识fork函数:在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
基本概念:在父进程中返回子进程的 PID(进程ID),在子进程中返回 0,如果出错则返回 -1。
进程调用fork,当控制转移到内核中的fork代码后,内核做:分配新的内存块和内核数据结构给子进程,将父进程部分数据结构内容拷贝至子进程,添加子进程到系统进程列表当中 fork返回,开始调度器调度。
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程,看如下程序。
int main( void )
{
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
printf("After:pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after 消息有43677打印的。而进程43677没有打印before,原理如下图所示:
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器 决定。
进程终止:
进程终止有三种场景,分别是:(1)代码运行完毕,结果正确 (2)代码运行完毕,结果不正确 (3)代码异常终止
进程常见退出方法:
1.正常终止(可以通过echo $? 查看进程退出码):(1)从main返回 (2)调用exit (3_exit
int main() {
return 0; // 进程正常终止
}
#include <stdlib.h>
exit(0); // 正常终止,状态码0
#include <unistd.h>
_exit(0); // 立即终止,不执行清理
异常终止:ctrl + c,信号终止
exit函数和_exit函数的区别
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
printf("这行内容在缓冲区中"); // 注意没有换行符
// exit(0); // 会输出上面没有换行的内容
_exit(0); // 不会输出上面没有换行的内容
return 0;
}
特性 | exit() | _exit() |
---|---|---|
头文件 | <stdlib.h> | <unistd.h> |
是否刷新缓冲区 | 是 | 否 |
调用退出处理函数 | 是(atexit注册的) | 否 |
关闭标准IO流 | 是 | 否 |
进程等待:父进程等待子进程终止并获取其退出状态的机制(防止子进程变成僵尸进程从而导致内存泄漏)
进程等待的方法:
wait:
#include <sys/wait.h>
pid_t wait(int *status);
功能:等待任意一个子进程终止
参数:status
:用于存储子进程退出状态(可以为NULL)
返回值:成功:返回终止的子进程PID / 失败:返回-1(如没有子进程)
waitpid:
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
功能:等待特定子进程终止,提供更多控制选项
参数:
pid
:
(1)>0
:等待特定PID的子进程
(2)-1
:等待任意子进程(类似wait)
(3)0
:等待与调用进程同组ID的任何子进程
(4)<-1
:等待进程组ID等于pid绝对值的任何子进程
status
:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
options
:
(1)WNOHANG
:非阻塞,即使没有子进程退出也立即返回
(2)WUNTRACED
:报告已停止的子进程
(3)WCONTINUED
:报告已继续的子进程
返回值:成功:返回子进程PID,非阻塞且无子进程退出:返回0,失败:返回-1。
如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。如果不存在该子进程,则立即出错返回。

wait的基本使用示例:
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程运行,PID=%d\n", getpid());
sleep(2);
exit(123);
} else {
int status;
printf("父进程等待子进程...\n");
pid_t child_pid = wait(&status);
if (WIFEXITED(status)) {
printf("子进程%d正常退出,状态码=%d\n",
child_pid, WEXITSTATUS(status));
}
}
return 0;
}
原理图:

获取子进程status:在Unix/Linux系统中,父进程可以通过wait()
或waitpid()
系统调用获取子进程的终止状态。这个状态信息存储在status
变量中,需要使用特定的宏来解析。
WIFEXITED(status) // 子进程正常退出?
WEXITSTATUS(status) // 获取子进程退出码(当WIFEXITED为真)
WIFSIGNALED(status) // 子进程被信号终止?
WTERMSIG(status) // 获取终止信号(当WIFSIGNALED为真)
WIFSTOPPED(status) // 子进程是否停止?
WSTOPSIG(status) // 获取停止信号(当WIFSTOPPED为真)
WIFCONTINUED(status) // 子进程是否已继续执行?
wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待(只研究status低16比特位):
进程退出状态(低 8 位):如果进程正常退出(WIFEXITED(status)
为真),则低 8 位(status & 0xFF
)是进程的退出状态(exit()
或 _exit()
的参数)。
如果进程被信号终止(WIFSIGNALED(status)
为真),则低 8 位包含终止信号编号(WTERMSIG(status)
)。
进程终止原因(高 8 位):第 8 位(status & 0x80
)可能指示是否生成了核心转储(WCOREDUMP(status)
)。其他位可能用于特殊标志(如 WIFSTOPPED(status)
表示进程被暂停)。
测试代码:
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
int main( void )
{
pid_t pid;
if ( (pid=fork()) == -1 )
perror("fork"),exit(1);
if ( pid == 0 ){
sleep(20);
exit(10);
} else {
int st;
int ret = wait(&st);
if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
printf("child exit code:%d\n", (st>>8)&0XFF);
} else if( ret > 0 ) { // 异常退出
printf("sig code : %d\n", st&0X7F );
}
}
}
测试结果:
(1)# ./a.out #等20秒退出
child exit code:10
(2)# ./a.out #在其他终端kill掉
sig code : 9
进程等待方式:在Unix/Linux多进程编程中,父进程等待子进程终止有两种主要方式:阻塞等待和循环(非阻塞)等待。
(1)阻塞等待:阻塞等待是指父进程调用wait()
或waitpid()
时会暂停执行,直到有子进程状态改变。
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程(PID=%d)开始运行\n", getpid());
sleep(3);
printf("子进程结束\n");
exit(10);
} else {
printf("父进程(PID=%d)开始阻塞等待子进程\n", getpid());
int status;
pid_t child_pid = wait(&status); // 阻塞等待
if (WIFEXITED(status)) {
printf("子进程(PID=%d)正常退出,状态码=%d\n",
child_pid, WEXITSTATUS(status));
}
}
return 0;
}
特点:父进程会暂停执行,直到子进程终止
循环等待(非阻塞等待):循环等待是指父进程定期检查子进程状态,而不是一直阻塞等待。
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
printf("子进程(PID=%d)开始运行\n", getpid());
sleep(5);
printf("子进程结束\n");
exit(20);
} else {
printf("父进程(PID=%d)开始循环等待子进程\n", getpid());
int status;
pid_t child_pid;
int wait_count = 0;
while (1) {
child_pid = waitpid(pid, &status, WNOHANG); // 非阻塞检查
if (child_pid == -1) {
perror("waitpid失败");
exit(1);
} else if (child_pid == pid) {
if (WIFEXITED(status)) {
printf("子进程(PID=%d)正常退出,状态码=%d\n",
child_pid, WEXITSTATUS(status));
}
break;
} else if (child_pid == 0) {
wait_count++;
printf("等待中(%d秒)... 父进程可以做其他工作\n", wait_count);
sleep(1); // 等待1秒后再次检查
}
}
}
return 0;
}
特点:父进程可以继续执行其他任务,需要主动定期检查子进程状态,适用于需要父进程同时处理其他任务的场景。
option:在进程等待(如使用 waitpid()
系统调用)时,options
参数用于控制等待的行为。默认情况下,options
设置为 0
,表示阻塞等待(即父进程会挂起,直到目标子进程状态改变)。
option = 0的默认行为:
父进程会一直阻塞(挂起),直到以下情况发生:(1)目标子进程终止(正常退出或被信号杀死)。(2)目标子进程被信号暂停(如 SIGSTOP
)。(3)如果没有子进程状态改变,父进程会一直等待。
返回条件:(1)waitpid()
会返回任意一个终止或暂停的子进程的 PID。(2)如果没有子进程匹配指定的 pid
参数,则返回 -1
(错误码 ECHILD
)。
其他option模式:
选项标志 | 说明 |
---|---|
WNOHANG |
非阻塞模式:如果没有子进程状态改变,立即返回 0 (不阻塞父进程)。 |
WUNTRACED |
报告被暂停的子进程状态(如 SIGSTOP )。 |
WCONTINUED |
报告被恢复的子进程状态(如 SIGCONT )。 |