浅析fork()和底层实现

记得以前初次接触fork()函数的时候,一直被“printf”输出多少次的问题弄得比较晕乎。不过,“黄天不负留心人"。哈~ 终于在学习进程和进程创建fork相关知识后,总算是大致摸清了其中的来龙去脉。废话不多讲,下面来谈谈本人的一点小小积累

一个现有的进程可以调用fork函数创建一个新进程。原型如下:

#include<unistd.h> 

pid_t fork(void); 

返回值:自进程中返回0,父进程返回进程id,出错返回-1

fork()系统调用会通过复制一个现有进程来创建一个全新的进程. 进程被存放在一个叫做任务队列的双向循环链表当中.链表当中的每一项都是类型为task_struct成为进程描述符的结构.也就是我们写过的进程PCB.

小知识:内核通过一个位置的进程标识值或PID来标识每一个进程.同时其最大值默认为32768,short int短整型的最大值. 他就是系统中允许同时存在的进程最大的数目.可以去linux下的proc目录中寻找一个 pid_max的文件,并打开它加以验证. 如

浅析fork()和底层实现

fork()运行时做的事情

--------------------------------------------------------------------------------

首先我们来看一段代码,不过这里会有一点奇怪的现象:

/*************************************************************************
  > File Name: 1.c
  > Author: tp
  > Mail:
  > Created Time: Mon 07 May 2018 07:57:28 PM CST
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main( void)
{
    printf("change world!\n");
    pid_t pid = fork();
    if( pid == -1) {perror("fork"),exit(1); }

printf( "pid=%d, returnVal=%d\n", getpid(), pid);
    sleep( 1);
    exit(0);
}
~

这段代码的运行结果,大家如果像我当时不了解fork的时候,一定会以为输出结果是两个"change world!",然后2个printf里面的内容. 因为

我们复制出来了两个一模一样的进程,那么他们就应该做同样的事情. But!!! 我们看运行结果:

浅析fork()和底层实现

结果并非我们想的那样,这个时候我们就需要知道fork出子进程之后,程序的运行细节。可以来一张图帮助我们理解:

浅析fork()和底层实现

一般来说,在fork之后是父进程先执行还是子进程先执行是不确定的.这取决于内核所使用的调度算法.如果要求父,子进程之间相互同步.则要求某种形式的进程间通信. 好了我们继续,当进程调用fork后,当控制转移到内核中的fork代码后,内核会做4件事情:

  1.分配新的内存块和内核数据结构给子进程

  2.将父进程部分数据结构内容(数据空间,堆栈等)拷贝至子进程

  3.添加子进程到系统进程列表当中

  4.fork返回,开始调度器调度

为什么fork成功调用后返回两个值?

由于在复制时复制了父进程的堆栈段,所以两个进程都停留在fork函数中,等待返回。所以fork函数会返回两次,一次是在父进程中返回,另一次是在子进程中返回,这两次的返回值不同,

其中父进程返回子进程pid,这是由于一个进程可以有多个子进程,但是却没有一个函数可以让一个进程来获得这些子进程id,那谈何给别人你创建出来的进程。而子进程返回0,这是由于子进程可以调用getppid获得其父进程进程ID,但这个父进程ID却不可能为0,因为进程ID0总是有内核交换进程所用,故返回0就可代表正常返回了。

从fork函数开始以后的代码父子共享,既父进程要执行这段代码,子进程也要执行这段代码.(子进程获得父进程数据空间,堆和栈的副本. 但是父子进程并不共享这些存储空间部分. (即父,子进程共享代码段.)。现在很多实现并不执行一个父进程数据段,堆和栈的完全复制. 而是采用写时拷贝技术(不懂可以戳进去看一看).这些区域有父子进程共享,而且内核地他们的访问权限改为只读的.如果父子进程中任一个试图修改这些区域,则内核值为修改区域的那块内存制作一个副本, 也就是如果你不修改我们一起用,你修改了之后对于修改的那部分内容我们分开各用个的.

父子进程文件共享问题

--------------------------------------------------------------------------------

来看个例子

/*************************************************************************
  > File Name: 2.c
  > Author: tp
  > Mail:
  > Created Time: Mon 07 May 2018 12:40:39 PM CST
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int set = 110;
int main( void)
{
    printf( "before fork\n");
    pid_t pid = fork( );
    if( pid < 0){ perror(" fork"),exit( 1);}

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/9fe16721398026d0ff3ba7ce6aa62d24.html