深入理解Linux fork函数0212.docx
- 文档编号:2800735
- 上传时间:2022-11-15
- 格式:DOCX
- 页数:8
- 大小:22.81KB
深入理解Linux fork函数0212.docx
《深入理解Linux fork函数0212.docx》由会员分享,可在线阅读,更多相关《深入理解Linux fork函数0212.docx(8页珍藏版)》请在冰豆网上搜索。
深入理解Linuxfork函数0212
深入理解Linuxfork函数调用
2014/08/08
2015/02/12
1、线程,进程,轻量级进程
进程必须的4个要点
a)要有一段程序供该进程运行,就像一场戏剧要有一个剧本一样。
该程序是可以被多个进程共享的,多场戏剧用一个剧本一样
b)有起码的私有财产,就是进程专用的系统堆栈空间
c)有“户口”,既操作系统所说的进程控制块,在linux中具体实现是task_struct
d)有独立的存储空间
⏹当一个进程缺少d条件时候,我们称其为线程。
⏹三者都是linux的系统调用,用来创建子进程的
⏹确切说vfork创造出来的是线程
--------------------------------------------------------------
进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在Linux中被抽象成各种数据对象:
进程控制块、虚存空间、文件系统,文件I/O、信号处理函数。
所以创建一个进程的过程就是这些数据对象的创建过程。
在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性,但是二者之间的通讯需要通过专门的通讯机制,如:
pipe,fifo,SystemV,IPC机制等,另外通过fork创建子进程系统开销很大,需要将上面描述的每种资源都复制一个副本。
这样看来,fork是一个开销十分大的系统调用,这些开销并不是所有的情况下都是必须的,比如某进程fork出一个子进程后,其子进程仅仅是为了调用exec执行另一个执行文件,那么在fork过程中对于虚拟存储空间的复制将是一个多余的过程(由于Linux中采取了copy-on-write技术,所以这一步骤的所做的工作只是虚拟内存管理部分的复制以及页表的创建,而并没有包括物理页面的拷贝)
另外,有时一个进程中具有几个独立的计算单元,可以在相同的地址空间上基本无冲突进行运算,但是为了把这些计算单元分配到不同的处理器上,需要创建几个子进程,然后各个子进程分别计算最后通过一定的进程间通讯和同步机制把计算结果汇总,这样做往往有许多格外的开销,而且这种开销有时足以抵消并行计算带来的好处。
这说明了把计算单元抽象到进程上是不充分的,这也就是许多系统中都引入了线程的概念的原因。
在讲述线程前首先介绍以下vfork系统调用,vfork系统调用不同于fork,用vfork创建的子进程共享地址空间,也就是说子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改同样为父进程所见。
但是用vfork创建子进程后,父进程会被阻塞直到子进程调用exec或exit。
这样的好处是在子进程被创建后仅仅是为了调用exec执行另一个程序时,因为它就不会对父进程的地址空间有任何引用,所以对地址空间的复制是多余的,通过vfork可以减少不必要的开销。
在Linux中,fork和vfork都是调用同一个核心函数
do_fork(unsignedlongclone_flag,unsignedlongusp,structpt_regs)
其中clone_flag包括CLONE_VM,CLONE_FS,CLONE_FILES,CLONE_SIGHAND,CLONE_PID,CLONE_VFORK等等标志位,任何一位被置1了则表明创建的子进程和父进程共享该位对应的资源。
所以在vfork的实现中,cloneflags=CLONE_VFORK|CLONE_VM|SIGCHLD,这表示子进程和父进程共享地址空间,同时do_fork会检查CLONE_VFORK,如果该位被置1了,子进程会把父进程的地址空间锁住,直到子进程退出或执行exec时才释放锁。
在讲述clone系统调用前先简单介绍线程的一些概念
线程是在进程的基础上进一步的抽象,也就是说一个进程分为两个部分:
线程集合和资源集合。
线程是进程中的一个动态对象,它应该是一组独立的指令流,进程中的所有线程将共享进程里的资源。
但是线程应该有自己的私有对象:
比如程序计数器、堆栈和寄存器上下文。
线程分为三种类型:
内核线程、轻量级进程和用户线程。
内核线程:
它的创建和撤消是由内核的内部需求来决定的,用来负责执行一个指定的函数,一个内核线程不需要和一个用户进程联系起来。
它共享内核的正文段核全局数据,具有自己的内核堆栈。
它能够单独的被调度并且使用标准的内核同步机制,可以被单独的分配到一个处理器上运行。
内核线程的调度由于不需要经过内核态与用户态的转换并进行地址空间的重新映射,因此在内核线程间做上下文切换比在进程间做上下文切换快得多。
轻量级进程:
轻量级进程是核心支持的用户线程,它在一个单独的进程中提供多线程控制。
这些轻量级进程被单独的调度,可以在多个处理器上运行,每一个轻量级进程都被绑定在一个内核线程上,而且在它的生命周期这种绑定都是有效的。
轻量级进程被独立调度并且共享地址空间和进程中的其它资源,但是每个LWP都应该有自己的程序计数器、寄存器集合、核心栈和用户栈。
用户线程:
用户线程是通过线程库实现的。
它们可以在没有内核参与下创建、释放和管理。
线程库提供了同步和调度的方法。
这样进程可以使用大量的线程而不消耗内核资源,而且省去大量的系统开销。
用户线程的实现是可能的,因为用户线程的上下文可以在没有内核干预的情况下保存和恢复。
每个用户线程都可以有自己的用户堆栈,一块用来保存用户级寄存器上下文以及如信号屏蔽等状态信息的内存区。
库通过保存当前线程的堆栈和寄存器内容载入新调度线程的那些内容来实现用户线程之间的调度和上下文切换。
内核仍然负责进程的切换,因为只有内核具有修改内存管理寄存器的权力。
用户线程不是真正的调度实体,内核对它们一无所知,而只是调度用户线程下的进程或者轻量级进程,这些进程再通过线程库函数来调度它们的线程。
当一个进程被抢占时,它的所有用户线程都被抢占,当一个用户线程被阻塞
时,它会阻塞下面的轻量级进程,如果进程只有一个轻量级进程,则它的所有用户线程都会被阻塞。
明确了这些概念后,来讲述Linux的线程和clone系统调用。
在许多实现了MT的操作系统中(如:
Solaris,DigitalUnix等),线程和进程通过两种数据结构来抽象表示:
进程表项和线程表项,一个进程表项可以指向若干个线程表项,调度器在进程的时间片内再调度线程。
但是在Linux中没有做这种区分, 而是统一使用task_struct来管理所有进程/线程,只是线程与线程之间的资源是共享的,这些资源可是是前面提到过的:
虚存、文件系统、文件I/O以及信号处理函数甚至PID中的几种。
也就是说Linux中,每个线程都有一个task_struct,所以线程和进程可以使用同一调度器调度。
其实Linux核心中,轻量级进程和进程没有质上的差别,因为Linux中进程的概念已经被抽象成了计算状态加资源的集合,这些资源在进程间可以共享。
如果一个task独占所有的资源,则是一个HWP(重量级线程),如果一个task和其它task共享部分资源,则是LWP(轻量级线程)。
clone系统调用就是一个创建轻量级进程的系统调用
intclone(int(*fn)(void*arg),void*stack,intflags,void*arg);
其中fn是轻量级进程所执行的过程,stack是轻量级进程所使用的堆栈,flags可以是前面提到的CLONE_VM,CLONE_FS,CLONE_FILES,CLONE_SIGHAND,CLONE_PID的组合。
clone和fork,vfork在实现时都是调用核心函数do_fork。
do_fork(unsignedlongclone_flag,unsignedlongusp,structpt_regs);
●fork时clone_flag=SIGCHLD;
●vfork时clone_flag=CLONE_VM|CLONE_VFORK|SIGCHLD;
●clone中,clone_flag由用户给出。
看起来clone的用法和pthread_create有些相似,两者的最根本的差别在于clone是创建一个LWP,对核心是可见的,由核心调度,而pthread_create通常只是创建一个用户线程,对核心是不可见的,由线程库调度。
linux的pthread_create最终调用clone,pthread_create调用clone,并把开辟一个stack作为参数thread建立,同步,销毁等由线程库负责。
2、子进程可继承的东西
⏹实际用户ID,实际组ID,有效用户ID,有效组ID
⏹附加组ID
⏹进程组ID
⏹会话ID
⏹控制终端
⏹设置用户ID标志,设置组ID标志
⏹当前工作目录
⏹根目录
⏹文件模式创建屏蔽字
⏹信号屏蔽和信号处理函数
⏹所有打开的文件描述符
⏹执行时关闭标志closeonexec
⏹环境(数据空间,堆,栈,内存)
⏹连接的共享存储段
⏹存储限制
⏹资源限制
⏹线程锁也会被继承,不过两个进程的锁就独立互不相干了
3、子进程不可继承的东西
⏹父进程设置的文件锁
⏹子进程未处理的闹钟alarm将被清除
⏹子进程的未处理信号集合设置为空集
⏹子进程和父进程共享正文段
⏹不继承锁定的内存
●#include h> ●intmlock(constvoid*addr,size_tlength) ●intmunlock(void*addr,size_tlength) ●intmlockall(intflag) ●intmunlockall(void) ⏹不继承异步输入和输出 ⏹nice值 4、fork基本概念 一般的,fork做如下事情 ●父进程的内存数据会原封不动的拷贝到子进程中 ●子进程在单线程状态下被生成 ●子进程只拷贝了当前调用线程,其他线程凭空消失 ●fork产生的子进程一般用exit结束 ●vfork产生的产生的子进程一般用_exit结束 Linux的线程实现是在核外进行的,核内提供的是创建进程的接口do_fork()。 内核提供了两个系统调用clone()和fork(),最终都用不同的参数调用do_fork()核内API。 do_fork()提供了很多参数,包括CLONE_VM(共享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND(共享信号句柄表)和CLONE_PID(共享进程ID,仅对核内进程,即0号进程有效)。 当使用fork系统调用时,内核调用do_fork()不使用任何共享属性,进程拥有独立的运行环境。 使用pthread_create()来创建线程时,则最终设置了所有这些属性来调用__clone(),而这些参数又全部传给核内的do_fork(),从而创建的“进程”拥有共享的运行环境,只有栈是独立的,由__clone()传入。 5、为什么需要exec 一个进程一旦调用exec类函数,它本身就"死亡"了,系统把代码段替换成新的程序的代码,废弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,唯一留下的,就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了(不过exec类函数中有的还允许继承环境变量之类的信息) 那么如果我的程序想启动另一程序的执行但自己仍想继续运行的话,怎么办呢? 那就是结合fo
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 深入理解Linux fork函数0212 深入 理解 Linux fork 函数 0212