linux中的fork函数
例子1
#include <unistd.h>
#include <stdio.h>
int main ()
{
pid_t fpid; //fpid表示fork函数返回的值
int count=0;
fpid=fork(); // ← 父子都从这里“回来”
if (fpid < 0) // 父:pid>0 跳过;子:pid==0 跳过
printf("error in fork!");
else if (fpid == 0) // 子进程命中
{
printf("i am the child process, my process id is %d\n",getpid());
printf("我是子进程\n");
count++;
sleep(10); // 子进程睡眠10s
}
else // 父进程命中
{
printf("i am the parent process, my process id is %d\n",getpid());
printf("我是父进程\n");
count++;
sleep(5); //父进程睡眠5s
}
printf("统计结果是: %d\n",count); //父子进程均会执行
return 0;
}
这里我们编译完的可执行文件名字为test,另起一个终端,执行以下命令:
watch -n0.1 'ps -eo pid,ppid,stat,comm | grep test'
可以每0.1秒展示test进程的pid,ppid,stat,comm信息
原终端结果:
fanbao@sea:~/tmp$ ./test
i am the parent process, my process id is 25679
我是父进程
i am the child process, my process id is 25680
我是子进程
统计结果是: 1
fanbao@sea:~/tmp$ 统计结果是: 1
另一个终端输出结果:
| PID | PPID | STAT | COMM |
|---|---|---|---|
| 25679 | 16336 | S+ | test |
| 25680 | 25679 | S+ | test |
| 25680 | 3499 | S | test |
在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)……
为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 1)在父进程中,fork返回新创建子进程的进程ID;
- 2)在子进程中,fork返回0;
- 3)如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
fork出错可能有两种原因:
- 1)当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
- 2)系统内存不足,这时errno的值被设置为ENOMEM。
创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。
每个进程都有一个独特(互不相同)的进程标识符(process ID),可以通过getpid()函数获得,还有一个记录父进程pid的变量,可以通过getppid()函数获得变量的值。
执行完fork后,进程1的变量为count=0,fpid!=0(父进程)。进程2的变量为count=0,fpid=0(子进程),这两个进程的变量都是独立的,存在不同的地址中,不是共用的,这点要注意。
下面解释表格中的内容:
逐行解读:
第一行
- 25679 是父进程,PPID = 16336(你的 shell)。状态 S+:在前台睡眠。
第二行
- 25680 是刚 fork 出来的子进程,PPID 仍指向父进程 25679,状态也是 S+。
第三行
- 父进程 25679 已经结束并消失,子进程 25680 的 PPID 变成 3499(即系统的 init 或某个 subreaper)。状态变为 S(不再是 +,因为它已不在前台进程组)。
这完全符合 Linux 的 孤儿进程 处理逻辑:父进程死后,子进程会被 re-parent 到 PID 1(或最近的 subreaper)。
subreaper 是 Linux 内核在 3.4 版本引入的一个机制,用来解决传统“孤儿进程只能被过继给 PID 1”带来的单点瓶颈与容器/namespace 场景下隔离性不足的问题
STAT 字段中 S 与 S+ 的区别
| 字符 | 含义 |
|---|---|
| S | 可中断睡眠(等待事件完成,如 read()、sleep())。 |
| + | 前台进程组(与终端关联,Ctrl-C 等信号会发给它)。 |
- S:进程在睡眠,但不属于前台进程组,通常是后台任务或孤儿进程。
- S+:进程在睡眠,且属于前台进程组,收到终端键盘中断(Ctrl-C、Ctrl-Z)。
vfork和fork
| 维度 | fork | vfork |
|---|---|---|
| 地址空间 | 写时复制(COW) | 与父完全共享 |
| 父进程状态 | 继续并发运行 | 阻塞直到子exit/exec |
| 子能否改全局/栈变量 | 安全 | 可以改,但会污染父进程 |
| 子能否return | 安全 | 禁止return,会炸掉父进程栈 |
| 速度 | 稍慢(页表复制) | 极快(只做task_struct) |
| 可移植性 | POSIX,无处不在 | POSIX.1,但已被弃用/禁用(glibc 2.34+ 标记为deprecated) |
| 典型用途 | 通用 | 只剩一种场景:马上exec新程序 |
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
int global = 0;
int main()
{
pid_t pid;
int a = 9;
if ((pid = vfork()) < 0) {
return EXIT_FAILURE;
} else if (pid == 0) { /* child */
++a;
++global;
printf("Child Process a = %d,global = %d\n",a,global);
exit(0);
} else { /* parent */
printf("Parent Process a = %d,global = %d\n", a,global);
return 0;
}
}
sea@sea-VMware-Virtual-Platform:~/tmp$ ./test1
Child Process a = 10,global = 1
Parent Process a = 10,global = 1
可以看出,在 vfork 中,变量a在main函数中,属于局部变量,子进程对它的修改对父进程可见,这点和 fork 很不一样,fork中即使是全局变量,父进程与子进程也用的是各自的变量

浙公网安备 33010602011771号