fork系统调用,孤儿进程

Last Updated: 2023-11-05 08:21:23 Sunday

-- TOC --

Linux下著名的fork系统调用,它用来创建子进程,它没有入参!

// fork - create a child process
// man 2 fork
#include <unistd.h>
pid_t fork(void);

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

  1. 在父进程中,fork返回新创建子进程的PID;
  2. 在子进程中,fork返回0;
  3. 如果出现错误,fork在父进程中返回-1,此时没有子进程被创建;

fork创建子进程,是通过duplicating the calling process的方式,在fork的时刻,父进程和子进程的memory space内容相同,但父子进程确又是不同的两个进程。fork采用copy-on-write pages的方式,memory只有在write的时候,才会copy到不同的地址,以便将两个进程区别开来。fork创建了一个与自身完全一样的进程,fork执行成功后,两个拥有相同代码的进程在fork调用之后开始分叉。

下面是一段测试代码:

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


#define _PEXIT \
    do {\
        char ebuf[64] = {0};\
        sprintf(ebuf, "%s: %d", __FILE__, __LINE__);\
        perror(ebuf);\
        exit(errno);\
    }while(0)


int main(void) {
    pid_t c1, c2;

    printf("pid: %u\n",  getpid());

    if ((c1=fork()) == 0) { // child
        printf("in child 1, ppid: %u, pid: %u\n", getppid(), getpid());
        return 0;
    }
    else if (c1 == -1)
        _PEXIT;

    printf("child 1 pid: %u\n", c1);
    sleep(1);  // sync with child 1

    if ((c2=fork()) == 0) {
        sleep(2);
        printf("in child 2, ppid: %u, pid: %u\n", getppid(), getpid());
        return 0;
    }
    else if (c2 == -1)
        _PEXIT;

    printf("child 2 pid: %u\n", c2);
    return 0;  // early stop
}

这段测试代码故意让父进程提前return,然后child 2再执行结束。测试得到如下输出:

$ gcc -Wall -Wextra test_fork.c -o test_fork
$ ./test_fork
pid: 281824
child 1 pid: 281825
in child 1, ppid: 281824, pid: 281825
child 2 pid: 281826
in child 2, ppid: 1, pid: 281826

父进程结束之后,child 2的ppid就成了1。

类似前面这种情况,父进程先于子进程结束,子进程的PPID编号变为1(被1号进程领养了),此时的子进程就是所谓的孤儿进程!(没父亲了,为什么不说没妈了...)

fork出来的子进程会继承父进程所有的open file,除此之外,还是有很多细节上的差异,具体请参考manpage。常见fork与exec系列接口配合,来实现子进程执行不同于父进程的程序。Python标准os模块有封装fork接口,下面是一个Python版的测试代码,跟上面的测试代码功能完全相同:

import os
import sys
from time import sleep


print('pid:', os.getpid())

try:
    pid = os.fork()
    if pid == 0:
        print('in child 1, ppid: %d, pid: %d' % (os.getppid(), os.getpid()))
        sys.exit(0)
except OSError as e:
    print(os.strerror(e.errno))
    sys.exit(e.errno)

print('child 1 pid:', pid)
sleep(1)

try:
    pid = os.fork()
    if pid == 0:
        sleep(2)
        print('in child 2, ppid: %d, pid: %d' % (os.getppid(), os.getpid()))
        sys.exit(0)
except OSError as e:
    print(os.strerror(e.errno))
    sys.exit(e.errno)

print('child 2 pid:', pid)
sys.exit(0)

OSError异常后,异常对象的errno值可取,这个值与OS Kernel返回的错误码是对应的。

本文链接:https://cs.pynote.net/sf/linux/prog/202202041/

-- EOF --

-- MORE --