linux fork函数详解

linux fork函数详解

函数原型: pid_t fork(void)

  1. 参数:不需要参数
  2. 需要的头文件<sys/types.h><unistd.h>
  3. 返回值分两种情况:
    • 返回0表示成功创建子进程,并且接下来进入子进程执行流程
    • 返回PID(>0),成功创建子进程,并且继续执行父进程流程代码
    • 返回非正数(<0),创建子进程失败,失败原因主要有:
      • 进程数超过系统所能创建的上限,errno会被设置为EAGAIN
      • 系统内存不足,errno会被设置为ENOMEM

地址空间

使用 fork() 函数得到的子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间:包括进程上下文(进程执行活动全过程的静态描述)、进程堆栈、打开的文件描述符、信号控制设定、进程优先级、进程组号等。子进程所独有的只有它的进程号,计时器等(只有小量信息)。因此,使用 fork() 函数的代价是很大的。

共享方式

实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。

执行顺序

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

linux有个类似的函数vfork():函数表面看起来都一样,但是它保证子进程先运行,在它调用 exec(进程替换) 或 exit(退出进程)之后父进程才可能被调度运行。子进程共享父进程的地址空间(准确来说,在调用 exec(进程替换) 或 exit(退出进程) 之前与父进程数据是共享的), vfork() 创建的子进程会执行完后,才到父进程执行。

区别

子进程与父进程的区别在于:

  1. 除了文件锁以外,其他的锁都会被继承
  2. 各自的进程ID和父进程ID不同
  3. 子进程的未决告警被清除;
  4. 子进程的未决信号集设置为空集。

孤儿进程、僵尸进程

fork系统调用之后,父子进程将交替执行,执行顺序不定。如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程(托孤给了init进程)。(注:任何一个进程都必须有父进程)如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程(僵尸进程:只保留一些退出信息供父进程查询)

多线程进程的Fork调用

坑大,面试可能会问道,工作中也要小心使用
云风 BLOG: 极不和谐的 fork 多线程程序
讲的主要是当前的进程processA (thread a/b/c)的当前子线程thread a调用fork后,会创建子进程,但是只是复制了thread a,总结一句就是所有父进程中别的线程,到了子进程中都是突然蒸发掉的。这样会导致各种死锁问题,以及各种数据不一致等问题。最好的办法是在多线程进程里不是用fork。如果非使用不可,尽量fork完毕后直接exec,不调用任何其他除了fork之外的函数。exec可以覆盖内存空间,可以解决所有关于锁的问题。
还有一些文章可以看看:

  1. 谨防fork与锁之间的深坑 – CSDN博客
  2. 子进程继承父进程中互斥锁的讨论 – CSDN博客
  3. 在多线程中使用fork函数导致死锁,以及解决方案 – CSDN博客

发表评论

电子邮件地址不会被公开。 必填项已用*标注