Linux--深度认识"fork"函数

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/tonglin12138/article/details/86490735

写在前面

看似功能很简单的fork()函数,乱七八糟的东西实则有一大堆。这几天为了真正搞懂它的原理真的是让人几乎崩溃… ̄□ ̄|| 所以在这里做以总结。

1.fork()功能

举个例子:

#include <unistd.h>  
#include <stdio.h>   
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;  
}  

在这里插入图片描述

fork函数将运行着的程序分成2个几乎完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两个进程中的线程继续执行,就像是两个用户同时启动了该应用程序的两个副本。子进程会共享父进程的所有资源,变量。但是子进程从父进程拷贝下的所有资源会放到一个新的地址中,拥有自己独立的逻辑地址空间。子进程修改一个从父进程中拷贝下来的变量,父进程中的这个变量不会改变。

2.fork到底复制了父进程的哪些资源?

子进程从父进程拷贝的内容主要有以下:
用户号UIDS和用户组号GIDS
环境Environment
堆栈
共享内存
打开文件的描述符
执行时关闭(Close-on-exec)标志
信号(Signal)控制设定
进程组号
当前工作目录
根目录
文件方式创建屏蔽字
资源限制
控制终端

子进程独有
进程号PID
不同的父进程号
自己的文件描述符和目录流的拷贝
子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)
不继承异步输入和输出

父进程和子进程拥有独立的地址空间和PID参数。
子进程从父进程继承了用户号和用户组号,用户信息,目录信息,环境(表),打开的文件描述符,堆栈,(共享)内存等。
经过fork()以后,父进程和子进程拥有相同内容的代码段、数据段和用户堆栈,就像父进程把自己克隆了一遍。事实上,父进程只复制了自己的PCB块。而代码段,数据段和用户堆栈内存空间并没有复制一份,而是与子进程共享。只有当子进程在运行中出现写操作时,才会产生中断,并为子进程分配内存空间。父子进程间的复制就是–-写时拷贝

3. 父子进程之间的写时拷贝

举个例子:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main()
{
	pid_t pid;
	int num = 5;
	int status;
	pid = fork();
	switch(pid)
	{
		case -1:
			perror("create porcess is failed");
			exit(-1);
		case 0:
			printf("child process num is:%d\n", num);
			exit(0);
		default:
			wait(&status);
			printf("parent process num is: %d\n", num);
			break;
	}
	return 0;
}

执行结果:
在这里插入图片描述
我们发现这个时候两者的num值是相同的,那我们想想如果在子进程中改变一下num这个值,父进程中的num值会被改变吗?

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

int main()
{
	pid_t pid;
	int num = 5;
	int status;
	pid = fork();
	switch(pid)
	{
		case -1:
			perror("create porcess is failed");
			exit(-1);
		case 0:
		    num = 10;
			printf("child process num is:%d\n", num);
			exit(0);
		default:
			wait(&status);
			printf("parent process num is: %d\n", num);
			break;
	}
	return 0;
}

执行结果:
在这里插入图片描述

这时我们发现子进程中的num值改变了而父进程中的值没有改变,这是为什么呢?
这个时候要牵扯到两个概念,一个是逻辑地址(虚拟地址),一个是物理地址。

逻辑地址: cpu所生成的地址。cpu产生的逻辑地址分为:p(页号)它包含在每个页在物理内存中的基址,用来做页表的索引;d(页偏移),同基址相结合,用来确定送入内存设备的物理内存地址。

物理地址:内存单元所看到的地址。
用户程序看不到物理地址。用户只生成逻辑地址。逻辑地址与物理地址呈现一一映射的关系。

fork()会产生一个和父进程完全相同的子进程,一般情况下,子进程会调用exec函数族去执行新的程序,这个时候子进程就会有新的栈,堆,数据段,和代码段。linux系统经过不断发展,从效率的角度出发,创造了一个写时复制技术。linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程。

在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个。当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

就以上面两个例子分析一下: 第一个例子,子进程访问了num的值而却没有改变num的值,只是相当于读取了一下,这个时候内核不会给子进程分配新的资源,而是接着共享父进程的资源,这个时候num的值还是5,这样就节省了系统调用时候的资源。
第二个例子,子进程在执行过程中改变了num的值,这个时候系统发现了这个操作,就会给子进程分配新(新指物理地址上的新)的栈,堆和数据段,并且将父进程中fork之前的变量拷贝一份(拷贝的值和父进程中的值就没有任何关系了),然后子进程再对这些值进行操作,所以二者虽然用的是相同的变量名,但是在物理地址上已经完全不相同了。

4.父子进程关于文件描述符

在这里插入图片描述
这种共享的方式使父、子进程对同一个文件使用了同一个文件偏移量。如果父、子进程写到同一个文件描述符,但有没有任何形式的同步,那么它们的输出就会相互混合。在fork之后处理文件描述符有两种常见的情况:

(1)父进程等待子进程完成。在这种情况下,父进程无须对其描述符做任何处理。当子进程终止之后,它曾进行过读、写的人一个共享描述符的文件偏移量已经执行了相应的更新。

(2)父、子进程各自执行不同的程序段。这种情况下,在fork之后,父、子进程各自关闭它们不需使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方式是网络服务进程中常用的方式。

总结:父子进程共享“文件表项”(file对象),同dup一样,这会增加“文件达开计数”(file对象的引用计数),只不过fork增加的引用计数来自不同进程(父进程和子进程)中的描述符,dup增加的引用来自同一文件描述符。但父子进程独立运行后再打开的文件就不在共享file对象,这个两个独立进程打开文件情况一样。父子进程传递文件描述符和两独立进程传递描述符性质一样。

注意点
1、父进程和子进程可以共享打开的文件描述符。

2、父子进程共享文件描述符的条件:在fork之前打开文件。

3、对于两个完全不相关的进程,文件描述符不能共享。

4、父子进程文件描述符是共享的,但是关闭的时候可以分别关闭,也可以同时在公有代码中关闭。

5.文件表项只有在所有引用它的fd(即文件描述符)全部关闭的情况下才会真正关闭。所以如上述情况,如果子进程关闭父、子进程共享的文件描述符后父进程仍可以使用对应的文件表项。(参见写时拷贝)

ps:共享文件描述符的意思参考:https://blog.csdn.net/tonglin12138/article/details/86490901

猜你喜欢

转载自blog.csdn.net/tonglin12138/article/details/86490735