Linux 中的fork()函数

2016-10-19 Bin

0x00 开始

程序启动,便会建立一个相应的进程。进程包括代码,数据,堆,栈…其实就是这个程序本身。

程序一般都是顺序执行的,也就是不能同时做两件事。但是有些时候需要做点额外的事,比如一个服务器程序:和客户端建立连接,然后通信,这时就不能和别的客户端连接了…

所以,世界需要额外的事,于是子进程运应而生(雾)

fork()函数做的就是这件事,不是多线程(多线程也是程序在做一样的事),而是复制一个进程,相当于分身。然后这个分身去做额外的事,比如那个服务器程序:建立连接->fork一个自己->分身去通讯->自己接着等待另一个客户端来建立连接。

这次主要是从一个博客Link过来的。恩…

0x01 fork()

talking is weak,先看函数原型:

pid_t fork( void);
  • pid_t 是一个宏定义,其实质是int 被定义在#include<sys/types.h>
  • 返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1

很简单的函数。如前面所说,一个进程调用fork()后,系统先进行资源分配,然后把父进程(调用fork的进程)的内容全部复制进子进程中,包括代码。所以就是个镜像实体。

从别处copy一个栗子:

#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()); 
        count++;
    }
    else {
        printf("i am the parent process, my process id is %d/n",getpid()); 
        count++;
    }
    printf("Result: %d/n",count);
    return 0;
}

然后结果是:

i am the child process, my process id is 5574
Result: 1
i am the parent process, my process id is 5573
Result: 1

乍一看好像理所当然,复制了一个进程嘛。但是仔细看却是interesting:它是怎么控制输出不同的信息的?

再看前面的函数原型,其实返回值这一条很有趣:“若调用成功一次会返回两个值”。没错,fork()最大的特点就在这里了。因为fork()之后会产生两个一样的进程,所以会分别对父进程和子进程返回一个值。

返回值:

  • 子进程:返回0(永远是返回0)
  • 父进程:返回子进程的PID(PID即进程ID)
  • REEOR:返回值小于0

在运行到fork()之前,只有一个进程在执行。fork()之后,产生子进程并返回了PID。如果程序之后也是普通的代码(不去根据PID判断什么),那么就是两个一样的程序在跑。在例子中,进行了PID判断,也就是判断了父子进程,判断之后,父子进程进入不同的条件分支,相当于两个进程分道扬镳,从此不同了。

所以喽,最后输出了不一样的信息。

PS:getpid()函数用于获取自己进程的PID。

0x02 其他

ERROR?

之前写了,如果发生错误,会返回小于零的值。代码中检查if(fpid<0)的原因,就是进行错误检查。

以下是ERROR的情况:

  • 当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN。
  • 系统内存不足,这时errno的值被设置为ENOMEM。

父进程,子进程?

这里说一下,父子进程是相对的:每次调用fork(),调用者为父进程,新建立的进程为子进程。

也就是说,如果fork一个子进程后(产生了子进程1),该子进程也调用fork(),那么相对于新产生的子进程2,它是父进程(子进程1是子进程2的父进程。原进程是子进程1的父进程。就是孩子,父亲和爷爷)。

这两个函数用于获取PID:

  • getpid():获取自己进程的PID
  • getppid():获取自己的父进程的ID

优先性?

fork()后,那个进程先进行呢?

随机的。可以理解为运行了两个程序,看CPU怎么调度喽

文件问题

特别的是,fork后父子进程会共享一个文件表。也就是说,父进程打开的文件子进程也是可以读取的…

0x03 Copy-on-write:写时复制

fork是Linux下的关键和常用函数之一,所以针对它也进行了优化。其实这和exec有关。

(实际上是没关的…提到exec只是举个栗子而已)

EXEC

简单来说,exec()做的事就是打开另一个新程序,然后把新程序的所有内容(代码,数据,栈,堆…)都覆盖到自己的进程中,但是PID不变。本质上说就是灭了自己,然后执行了新程序,不过PID等外表信息不变。

PS:exec实际上是6个,exec只是统称,详细内容另行查看

Linux内一个没有用的进程,经常就以exec方式销毁并启动另一个有用的新程序。另一个常用方法便是:fork一个子进程,然后子进程exec。相当于一个进程去启动了一个新程序(也是做点额外的事233,比如还是服务器程序,连接后直接fork和exec启动新程序,把连接好的客户端交给新程序,自己继续等待连接)。

fork会在创建子程序的时候完全复制原进程的信息。这样的话,如果fork后直接exec了,岂不是很浪费?(还是经常很浪费)

所以,世界也需要节约性能,于是COW(copy-on-write)运应而生(雾+1)

copy-on-write(写时复制)

按字面意思,就是”写”的时候才进行复制(废话)。其实就是这样。

当fork之后,其实并没有发生复制,只是系统给子进程分配了虚拟地址空间,代码,数据,栈,堆…等都指向原进程,并且原进程的数据全部被设置为只读。一旦父进程进行数据修改,此时系统才会给子进程分配相应的空间,进行修改部分数据的复制。

这样的话就可以节省资源了。fork后没有复制,然后exec,再然后…子进程完全变了样,和父进程毫无关联,永远不需要复制信息了。

都说了是主要是个Link…

其中还记录了多次循环fork的烧脑分析以及公式:这是地址

–End–