进程互斥和fork

自父进程继承

  • 进程的资格(真实(real)/有效(effective)/已保存(saved) 用户号(UIDs)和组号(GIDs))
  • 环境(environment)
  • 堆栈
  • 内存
  • 打开文件的描述符(注意对应的文件的位置也是和文件一起由父子进程共享的)
  • 执行时关闭(close-on-exec) 标志 (译者注:close-on-exec标志可通过fnctl()对文件描 述符设置,POSIX.1要求所有目录流都必须在exec函数调用时关闭。更详细说明, 参见《UNIX环境高级编程》 W. R. Stevens, 1993, 尤晋元等译(以下简称《高级编程》), 3.13节和8.9节)
  • 信号(signal)控制设定
  • 信号量(semaphore)
  • nice值 (译者注:nice值由nice函数设定,该值表示进程的优先级, 数值越小,优先级越高)
  • 进程调度类别(scheduler class) (译者注:进程调度类别指进程在系统中被调度时所属的类别,不同类别有不同优先级,根据进程调度类别和nice值,进程调度程序可计算出每个进程的全局优先级(Global process prority),优先级高的进程优先执行)
  • 进程组号
  • 对话期ID(Session ID) (《高级编程》:进程所属的对话期 (session)ID, 一个对话期包括一个或多个进程组, 更详细说明参见《高级编程》 9.5节)
  • 当前工作目录
  • 根目录 (根目录不一定是操作系统的“/”,它可由chroot函数改变)
  • 文件方式创建屏蔽字(file mode creation mask (umask)) (《高级编程》:创建新文件的缺省屏蔽字)
  • 资源限制
  • 控制终端
  • 所有互斥锁、读写锁和条件变量(同事考虑到多线程环境)

子进程所独有

  • 进程号
  • 不同的父进程号(译者注: 即子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到)
  • 自己的文件描述符和目录流的拷贝(译者注: 目录流由opendir函数创建,因其为顺序读取,顾称“目录流”)
  • 子进程不继承父进程的进程,正文(text), 数据和其它锁定内存(memory locks) (译者注:锁定内存指被锁定的虚拟内存页,锁定后, 不允许内核将其在必要时换出(page out), 详细说明参见《The GNU C Library Reference Manual》 2.2版, 1999, 3.4.2节)
  • 在tms结构中的系统时间(译者注:tms结构可由times函数获得, 它保存四个数据用于记录进程使用中央处理器 (CPU:Central Processing Unit)的时间,包括:用户时间,系统时间, 用户各子进程合计时间,系统各子进程合计时间)
  • 资源使用(resource utilizations)设定为0
  • 阻塞信号集初始化为空集(译者注:原文此处不明确, 译文根据fork函数手册页稍做修改)
  • 不继承由timer_create函数创建的计时器
  • 不继承异步输入和输出
  • 文件锁不继承,具体原因见参考文献。

随想杂谈

  • 假设fork是在父进程有多个线程时发生的,要考虑如下问题:

  子进程通过继承整个地址空间的副本,从而父进程哪里继承了所有互斥量,读写锁和条件状态。如果父进程包含多个线程,子进程在 fork 返回以后,如果紧接着不是马上调用 exec 的话,就需要清理锁状态。 在子进程内部只存在一个线程,它是由父进程调用 fork 返回以后,如果父进程中的线程占有锁,子进程同样占有这些锁,问题就出在子进程同样占有这些锁——但子进程不包含这些占有锁的线程副本,所以子进程没有办法知道它占有的那些锁并且需要释放哪些锁。如果子进程从 fork 返回以后马上调用某个 exec 函数,就可以避免这样的问题。这种情况下,老的地址空间被丢弃,所以锁的状态无关紧要。

  当然,不用exec,也可以使用pthread_atfork,在调用fork之前,线程先获取进程中所有锁,在调用fork后分别在父子进程中释放这些锁,从而父子可以重新获取和释放这些锁资源。

  • pthread接口也提供了进程范围的共享互斥量,这是需要设置互斥类型为PTHREAD_PROCESS_SHARED,因为是进程间共享,要求mutex init时候,使用共享的内存区域中分配的互斥量。进程间互斥,还推荐阅读《nginx模块开发与结构解析》最后一章关于互斥锁的实现方案,一种是基于文件锁,一种是基于原子(本质和互斥同质)操作&信号量。建议集合accept_mutex锁的实现了解不同互斥锁的原理!
  • 记录锁的继承和释放
    1、锁与进程和文件两方面
    a、当一个进程终止时,它所建立的锁全部释放;(即 进程退出,文件锁自动释放)
    b、任何时候关闭一个描述符时,则该进程通过这一描述符可以引用的文件上的任何一把锁都释放。(即 关闭文件,文件锁自动释放)

    情况一::
    fd1 = open(pathname,....);
    read_lock(fd1,....);
    fd2 = dup(fd1);
    close(fd2); //此时,在close(fd2)后,在fd1上加的锁被释放。
    情况二::
    fd1 = open(pathname,....);
    read_lock(fd1,....);
    fd2 = open(pathname,...);
    close(fd2); //此时,在close(fd2)后,在fd1上加的锁被释放。

    2、由fork产生的子进程不继承父进程所设置的锁。(文件锁不能被继承)
    这意味着,若一个进程得到一把锁,然后调用fork,那么对于父进程获得的锁而言,子进程被视为另外一个进程,对于从父进程处继承过来的任一描述符,子进程需要调用fcntl才能获得它自己的锁。
    (这其实也是锁的本意,文件锁的作用是阻止多个进程同时写一个文件或区域,如果子进程继承父进程的锁,则父、子就可以同时写同一个文件。这显然是不对的。)

    3、在执行exec后,新程序可以继承原执行程序的锁。(EXEC文件锁被继承)
    执行exec后,其实是用当前进程的进程实体替换原进程的进程实体。原进程被杀掉(但保留了 进程ID即 PID)

原文地址:https://www.cnblogs.com/zhaoyl/p/4056771.html