fork()二三事

本文详细解析了fork()函数在进程创建中的作用及进程管理机制,包括子进程与父进程的区别,进程组的概念,孤儿进程与僵死进程的产生,以及如何通过信号处理函数进行进程回收。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

fork();用于创建进程;返回值是整型:若是在子进程中则是返回0,父进程中返回子进程的PID

【例1】
void fork17() 
{
    if (fork() == 0) {
	printf("Child: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
    }
    else {
	printf("Parent: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
    }
    while(1);
} 

运行结果:
在这里插入图片描述
fork();创建了一个子进程;子进程,父进程分别打印了各自的pid与pgid
pid=4847和pid=4848属于同一进程组,组长是父进程pid=4847
值得注意的是程序“卡”住了,明显是由于了while(1);这条指令使程序(两个)停止在了这个位置
这时候,ctrl+z使进程挂起,再用ps指令查看当前正在运行的进程:
在这里插入图片描述

发现有两个forks.o进程,一个父进程一个子进程

在这里插入图片描述
杀死pid值为4847的forks.o进程,发现pid为4848的进程也同时消失了,说明pid=4847的进程使4848进程的父进程,4847在被杀死之前回收了其子进程
但是,如果在如果用ctrl+c替换前文提到的ctrl+z,会有不一样的结果:
在这里插入图片描述
原因使ctrl+z意为“挂起”,即将程序只是表面上退出,但是保持其进程继续在后台运行,只是在我们“眼前消失”了
而ctrl+c表示终止,将终止所有进程,后台也不保持运行

【例2】
void fork16() 
{
    if (fork() == 0) {
	printf("Child1: pid=%d pgrp=%d\n",
	       getpid(), getpgrp());
	if (fork() == 0)
	    printf("Child2: pid=%d pgrp=%d\n",
		   getpid(), getpgrp());
	while(1);
    }
} 

运行结果:

在这里插入图片描述
按理说1.应该有三个进程 2.而且程序并没有被在while(1);处停住,而是似乎自动被挂起了。可以执行其他命令
进程树如下:

最初的父进程最终终止了,而两个子进程都陷入了while(1);而没有终止,因此父进程在终止的时候不会回收其子进程
于是两个子进程都变成了孤儿进程,由inti进程收养这时要用kill命令杀死进程:

在这里插入图片描述

【例3】
#define N 5
int ccount = 0;
void fork15()
{
    pid_t pid[N];
    int i;
    ccount = N;

    signal(SIGCHLD, child_handler2);

    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    sleep(1);
	    exit(0); /* Child: Exit */

	}
    while (ccount > 0) {
	pause();
	}
}

void child_handler2(int sig)
{
    int child_status;
    pid_t pid;
    while ((pid = wait(&child_status)) > 0) {
	ccount--;
	printf("Received signal %d from process %d\n", sig, pid); /* Unsafe */
	fflush(stdout); /* Unsafe */
	}
}

运行结果:

在这里插入图片描述
在函数fork15中有一条语句:signal(SIGCHLD, child_handler2); 意思即为在以后的进程中,如果进程一旦捕获了一个类型为SIGCHLD的信号则会调用*child_handler这个信号处理程序来处理信号

SIGCHLD(==17):一个子进程停止或者终止

再来关注child_handler这个函数:里面用wait函数做了一个循环条件,因为在调用signal之前就已经把所以的子进程创建完成,所以signal中的while循环会等待所有的子进程结束才退出循环,wait的默认状态是挂起调用进程,只有当有某一子进程结束时才会返回这个子进程的pid,没有未返回子进程则返回-1

进程树如下:
在这里插入图片描述
让子程序sleep(1)的原因是保证在任一子进程结束时,父进程已经进入pause。

当接收到子进程的终止信号SIGCHLD时会停止pause,继而执行调用child_handler,打印相关信息,从child_handler返回之后ccount已经减到了0,退出循环,退出程序

【例4】
#define N 5
int ccount = 0;
void child_handler(int sig)
{
    int child_status;
    pid_t pid = wait(&child_status);
    ccount--;
    printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */
    fflush(stdout); /* Unsafe */
}

/*
 * fork14 - Signal funkiness: Pending signals are not queued
 */
void fork14()
{
    pid_t pid[N];
    int i;
    ccount = N;
    signal(SIGCHLD, child_handler);

    for (i = 0; i < N; i++) {
	if ((pid[i] = fork()) == 0) {
	    sleep(1);
	    exit(0);  /* Child: Exit */
	}
    }
    while (ccount > 0)
	;
}

运行结果:

在这里插入图片描述
与例3类似,程序会先创建好5个子进程,然后在while(ccount>0);停住,接收SIGCHLD之后调用child_handler。但是在信号处理程序中只wait了一次,所以内核也只会从系统中删除一个子进程。剩下的四个均成为了僵死进程,从child_handler返回之后程序继续停在while(ccount>0);
ctrl+z将进程挂起,ps查看运行进程,发现正是有一个父进程,和四个僵死子进程。
此时只需将父进程杀死,就可以一并回收僵死子进程:

在这里插入图片描述

【例5】
#define N 5
void int_handler(int sig)
{
    printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
    exit(0);
}

/*
 * fork13 - Simple signal handler example
 */
void fork13()
{
    pid_t pid[N];
    int i;
    int child_status;

    signal(SIGINT, int_handler);
    for (i = 0; i < N; i++)
	if ((pid[i] = fork()) == 0) {
	    /* Child: Infinite Loop */
	    while(1)
		;
	}

    for (i = 0; i < N; i++) {
	printf("Killing process %d\n", pid[i]);
	kill(pid[i], SIGINT);
    }

    for (i = 0; i < N; i++) {
	pid_t wpid = wait(&child_status);
	if (WIFEXITED(child_status))
	    printf("Child %d terminated with exit status %d\n",
		   wpid, WEXITSTATUS(child_status));
	else
	    printf("Child %d terminated abnormally\n", wpid);
    }
}

运行结果:

在这里插入图片描述
程序中signal(SIGINT, int_handler);指每当进程接受到SIGINT信号就会调用*int_handler信号处理函数。

第一个循环为同一个父进程创建了5个子进程,并且让子进程们在while(1);处停住

第二个循环又在父进程中将这五个子进程杀死。每当一个子进程收到终止信号就会调用信号处理函数,打印“Process xxxx received signal 2”(SIGINT==2)
kill(pid[i], SIGINT);——将信号SIGINT传给 进程ID=pid[i]的子进程,使相应子进程终止

第三个循环检查子进程的退出状态
子进程退出会设置child_status的值
如果正常退出,WIFEXITED(child_status)返回为真
只有正常退出才会定义WEXITSTATUS这个状态
状态码(转载)

<think>好的,我需要回答用户关于fork函数用法及其常见场景的问题。首先,我得回忆一下fork函数的基本概念,它在Linux系统中用于创建新进程,是进程复制的关键函数。用户可能想知道具体怎么使用,以及在实际开发中有哪些应用场景。 根据提供的引用,比如引用[3]、[4]、[5]都提到fork函数创建子进程的过程,以及代码示例。我需要确保正确解释fork的返回值,父进程和子进程的不同处理逻辑。fork返回两次,父进程得到子进程的PID,子进程得到0,这点很重要,必须准确说明。 接下来是使用步骤,应该分点列出,比如包含头文件、调用fork、根据返回值区分父子进程。同时要提到常见的错误,比如忘记处理错误情况,或者没有正确使用wait/waitpid导致僵尸进程。引用[5]中的代码示例可以用来展示如何通过if语句处理不同的返回值。 然后是应用场景,根据引用[4]和[5],常见的场景包括网络服务中的并发处理,比如父进程监听请求,fork子进程处理每个请求;还有shell命令执行,以及并行任务处理。需要结合这些引用内容,确保场景描述准确。 注意项部分,需要提到资源继承的问题,比如子进程会复制父进程的文件描述符,可能导致资源泄漏,需要在子进程中正确关闭。此外,僵尸进程的处理和避免也是重点,引用[5]提到子进程执行后应调用exit,而父进程需要wait。同时,引用[1]提到Fork/Join框架中的顺序问题,虽然这里可能不直接相关,但可以类比说明进程间协调的重要性。 最后,生成相关问题时要围绕fork的使用场景、注意项、与其他并发模型的对比等,确保问题有针对性且能帮助用户深入理解。 需要检查LaTeX格式是否正确,行内公式用$...$,独立公式用$$...$$,但在这个回答中可能用不到数学公式,所以可能不需要。同时确保引用标识正确添加,比如在提到网络服务场景时引用[4],进程复制时引用[5]等。 现在组织语言,确保结构清晰,分步骤和场景,注意项,最后相关问题。避免技术错误,比如正确说明fork的行为和常见问题,如僵尸进程、资源管理。</think>### Fork函数的使用方法及常见场景 #### 一、Fork函数的基本用法 1. **函数定义** `fork()` 是Linux系统中用于创建新进程的系统调用。调用后,父进程会创建一个**完全独立的子进程**,子进程复制父进程的代码段、数据段、堆栈等资源[^3][^5]。 2. **返回值说明** - 父进程:返回子进程的PID(进程ID)。 - 子进程:返回0。 - 失败时返回-1(如系统资源不足)。 3. **基本代码示例** ```c #include <unistd.h> #include <stdio.h> int main() { pid_t pid = fork(); if (pid < 0) { perror("fork failed"); } else if (pid == 0) { // 子进程逻辑 printf("Child process (PID=%d)\n", getpid()); _exit(0); // 子进程结束 } else { // 父进程逻辑 printf("Parent process (Child PID=%d)\n", pid); wait(NULL); // 等待子进程结束 } return 0; } ``` #### 二、常见应用场景 1. **网络服务并发处理** 父进程监听端口,接收到请求后调用 `fork()` 创建子进程处理具体任务,父进程继续监听[^4]。例如: - Web服务器处理HTTP请求。 - 数据库连接池管理。 2. **Shell命令执行** Shell通过 `fork()` 创建子进程,子进程调用 `exec()` 执行用户输入的命令(如 `ls`、`grep`),父进程等待子进程完成[^5]。 3. **并行任务分解** 类似Fork/Join框架,将大任务拆分为子任务并行处理。例如: ```c if (fork() == 0) { // 子进程处理任务A } else { // 父进程处理任务B wait(NULL); // 等待子进程 } ``` #### 三、注意项 1. **资源管理** - **文件描述符**:子进程会复制父进程打开的文件描述符,需及时关闭未使用的描述符,避免资源泄漏。 - **内存隔离**:子进程修改变量不会影响父进程(如 `int x=5`,子进程修改为 `x=10`,父进程中仍为5)。 2. **避免僵尸进程** - 子进程结束后需调用 `_exit()` 或 `exit()` 释放资源。 - 父进程必须调用 `wait()` 或 `waitpid()` 回收子进程状态[^4]。 3. **错误处理** - 检查 `fork()` 返回值,处理资源不足等异常情况。 #### 四、与其他并发模型的对比 - **Fork/Join框架**:适用于任务可分解的并行计算(如排序),但需注意任务调度顺序(如 `f1.fork(), f2.fork(), f2.join(), f1.join()`)以避免性能问题[^1][^2]。 - **多线程**:共享内存,适合高频率数据交互的场景,但需处理锁竞争。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值