Analysis of fork() and the underlying implementation

I remember that when I first came into contact with the fork() function, I was confused by how many times "printf" was output. However, "Huang Tian pays attention to people". Ha~ After learning about the process and process creation fork, I finally figured out the ins and outs of it. Not much nonsense, let's talk about my little accumulation

 
 An existing process can call the fork function to create a new process. The prototype is as follows:
  1. #include<unistd.h>  
  2.   
  3. pid_t fork(void);  
  4.   
  5. Return value: 0 is returned from the process, the parent process returns the process id, and an error returns -1  

The fork () system call creates a new process by copying an existing process. Processes are stored in a doubly circular linked list called a task queue. Each item in the linked list is a structure of type task_struct that becomes a process descriptor .That is, the process PCB we wrote
 
Little knowledge: The kernel identifies each process by a process identification value or PID at a location. At the same time, its maximum value defaults to 32768, the maximum value of short int short integer. It is the maximum number of processes allowed to exist at the same time in the system. It can be Go to the proc directory under linux to find a pid_max file and open it for verification. For example
 
 

 

what fork() does when it runs


First let's look at a piece of code, but there's a little weirdness here:
 
copy code
  1 /*************************************************************************
  2   > File Name: 1.c
  3   > Author: tp
  4   > Mail: 
  5   > Created Time: Mon 07 May 2018 07:57:28 PM CST
  6  ************************************************************************/
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <unistd.h>
 11 int main( void)
 12 {
 13     printf("change world!\n");
 14     pid_t pid = fork();
 15     if( pid == -1) {perror("fork"),exit(1); }
 16 
 17     printf( "pid=%d, returnVal=%d\n", getpid(), pid);
 18     sleep( 1);
 19     exit(0);
 20 }
~     
copy code

 


这段代码的运行结果,大家如果像我当时不了解fork的时候,一定会以为输出结果是两个"change world!",然后2个printf里面的内容. 因为
 
我们复制出来了两个一模一样的进程,那么他们就应该做同样的事情. But!!! 我们看运行结果:
 
 
结果并非我们想的那样,这个时候我们就需要知道fork出子进程之后,程序的运行细节.这里我画一张图帮助我理解:
 
 

 
 
一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的.这取决于内核所使用的调度算法.如果要求父,子进程之间相互同步.则要求某种形式的进程间通信. 好了我们继续,当进程调用fork后,当控制转移到内核中的fork代码后,内核会做4件事情:
 
  1.分配新的内存块和内核数据结构给子进程
 
  2.将父进程部分数据结构内容(数据空间,堆栈等)拷贝至子进程
 
  3.添加子进程到系统进程列表当中
 
  4.fork返回,开始调度器调度
 
 
为什么fork成功调用后返回两个值? 
 
由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。所以fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值不同
其中父进程返回子进程pid,这是由于一个进程可以有多个子进程,但是却没有一个函数可以让一个进程来获得这些子进程id,那谈何给别人你创建出来的进程。而子进程返回0,这是由于子进程可以调用getppid获得其父进程进程ID,但这个父进程ID却不可能为0,因为进程ID0总是有内核交换进程所用,故返回0就可代表正常返回了。
 
 
从fork函数开始以后的代码父子共享,既父进程要执行这段代码,子进程也要执行这段代码.(子进程获得父进程数据空间,堆和栈的副本. 但是父子进程并不共享这些存储空间部分. (即父,子进程共享代码段.)。现在很多实现并不执行一个父进程数据段,堆和栈的完全复制. 而是采用写时拷贝技术(不懂可以戳进去看一看).这些区域有父子进程共享,而且内核地他们的访问权限改为只读的.如果父子进程中任一个试图修改这些区域,则内核值为修改区域的那块内存制作一个副本, 也就是如果你不修改我们一起用,你修改了之后对于修改的那部分内容我们分开各用个的.
 
 
 
 

父子进程文件共享问题


 
来看个例子
copy code
  1 /*************************************************************************
  2   > File Name: 2.c
  3   > Author: tp
  4   > Mail: 
  5   > Created Time: Mon 07 May 2018 12:40:39 PM CST
  6  ************************************************************************/
  7 
  8 #include <stdio.h>
  9 #include <stdlib.h>
 10 #include <unistd.h>
 11 #include <fcntl.h>
 12 
 13 int set = 110;
 14 int main( void)
 15 {
 16     printf( "before fork\n");
 17     pid_t pid = fork( );
 18     if( pid < 0){ perror(" fork"),exit( 1);}
 19 
 20     if( pid == 0)
 21     {
 22         ++set;
 23         printf( "son pid=%d, %d\n", getpid(), set);
 24     }
 25     else
 26     {
 27         sleep( 1);
 28         printf( "parent pid=%d , %d\n", getpid( ), set);
 29     }
 30     exit( 0);
 31 }
copy code

看一下结果:

不难注意到  before fork”这句话只是被打印了一次,这个从上面的例子,这不难理解;与此同时子进程中的set的值被改变了。此时再进行一个重定向操作会发生什么

 

copy code
copy code

 

出现很神奇的现象! 这个时候打印了出了两次“before fork”,不仅仅是如此,上述针对父进程的标准输出执行重定向操作导致了让子进程也执行重定向的操作。

透过现象看本质,来细细分析一下。针对打印两次“before fork”,首先,先要知道标准IO库是带缓冲,而像printf这种直接输出到标准输出时,这个缓冲区是由换行符刷新的;而当执行了重定向操作,这里就是将标准输出重定向到文件,文件就不会刷新缓冲区了,好,由于在fork之前调用了一次printf,但fork之后,该行数据仍留着缓冲区,然后父进程数据空间被复制到子进程中,该缓冲去也被复制进去,这样父子进程都各自带有该行内容的缓冲区了,然后exit之前就又追加了一次“before fork”到缓冲区。所以最后就打印了两次。

  再一个就是,在重定向父进程的标准输出时,子进程标准输出也被重定向。这就源于父子进程会共享所有的打开文件。 因为fork的特性就是将父进程所有打开文件描述符复制到子进程中。当父进程的标准输出被重定向,子进程本是写到标准输出的时候,自然写到其它地方,与此同时,它还更新了与父进程共享的该文件的偏移量。这里,在父进程等待子进程执行时,子进程将改为写到文件show.out中;在子进程终止后,父进程也写到show.out中,同时其输出会追加在子进程所写数据之后。

 

 

在fork之后处理文件描述符一般又以下两种情况:

  1.父进程等待子进程完成。此种情况,父进程无需对其描述符作任何处理。当子进程终止后,它曾进行过读,写操作的任一共享描述符的文件偏移已发生改变。

  2.父子进程各自执行不同的程序段。这样fork之后,父进程和子进程各自关闭它们不再使用的文件描述符,这样就避免干扰对方使用的文件描述符了。这类似于网络服务进程。

 

同时父子进程也是有区别的:它们不仅仅是两个返回值不同;它们各自的父进程也不同,父进程的父进程是ID不变的;还有子进程不继承父进程设置的文件锁,子进程未处理的信号集会设置为空集等不同

 

 

fork()函数在底层中做了什么?


 
   linux平台通过clone()系统调用实现fork(). fork(),vfork()和clone()库函数都根据各自需要的参数标志去调用clone(),然后由clone()去调用
do_fork(). 再然后do_fork()完成了创建中的大部分工作,他定义在kernel/fork.c当中.该函数调用copy_process(). 然后重点来了,我们看看这个
copy_process函数到底做了那些事情?? 我画一张图帮我们理解:
 
 

 

 

vfork和fork的之间的比较:


vfork()的诞生是在fork()还没有写时拷贝的时候,因为那个时候创建一个子进程的成本太大了,如果一下子创建好多了那么程序的效率一定会下降. 然后就有人提出了vfork(). vfork的实现原理非常简单,就是子进程,父进程完全公用一个资源. 就是是有人修改了内容,甚至main()函数退出了也不会新开辟一个空间. 所以这里里会有问题的,如果你的一个子进程没有使用exit()退出,那么程序就会出现段错误. 不相信可以去试一试~ 
 
为什么会出现段错误? 
 
  在函数栈上面,子进程运行结束了,main的函数栈被子进程释放了,然后父进程在使用的时候,就访问不到了,一旦vfork出子进程,退出的时候需要使用exit来结束.
 
 

vfork和fork之间的区别:

 
1.fork父子进程交替运行,vfork保证子进程先运行,父进程阻塞,直到子进程结束(或子进程调用了exec或exit).
 
2.fork实现了写时拷贝. 而vfork直接让父子进程共用公用资源,避免多开辟空间拷贝,
 
3,vfork必须使用exit或者excl退出.
 
4.就算是fork使用了写时拷贝,也没有vfork性能高.
 
5.每个系统上的vfork都有问题,推荐不要使用.
 
http://www.vrc3443.top/
http://www.xzg8926.top/
http://www.hrd7175.top/
http://www.njn2935.top/
http://www.gmn7922.top/
http://www.gdi2417.top/
http://www.dpz7007.top/
http://www.slg0631.top/
http://www.pzq0064.top/
http://www.jqg9208.top/
http://www.smp7329.top/
http://www.sqd7023.top/
http://www.bmh5849.top/
http://www.lwg3929.top/
http://www.wgt9662.top/
http://www.bux1348.top/
http://www.ukr4854.top/
http://www.cfs8763.top/
http://www.psd1092.top/
http://www.xck1603.top/
http://www.fgm4024.top/
http://www.zoj1707.top/
http://www.oiv1998.top/
http://www.ftw8814.top/
http://www.jfs6888.top/
http://www.kdx4817.top/
http://www.sbx6519.top/
http://www.rrq5611.top/
http://www.pxk9336.top/
http://www.vik6796.top/
http://www.kod8371.top/
http://www.nuq3623.top/
http://www.vfv3740.top/
http://www.tbt7039.top/
http://www.wky3695.top/
http://www.kcs3342.top/
http://www.gum4900.top/
http://www.mrw5927.top/
http://www.wnu1861.top/
http://www.vlc4617.top/
http://www.idv6045.top/
http://www.jmk5203.top/
http://www.mug5965.top/
http://www.gtt6107.top/
http://www.cnp6436.top/
http://www.sdx1013.top/
http://www.jwd3113.top/
http://www.qeu2095.top/
http://www.tux4376.top/
http://www.tay3928.top/
http://www.tgq6935.top/
http://www.win4778.top/
http://www.ngh4321.top/
http://www.cqq1459.top/
http://www.fxm1291.top/
http://www.wyz5825.top/

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326751086&siteId=291194637