进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在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, structpt_regs),代码如下:

asmlinkage int sys_fork(struct pt_regs regs)
{return do_fork(SIGCHLD, regs.esp, &regs, 0, NULL, NULL);
}
asmlinkage int sys_vfork(struct pt_regs regs)
{return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, &regs, 0, NULL, NULL);
}

其中 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 系统调用前先简单介绍线程的一些概念。

线程是在进程的基础上进一步的抽象,也就是说一个进程分为两个部分:线程集合和资源集合。线程是进程中的一个动态对象,它应该是一组独立的指令流,进程中的所有线程将共享进程里的资源。但是线

程应该有自己的私有对象:比如程序计数器、堆栈和寄存器上下文。

线程分为三种类型:内核线程、轻量级进程和用户线程。

内核线程:

它的创建和撤消是由内核的内部需求来决定的,用来负责执行一个指定的函数,一个内核线程不需要和一个用户进程联系起来。它共享内核的正文段核全局数据,具有自己的内核堆栈。它能够单独的被调度并且使用标准的内核同步机制,可以被单独的分配到一个处理器上运行。内核线程的调度由于不需要经过态的转换并进行地址空间的重新映射,因此在内核线程间做上下文切换比在进程间做上下文切换快得多。

源码:

// Create a kernel thread
/*** 创建一个新的内核线程* fn-要执行的内核函数的地址。* arg-要传递给函数的参数* flags-一组clone标志*/
int kernel_thread(int (*fn)(void *), void * arg, unsigned long flags)
{struct pt_regs regs;memset(&regs, 0, sizeof(regs));// 内核栈地址,为其赋初值。// do_fork 将从这里取值来为新线程初始化 CPU。regs.ebx = (unsigned long) fn;regs.edx = (unsigned long) arg;regs.xds = __USER_DS;regs.xes = __USER_DS;regs.orig_eax = -1;/// 把 eip 设置成 kernel_thread_helper,这样,新线程将执行 fn 函数。如果函数结束,将执行do_exit// fn 的返回值作为 do_exit 的参数。regs.eip = (unsigned long) kernel_thread_helper;regs.xcs = __KERNEL_CS;regs.eflags = X86_EFLAGS_IF | X86_EFLAGS_SF | X86_EFLAGS_PF | 0x2;/* Ok, create the new process.. */// CLONE_VM避免复制调用进程的页表。由于新的内核线程无论如何都不会访问用户态地址空间。// 所以复制只会造成时间和空间的浪费。// CLONE_UNTRACED标志保证内核线程不会被跟踪,即使调用进程被跟踪。return do_fork(flags | CLONE_VM | CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
}

轻量级进程:

轻量级进程是核心支持的用户线程,它在一个单独的进程中提供多线程控制。这些轻量级进程被单独的调度,可以在多个处理器上运行,每一个轻量级进程都被绑定在一个内核线程上,而且在它的生命周期

这种绑定都是有效的。轻量级进程被独立调度并且共享地址空间和进程中的其它资源,但是每个 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, structpt_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 建立, 同步,销毁等由线程库负责。

总结:

创建进程的系统调用函数:

  1. fork:父子各自独立,

  2. vfork:父子共用一套虚拟地址空间,但是开始的时候会堵塞父进程,直至子进程调用 exit 或者 exec 。一般用于执行 exec 调用。

  3. clone:创建轻量级进程,父子共用部分内容(虚拟地址空间等)

  4. exec:加载可执行文件从而创建进程,父进程消亡。

转载于:https://blog.csdn.net/rock_joker/article/details/72722008

(SAW:Game Over!)

OS / Linux / clone、fork、vfork 与 pthread_create 创建线程有何不同相关推荐

  1. clone的fork与pthread_create创建线程有何不同pthread多线程编程的学习小结(转)

    进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在Linux中被抽 象成各种数据对象:进程控制块.虚存空间.文件系统,文件I/O.信号处理函数.所以创建一个进程的 过程就是这 ...

  2. clone的fork与pthread_create创建线程有何不同pthread多线程编程的学习小结

    进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合,这些资源在Linux中被抽 象成各种数据对象:进程控制块.虚存空间.文件系统,文件I/O.信号处理函数.所以创建一个进程的 过程就是这 ...

  3. linux c之使用pthread_create创建线程pthread_join等待线程和pthread_exit终止线程总结

    1.介绍API 1.pthread_create函数 函数简介 pthread_create是UNIX环境创建线程函数 头文件 #include<pthread.h> 函数声明 int p ...

  4. pthread_create创建线程

    什么是线程? 轻量级的进程(LWP:light weight process),在Linux环境下线程的本质仍是进程. 进程:拥有独立的地址空间,拥有PCB,相当于独居. 线程:有PCB,但没有独立的 ...

  5. pthread_create()创建线程最大个数

    线程应用程序最常见导致创建线程失败的原因是线程栈大小的设置.创建一个新的线程,默认情况下系统为线程栈预留了2MB的寻址空间.线程栈起始于进程虚拟 内存的高端地址,并向虚拟内存底端地址方向扩展.取决于线 ...

  6. pthread_create创建线程失败,返回11: Resource temporarily unavailable

    一.问题背景 在重启一个程序时死活启动不起来,后发现程序中在使用pthread_create函数创建线程时失败,返回值11,即Resource temporarily unavailable. 二.问 ...

  7. OS / Linux / 主线程退出了,子线程会退出吗?

    在 linux 世界中,主线程退出了,子线程是否退出是要看主线程的退出方式. 主线程以 return 的方式退出. 主线程以 pthread_exit() 函数的方式退出. 前者,main() 执行完 ...

  8. Linux系统编程36:多线程之线程控制之pthread线程库(线程创建,终止,等待和分离)

    文章目录 (1)POSIX线程库 (2)pthread_create--创建线程 A:关于Linux线程的再理解 B:线程ID及地址空间布局 (3)pthread_exit--线程终止 (4)pthr ...

  9. 【Linux进程、线程、任务调度】二 fork/vfork与写时拷贝 线程的本质 托孤 进程睡眠和等待队列

    学习交流加(可免费帮忙下载CSDN资源): 个人微信: liu1126137994 学习交流资源分享qq群1(已满): 962535112 学习交流资源分享qq群2(已满): 780902027 学习 ...

最新文章

  1. 网络配置——Linux入门到精通的难关之一
  2. 子弹短信体验分析:一个单纯想“快”的IM工具 子弹短信体验分析:锤科情怀缩影,打败微信有点远
  3. android socket ppt,socket網路設計.ppt
  4. pstools psexec 执行文件
  5. Longest k-Good Segment CodeForces - 616D(尺取法)
  6. Servlet使用适配器模式进行增删改查案例(EmpDaoImpl.java)
  7. 客户端本地存储的比较及使用window.name数据传输
  8. 如何评价,为何程序员被骂立马就辞职呢?
  9. (转)机器学习算法比较
  10. linux ls-la/boot,CentOS系统启动/boot/vmlinuz修复(实验)
  11. 删除了项目下的\WEB-INF\classes文件夹,在eclipse的tomcat部署启动时报错。
  12. 阿宁的linux学习---vi/vim
  13. 知识传输革命即将到来
  14. C++描述 1341. 十三号星期五
  15. 计算机中职生毕业鉴定评语,中职生学生毕业鉴定评语
  16. Unity粒子系统-粒子光环
  17. 在Linux系统下制作U盘启动盘
  18. 【Pytorch神经网络理论篇】 39 Transformers库中的BERTology系列模型
  19. CSU - 2078 查找第k大(O(n)区间第k大 快排思想)
  20. 这4类人去创业和自由职业会死得很惨

热门文章

  1. asp.net上传图片文件自动修改图片大小代码
  2. 解决开机自检D盘问题
  3. C#是面向对象编程语言
  4. [python pycharm pytorch debug sets]
  5. centos7 minimal运行vue项目npm run serve时报错:Error: spawn xdg-open ENOENT --解决办法:yum install xdg-utils
  6. js小案例:控制电灯开关
  7. Win10自带的邮件客户端配置腾讯企业邮箱账号
  8. scala Akka并发编程介绍
  9. 使用KubeKey 离线环境部署 KubeSphere v3.0.0
  10. Windows彻底卸载删除MySQL