C语言使用fork()系统调用创建子进程
有些时候,创建多个子进程可用于提高任务处理效率或提高程序的并发性;在Linux系统下可使用fork()
系统调用创建一个新的子进程;
fork()系统调用
要使用fork()
函数,需引入unistd.h头文件;其函数原型如下:
pid_t fork(void);
调用fork()
成功后,将存在两个进程,一个父进程和一个子进程(假设只调用了一次);
这两个进程执行相同的文本段,通俗来讲就是子进程和父进程的代码一样;但两个进程各自拥有不同的栈段,数据段,以及堆段的拷贝;
子进程在创建时,栈段、数据段的内存内容是对父进程内存相应部分的复制。执行fork()
后,每个进程均可修改各自的栈段、数据段内容,却不影响另一个进程。
两个进程都从fork()
的返回处开始执行,执行的先后顺序并不一定,这取决于系统是如何调度的;
程序可通过fork()
的返回值来判断当前进程是父进程还是子进程;
fork()
函数的返回值:
- -1,表示无法创建子进程,原因可能是超过了进程数量的上限;
- 大于0的值,表示当前进程为父进程,返回值表示子进程的进程ID;
- 0,表示当前进程为子进程;
示例代码
这是一段演示fork()
使用的代码,其中使用了getpid()
函数可用于获取当前进程的进程ID;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static int i_data_segment = 123; //数据段的数据
//子进程和父进程有相同的文本段
void printdata(pid_t pid,int i1,int i2){
printf("PID=%ld %s i_data_segment=%d i_stack_segment=%d\n",
(long)getpid(),
(pid==0)?"(child)":"(parent)",
i1,
i2
);
}
int main(){
int i_stack_segment = 456; //栈段的数据
pid_t pid;
pid = fork();
//如果pid不为-1,从这里后将存在两个进程
//pid==0时表示当前进程是子进程
//pid>0时表示当前进程是父进程
//根据pid来判断是父进程还是子进程
switch(pid){
case -1:
/* fork() 返回 -1 表示子进程创建失败*/
printf("fork() failed\n");
exit(1);
case 0:
/*
执行子进程的任务
子进程拥有独立的数据段和堆段
且初始内存为父进程对应的内存拷贝
*/
//子进程修改的是自己栈段和数据段的变量
//不会影响到父进程
i_data_segment += 100; //修改数据段的变量
i_stack_segment += 100; //修改栈段的变量
//子进程拥有和父进程一样的文本段
//所以能调用printdata函数
printdata(pid,i_data_segment,i_stack_segment);
//子进程任务完成后,你也可以直接使用exit()函数退出子进程
break;
default:
/*
父进程执行的任务
*/
printf("parent: sleeping...\n");
sleep(3); //父进程休眠3秒,确保子进程结束
//这里等待子进程结束的方法只用于简单演示fork()的流程
//!!!不要在你的程序中采用这种方法 !!!
//要等待子进程结束,请了解 wait(),waitpid()等函数;
printf("parent: wake up.\n");
break;
}
//因为已跳出了switch块
//子进程和父进程都将执行这里的代码;
printdata(pid,i_data_segment,i_stack_segment);
//如果父进程不会退出,需要调用wait()这类函数回收子进程资源,否则子进程将成为僵尸进程;
//如果父进程先于子进程退出,子进程将成为孤儿进程;
}
运行效果
parent: sleeping... PID=53 (child) i_data_segment=223 i_stack_segment=556 PID=53 (child) i_data_segment=223 i_stack_segment=556 parent: wake up. PID=52 (parent) i_data_segment=123 i_stack_segment=456

可以看出,这里fork()
后,父进程开始执行并进入休眠(实际上父进程和子进程哪个先执行并不确定);
子进程运行后,修改了自身数据段和栈段的变量并打印,至于为什么有两条一样的输出,请仔细查看代码注释;
父进程唤醒后打印自身数据段和栈段的变量,可以发现子进程并没有修改它;