Linux系统——fork()函数详解(看这一篇就够了!!!)

fork()函数详解(包看包会!!!)

1.fork()简介

函数原型:

  • pid_t fork(void);//pid_t为int类型,进行了重载
  • pid_t getpid();// 获取当前进程的 pid 值。
  • pid_t getppid(); //获取当前进程的父进程 pid 值。

用于创建一个进程,所创建的进程复制父进程的代码段/数据段/BSS段/堆/栈等所有用户空间信息;在内核中操作系统重新为其申请了一个PCB,并使用父进程的PCB进行初始化;
在这里插入图片描述
如图所示 :我们将A 进程, 也就是调用 fork 的进程称之为父进程, 而新的进程(B 进程)称之为子进程

我们来看个例子:

int main()
{
	pid_t fpid; //fpid表示fork函数返回的值
	int count = 0;
	fpid = fork();
	if (fpid < 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++;
	}
	else {
		printf("i am the parent process, my process id is %d/n", getpid());
		printf("我是孩子他爹/n");
		count++;
	}
	printf("统计结果是: %d/n", count);
	return 0;
}

输出结果:
i am the child process, my process id is 5574
我是爹的儿子
统计结果是: 1
i am the parent process, my process id is 5573
我是孩子他爹
统计结果是: 1

为什么两个进程的fpid不同呢,这与fork函数的特性有关。

2.fork()特性

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

  1. 在父进程中,fork返回新创建子进程的进程ID;
  2. 在子进程中,fork返回0;
  3. 如果出现错误,fork返回一个负值;

因此我们可以通过fork返回的值来判断当前进程是子进程还是父进程。(注: fork 调用生成的新进程与其父进程谁先执行不一定,哪个进程先执行要看系统的进程调度策略

举个例子来解释fpid的值为什么在父子进程中不同:“相当于链表,进程形成了链表,父进程的fpid(p 意味point)指向子进程的进程id, 因为子进程没有子进程,所以其fpid为0.

看到这里大家对fork()有个大致了解了,让我们来看个例题:

int main(void)
{
	int i = 0;
	printf("i son/pa ppid pid  fpid/n");
	//ppid指当前进程的父进程pid
	//pid指当前进程的pid,
	//fpid指fork返回给当前进程的值
	for (i = 0; i<2; i++){
		pid_t fpid = fork();
		if (fpid == 0)
			printf("%d child  %4d %4d %4d/n", i, getppid(), getpid(), fpid);
		else
			printf("%d parent %4d %4d %4d/n", i, getppid(), getpid(), fpid);
	}
	return 0;
}

运行结果是:
i son/pa ppid pid fpid
0 parent 2043 3224 3225
0 child 3224 3225 0
1 parent 2043 3224 3226
1 parent 3224 3225 3227
1 child 1 3227 0
1 child 1 3226 0

我们来一步步分析:

第一步: 在父进程中,指令执行到for循环中,i=0,接着执行fork,fork执行完后,系统中出现两个进程,分别是p3224和p3225(后面我都用pxxxx表示进程id为xxxx的进程)。可以看到父进程p3224的父进程是p2043,子进程p3225的父进程正好是p3224。

我们用一个链表来表示这个关系: p2043->p3224->p3225

第一次fork后,p3224(父进程)的变量为i=0,fpid=3225(fork函数在父进程中返向子进程id)

所以打印出结果:
0 parent 2043 3224 3225
0 child 3224 3225


第二步: 假设父进程p3224先执行,当进入下一个循环时,i=1,接着执行fork,系统中又新增一个进程p3226,对于此时的父进程,p2043->p3224(当前进程)->p3226(被创建的子进程)。
对于子进程p3225,执行完第一次循环后,i=1,接着执行fork,系统中新增一个进程p3227,对于此进程,p3224->p3225(当前进程)->p3227(被创建的子进程)。从输出可以看到p3225原来是p3224的子进程,现在变成p3227的父进程。父子是相对的,这个大家应该容易理解。只要当前进程执行了fork,该进程就变成了父进程了,就打印出了parent。

所以打印出结果是:
1 parent 2043 3224 3226
1 parent 3224 3225 3227


第三步: 第二步创建了两个进程p3226,p3227,这两个进程执行完printf函数后就结束了,因为这两个进程无法进入第三次循环,无法fork,该执行return 0;了,其他进程也是如此。

以下是p3226,p3227打印出的结果:
1 child 1 3227 0
1 child 1 3226 0


这里不免有个问题:到p3226,p3227的父进程难道不该是p3224和p3225吗,怎么会是1呢?这里得讲到进程的创建和死亡的过程,在p3224和p3225执行完第二个循环后,main函数就该退出了,也即进程该死亡了,因为它已经做完所有事情了。p3224和p3225死亡后,p3226,p3227就没有父进程了,这在操作系统是不被允许的,所以p3226,p3227的父进程就被置为p1了,p1是永远不会死亡的。

3.fork()的执行过程

  1. 申请PID
  2. 申请PCB结构
  3. 复制父进程的PCB
  4. 将子进程的运行状态设置为不可执行的
  5. 将子进程中的某些属性清零,某些保留,某些修改
  6. 复制父进程的页(用到了写时拷贝技术)

写实拷贝技术: 父子进程在初始阶段共享所有的数据(全局、 栈区、 堆区、 代码), 内核会将所有的区域设置为只读。 当父子进程中任意一个进程试图修改其中的数据时, 内核才会将要修改的数据所在的区域(页) 拷贝一份。

画个图就很好理解了:
在这里插入图片描述

写时拷贝后:
在这里插入图片描述

  • 112
    点赞
  • 281
    收藏
    觉得还不错? 一键收藏
  • 19
    评论
### 回答1: Fork()函数Linux内核中一个重要的函数,它用于创建一个新的进程,并且新进程完全与父进程相同,但是它有一个独立的内存空间,拥有自己的独立的进程ID,和自己的执行流程。新进程会从fork()函数调用处开始执行,而父进程会在fork()函数调用处继续执行。 ### 回答2: fork()函数Linux系统中一个非常重要的系统调用函数,它用于创建一个新的进程。具体来说,该函数会在当前进程中创建一个子进程,使得原进程成为父进程,而子进程则是新创建的进程。 fork()函数的原型为: ```c #include <unistd.h> pid_t fork(void); ``` 其中pid_t是一个整型,它代表了进程的ID,父进程调用fork()函数后会返回子进程的pid,而子进程则会返回0。 调用fork()函数后,子进程会继承父进程的大部分资源,包括代码段、数据段、堆栈段等。因此,子进程会复制父进程的执行环境,并从fork()函数返回的地方开始执行。父进程和子进程后续的代码会同时执行,但是它们是完全独立的进程,互不干扰。 通过fork()函数,可以实现进程的并发执行。父进程可以继续执行它的任务,而子进程则可以独立地执行其他任务。这种并发执行的方式被称为多进程编程,是Linux系统中实现并发的常用手段。 需要注意的是,在父子进程中的某个进程退出后,另一个进程并不会受到影响,它会继续执行直到自己退出。另外,子进程会继承父进程的文件描述符和打开的文件,但是它们具有不同的文件描述符表,因此它们之间对文件的操作是互不干扰的。 总结来说,fork()函数Linux中创建进程的重要工具,它可以实现并发执行,提供了进程间独立运行的环境,是多进程编程不可或缺的一部分。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值