线程和fork
对于fork相信大家都已经熟悉的不能再熟悉了! 我们最近学习到了线程,我们都知道了新创建的线程可以访问进程的地址空间,并且
继承调用线程
的
浮点环境和信号屏蔽字.线程中都可以访问到进程的分别有: 1.文件描述符表 2.每种信号的处理方式 3.当前工作目
录 4.用户id和组id
但是线程的有些资源是每个线程独有的: 1.线程id 2.上下文,包括各种寄存器的值,程序计数器和栈指针.
3.栈空间 4.errno变量 5.信号屏蔽字
6.调度优先级 那么我们来思考一下! 在一个线程中调用fork,会发生什么情况呢 ?
当线程调用fork的时候,就是为了子进程创建了整个进程地址空间的副本,子进程与父进程是两个完全不同的进程,只要两者都没有
对内存做出改
动,父进程和子进程之间可以共享内存页的副本.
子进程通过继承整个地址空间的副本,还从父进程哪里继承了每一个互斥量,读写锁和条件变量的状态. 如果父进程包含一个以上的
线程,子进程
在
fork返回以后,如果紧接着不是马上调用exec的话,就需要清理锁的状态
,
在子进程的内部,只存在一个线程,它是由父进程中调用fork的线程的副本构成的,如果父进程中的线程占有锁,子进程将同样占有
这些锁. 问题
是
子进程并不包含占有锁的线程的副本,所以子进程没有办法知道它占有了那些锁,需要释放那些锁.
如果子进程从fork返回以后马上调用一个exec函数就可以避免这样的问题! 这种情况下,旧的地址空间就会被丢弃,所以锁的状态无
关紧要,但如
果子进程进行做处理工作的时候,这种策略就行不用了. 这个时候系统提供了一个pthread_atfork函数送给你.
#include <pthread.h>
int pthread_atfork(void (*prepare)(void), void (*parent)(void),void (*child)(void));
用pthread_atfork函数最多可以安装三个帮助清理锁的函数. prepare fork处理程序有父进程在fork创建子进程之前调用,这个fork
处理程序的任
务是获取父进程定义的所有锁
parent fork
处理程序是在fork创建子进程之后,返回之前在父进程上下文之中调用的.
这个fork处理程序的任务
是对prepare fork处理程序获取的所有锁进行解锁. child fork处理程序在fork函数之前在子进程上下文
之中调用. 与parent fork处理程序一样
,child fork处理程序也必须释放prepare fork处理程序获取的所有锁.
注意,不会出现加锁一次解锁两次的情况,虽然看起来也许会出现. 子进程地址空间在创建时就得到了父进程的所有锁的副本. 因为
prepare fork
处理程序获取了所有的锁,父进程中的内存和子进程中的内存内容在开始的时候是相同的. 当父进程和子进程对他们锁
的副本进程解锁的时候,新
的内存是分配给子进程的,父进程的内存内容是赋值到子进程的内存中(写时拷贝),所以我们就会陷入这
种假象,看起来父进程对它的所有锁的副
本进行加锁,子进程对它的所有锁的副本进行了加锁,父进程和子进程对不同单元的重复的
锁都进行了解锁操作,就好像出现了下列的事件:
注:fork后的父子进程锁的状态
(1)父进程获取所有的锁
(2)子进程获取所有锁
(3)父进程释放它的锁
(4)子进程释放它的锁
从而可以多次调用pthread_atfork函数从而设置多套fork处理程序. 如果不需要使用其中某个处理程序,可以给特定的处理程序参数
传入空指针,
它就不会起任何作用.
使用多个fork处理程序时,处理程序的调用顺序并不相同. parent和child fork处理程序是以
它们注册时的顺序进行调用
的
,而prepare fork处理程序的调用顺序与它们注册时的顺序相反.
这样就可以允许多个模块注册它们
自己的fork处理程序,并且可以保持锁
的层次.
加入模块A调用模块B当中的函数,并且每个模块有自己的一套锁. 如果锁的层次是A在B之前,模块B必须在模块A之前设置它的fork处
理程序. 当父
进程调用fork时,就会执行下列步骤:
(1)调用模块A的prepare fork处理程序获取模块A的所有锁
(2)调用模块B的prepare fork处理程序获取模块B的所有锁
(3)创建子进程
(4)调用模块B中的child fork处理程序获取子进程中的模块B的所有锁
(5)调用模块A中的child fork处理程序释放子进程当中模块A的所有锁
(6)fork函数返回到子进程.
(7)调用模块B中的parent fork处理程序释放父进程中的模块B的所有锁
(8)调用模块A中的parent fork处理程序来释放父进程中模块A的所有锁.
(9
)fork函数返回到父进程.
如果fork处理程序是用来清理锁的状态的,那么又由谁来负责清理条件变量的状态呢? 在有些操作系统的实现中,条件变量可能并
不需要做任何
清理. 但是有些操作系统实现把锁作为条件变量实现的一部分,这种情况下的条件变量就需要清理. 问题是目前不存
在允许清理锁状态的接口.
如果所示嵌入到条件变量的数据结构中的,那么在调用fork之后就不能够使用条件变量了,因为还没有可移
植的方法对锁进行状态清理. 另外,
如果操作系统的实现是使用全局锁保护进程中所有的条件变量数据结构,那么操作系统实现本身
可以在fork库例程中做清理锁的工作,但是应用
程序不应该依赖操作系统实现中类似这样的细节.