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中即使是全局变量,父进程与子进程也用的是各自的变量

posted @ 2025-08-12 15:26  爱吃鸡魔人zf  阅读(8)  评论(0)    收藏  举报