Fork & vfork & clone (转载)
转自:http://blog.csdn.net/zqy2000zqy/archive/2006/09/04/1176924.aspx
进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在Linux中被抽
象成各种数据对象:进程控制块、虚存空间、文件系统,文件I/O、信号处理函数。所以创建一个进程的
过程就是这些数据对象的创建过程。
在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于
父进程,具有良好的并发性,但是二者之间的通讯需要通过专门的通讯机制,如:pipe,fifo,System V
IPC机制等,另外通过fork创建子进程系统开销很大,需要将上面描述的每种资源都复制一个副本。这样
看来,fork是一个开销十分大的系统调用,这些开销并不是所有的情况下都是必须的,比如某进程fork出
一个子进程后,其子进程仅仅是为了调用exec执行另一个执行文件,那么在fork过程中对于虚存空间的复
制将是一个多余的过程(由于Linux中是采取了copy-on-write技术,所以这一步骤的所做的工作只是虚存
管理部分的复制以及页表的创建,而并没有包括物理也面的拷贝);另外,有时一个进程中具有几个独立
的计算单元,可以在相同的地址空间上基本无冲突进行运算,但是为了把这些计算单元分配到不同的处理
器上,需要创建几个子进程,然后各个子进程分别计算最后通过一定的进程间通讯和同步机制把计算结果
汇总,这样做往往有许多格外的开销,而且这种开销有时足以抵消并行计算带来的好处。
这说明了把计算单元抽象到进程上是不充分的,这也就是许多系统中都引入了线程的概念的原因。在
讲述线程前首先介绍以下vfork系统调用,vfork系统调用不同于fork,用vfork创建的子进程共享地址空
间,也就是说子进程完全运行在父进程的地址空间上,子进程对虚拟地址空间任何数据的修改同样为父进
程所见。但是用vfork创建子进程后,父进程会被阻塞直到子进程调用exec或exit。这样的好处是在子进
程被创建后仅仅是为了调用exec执行另一个程序时,因为它就不会对父进程的地址空间有任何引用,所以
对地址空间的复制是多余的,通过vfork可以减少不必要的开销。
在Linux中, fork和vfork都是调用同一个核心函数
do_fork(unsigned long clone_flag, unsigned long usp, struct pt_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,Digital Unix等), 线程和进程通过两种数据结构来
抽象表示: 进程表项和线程表项,一个进程表项可以指向若干个线程表项, 调度器在进程的时间片内再
调度线程。 但是在Linux中没有做这种区分, 而是统一使用task_struct来管理所有进程/线程,只是
线程与线程之间的资源是共享的,这些资源可是是前面提到过的:虚存、文件系统、文件I/O以及信号处
理函数甚至PID中的几种。
也就是说Linux中,每个线程都有一个task_struct,所以线程和进程可以使用同一调度器调度。其实
Linux核心中,轻量级进程和进程没有质上的差别,因为Linux中进程的概念已经被抽象成了计算状态加资
源的集合,这些资源在进程间可以共享。如果一个task独占所有的资源,则是一个HWP,如果一个task和
其它task共享部分资源,则是LWP。
clone系统调用就是一个创建轻量级进程的系统调用:
int clone(int (*fn)(void * arg), void *stack, int flags, void * arg);
其中fn是轻量级进程所执行的过程,stack是轻量级进程所使用的堆栈,flags可以是前面提到的
CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND,CLONE_PID的组合。Clone 和fork,vfork在实现时
都是调用核心函数do_fork。
do_fork(unsigned long clone_flag, unsigned long usp, struct pt_regs);
和fork、vfork不同的是,fork时clone_flag = SIGCHLD;
vfork时clone_flag = CLONE_VM | CLONE_VFORK | SIGCHLD;
而在clone中,clone_flag由用户给出。
下面给出一个使用clone的例子。
Void * func(int arg)
{
. . . . . .
}
int main()
{
int clone_flag, arg;
. . . . . .
clone_flag = CLONE_VM | CLONE_SIGHAND | CLONE_FS |
CLONE_FILES;
stack = (char *)malloc(STACK_FRAME);
stack += STACK_FRAME;
retval = clone((void *)func, stack, clone_flag, arg);
. . . . . .
}
看起来clone的用法和pthread_create有些相似,两者的最根本的差别在于clone是创建一个LWP,对
核心是可见的,由核心调度,而pthread_create通常只是创建一个用户线程,对核心是不可见的,由线程
库调度。
linux的pthread_create最终调用clone,pthread_create调用clone,并把开辟一个stack作为参数
thread 建立, 同步,销毁等由线程库负责,
Fork & vfork & clone (转载)的更多相关文章
- Unix环境高级编程:fork, vfork, clone
fork fork产生的子进程是传统意义上的进程,fork之后执行路径就互不关联了,一旦fork返回后面的代码就在不用的进程上下文中执行了.到底是子进程先执行还是父进程先执行一般是随机的或者依赖实现的 ...
- 写时复制和fork,vfork,clone
写时复制 原理: 用了“引用计数”,会有一个变量用于保存引用的数量.当第一个类构造时,string的构造函数会根据传入的参数从堆上分配内存,当有其它类需要这块内存时,这个计数为自动累加,当有类析构时, ...
- fork,vfork和clone底层实现
分类: LINUX2011-10-13 09:33 1116人阅读 评论(0) 收藏 举报 structdstsignalthreadnulldomain fork,vfork,clone都是linu ...
- GIT(6)----fork和clone的区别,fetch与pull的区别
参考资料: [1].Git学习笔记:fork和clone的区别,fetch与pull的区别 [2].在Github和Git上fork之简单指南
- fork &vfork --陈皓
http://coolshell.cn/articles/7965.html http://coolshell.cn/articles/12103.html#more-12103 前两天有人问了个关于 ...
- fork系统调用(转载)
(1) fork系统调用说明 fork系统调用用于从已存在进程中创建一个新进程,新进程称为子进程,而原进程称为父进程.fork调用一次,返回两次,这两个返回分别带回它们各自的返回值,其中在父进程中的返 ...
- 进程控制fork vfork,父子进程,vfork保证子进程先运行
主要函数: fork 用于创建一个新进程 exit 用于终止进程 exec 用于执行一个程序 wait 将父进程挂起,等待子进程结束 getpid 获取当前进程的进程ID nice 改变进程的优先级 ...
- Ruby中如何复制对象 (deep clone)(转载)
Ruby中如何复制对象 (deep clone) 用Ruby复制一个对象(object)也许没有你想像的那么容易. 今天我google了半天, 做个总结吧. 先从最简单的开始, b = a 是复制吗? ...
- 在shell脚本中调用另一个脚本的三种不同方法(fork, exec, source)——转载
原文链接:http://blog.chinaunix.net/uid-22548820-id-3181798.html fork ( /directory/script.sh) :如果shell中包含 ...
随机推荐
- python 使用国内源安装软件
python linux 等 使用国内源安装软件 速度更快 你值得拥有 ! 豆瓣源:pip install -i https://pypi.douban.com/simple/ 阿里源:pip ins ...
- 根据节点解析xml
config.xml文件如下: <?xml version="1.0" encoding="gb2312" ?> <root> < ...
- 三:Storm设计一个Topology用来统计单词的TopN的实例
Storm的单词统计设计 一:Storm的wordCount和Hadoop的wordCount实例对比
- php 公历农历互相转换
废话不多,直接上代码 <?php class Lunar { /** * Created by 闻海南. * User: timeless * Date: 2018-5-29 * Time: 上 ...
- Centos7安装和配置NFS
(1)nfs简介 作用:通过网络的不同的主机之间共享资源,支持多节点挂载并发写入 特点:单台,适合小型网络集群架构,非常稳定:大型公司使用(mfs,glusterfs,fastdfs) nfs优点:部 ...
- 基于kbengine 0.4.20 解读
[以下文章转自kbe论坛] MMOG服务端是一种高品质的工程项目,品读开源的kbe是一种乐趣.本文档我带童鞋们一起领略一下.囿于我知识面和经验方面所限,文中所述之处难免有错误存在,还请读童鞋们睁大慧眼 ...
- 架构设计之NodeJS操作消息队列RabbitMQ
一. 什么是消息队列? 消息(Message)是指在应用间传送的数据.消息可以非常简单,比如只包含文本字符串,也可以更复杂,可能包含嵌入对象. 消息队列(Message Queue)是一种应用间的通信 ...
- 安装 SQL Server 2008 和管理工具 SQL Server 2008 management studio 及相关问题解决
Sql Server 2008 问题小总结 http://www.lihengyu.com/blog/4877.html 安装 SQL Server 2008 和管理工具 SQL Server 200 ...
- Linux命令之who
who [选项] … [FILE | ARG1 ARG2] 打印有关当前登录用户的信息. (1).选项 -a,--all 同-b –d –login –p –r –t –T -u -b,--boot ...
- RabbitMQ (十一) 消息确认机制 - 消费者确认
由于生产者和消费者不直接通信,生产者只负责把消息发送到队列,消费者只负责从队列获取消息(不管是push还是pull). 消息被"消费"后,是需要从队列中删除的.那怎么确认消息被&q ...