进程管理之2:进程三部曲:创建、执行和消亡
2018-07-15 23:18
309 查看
date: 2014-10-18 12:00
这一章《情景分析》讲得很通俗易懂。
在linux系统中,第一个进程(init进程)是与生俱来的,是内核设计者刻意安排的。除此以外,一个进程必须从当前已经存在的某个进程“复制”出来,而不能凭空“创造”出来。
linux将进程的创建与目标程序的执行分两步走。
第一步,从已经存在的“父进程”像细胞分裂那样“复制”出一个子进程。 此时,子进程有自己的task_struct结构与系统空间堆栈(即进程四要素中的第2和第3个要素),但与父进程共享所有其他资源,比如打开的文件,而且这些文件的当前读写指针也停留在相同的地方(父子进程每个相同的文件描述符共用同一个文件表项)。所以这一步所做的工作是“复制”。为此,linux提供了两个系统调用,fork和clone。区别在于fork是全部复制(因此fork不带参数),父进程的资源通过结构体的复制(可以理解为“深拷贝”)“遗传”给子进程。而clone则是有选择性的复制(所以clone带参数),而没有复制的数据结构则通过指针的拷贝(可以理解为“浅拷贝”)与父进程共享。在极端情况下,一个进程可以clone出一个线程。另外还有一个vfork系统调用,也不带参数,除了task_struct结构和系统空间堆栈外,所有的资源都是通过指针拷贝与父进程共享,所以vfork复制出来的是线程而不是进程。
为什么要有复制这一步?所谓复制(“深拷贝”)只是进程基本资源的复制,如task_struct结构、系统空间堆栈、页面表等等;除此以外,其他的资源比如父进程的代码及全局变量并不需要复制,而是以只读的方式共享,仅在需要写时才通过copy on write手段为所涉及的页面建立一个新的副本(这就意味着,当子进程改变一个全局变量时,并不会影响该全局变量在父进程中的值,因为子进程修改的是属于它的副本)。所以复制的代价是很低的,但是通过复制继承下来的资源往往对子进程很有用。比如在client/server系统中的server一方的实现中,fork或clone常常是最自然、最有效的手段。
第二步是目标程序的执行。大多数情况下,复制出一个子进程后有新的目标程序等待子进程去执行(但也不一定,比如子进程共享父进程的代码段),这种情况下,复制完成以后,子进程一般要与父进程分道扬镳走自己的路了。为此,linux提供了一个系统调用execve,让一个进程执行一个以文件形式存在的可执行程序映像。既然子进程要走自己的路,那就不要回头了,所以系统调用execve不会返回,而是“壮士一去兮不复返”。
那么这种分两步走有什么好处呢?首先,很多场景只需单独使用fork,其后并不跟execve;其次,分两步走是的子进程在执行exceve之前有机会修改自己的属性(比如IO重定向、用户ID,信号安排等)。
创建了子进程后,父进程该何去何从?一般有三个选择。第一是继续走自己的路,与子进程分道扬镳。只是如果子进程先于父进程去世,则由内核给父进程发一个报丧的信号(SIGCHILD)。第二个是停下了,也就是进入睡眠状态,等待子进程完成自己的使命而最终去世,然后父进程再运行。linux为此提供了两个系统调用,wait4(等待某个特定的子进程去世)和wait3(等待任何一个子进程去世)。第三个选择是“自行退出历史舞台”,结束自己的生命。linux为此提供了一个系统调用exit,(不过代码中一般很少直接调用exit,而是通过return语句从main函数中返回,gcc在编译和链接时会自动加上exit,详见APUE第7章)。
需要说明一点:父进程通过fork系统调用复制出一个子进程后,由于子进程有了自己的task_struct结构,所以子进程就会被调度器看到并可能被调度执行,且与父进程具有相同的返回地址(即返回到调用fork的下一条指令),所以当父进程和子进程受调度继续运行而从内核空间返回到用户空间时都返回到同一点上,但二者的返回值不一样,父进程的返回值为子进程的pid,而子进程的返回值为0。因此,代码中可以根据返回值来判断父子进程。
这一章《情景分析》讲得很通俗易懂。
在linux系统中,第一个进程(init进程)是与生俱来的,是内核设计者刻意安排的。除此以外,一个进程必须从当前已经存在的某个进程“复制”出来,而不能凭空“创造”出来。
linux将进程的创建与目标程序的执行分两步走。
第一步,从已经存在的“父进程”像细胞分裂那样“复制”出一个子进程。 此时,子进程有自己的task_struct结构与系统空间堆栈(即进程四要素中的第2和第3个要素),但与父进程共享所有其他资源,比如打开的文件,而且这些文件的当前读写指针也停留在相同的地方(父子进程每个相同的文件描述符共用同一个文件表项)。所以这一步所做的工作是“复制”。为此,linux提供了两个系统调用,fork和clone。区别在于fork是全部复制(因此fork不带参数),父进程的资源通过结构体的复制(可以理解为“深拷贝”)“遗传”给子进程。而clone则是有选择性的复制(所以clone带参数),而没有复制的数据结构则通过指针的拷贝(可以理解为“浅拷贝”)与父进程共享。在极端情况下,一个进程可以clone出一个线程。另外还有一个vfork系统调用,也不带参数,除了task_struct结构和系统空间堆栈外,所有的资源都是通过指针拷贝与父进程共享,所以vfork复制出来的是线程而不是进程。
为什么要有复制这一步?所谓复制(“深拷贝”)只是进程基本资源的复制,如task_struct结构、系统空间堆栈、页面表等等;除此以外,其他的资源比如父进程的代码及全局变量并不需要复制,而是以只读的方式共享,仅在需要写时才通过copy on write手段为所涉及的页面建立一个新的副本(这就意味着,当子进程改变一个全局变量时,并不会影响该全局变量在父进程中的值,因为子进程修改的是属于它的副本)。所以复制的代价是很低的,但是通过复制继承下来的资源往往对子进程很有用。比如在client/server系统中的server一方的实现中,fork或clone常常是最自然、最有效的手段。
第二步是目标程序的执行。大多数情况下,复制出一个子进程后有新的目标程序等待子进程去执行(但也不一定,比如子进程共享父进程的代码段),这种情况下,复制完成以后,子进程一般要与父进程分道扬镳走自己的路了。为此,linux提供了一个系统调用execve,让一个进程执行一个以文件形式存在的可执行程序映像。既然子进程要走自己的路,那就不要回头了,所以系统调用execve不会返回,而是“壮士一去兮不复返”。
那么这种分两步走有什么好处呢?首先,很多场景只需单独使用fork,其后并不跟execve;其次,分两步走是的子进程在执行exceve之前有机会修改自己的属性(比如IO重定向、用户ID,信号安排等)。
创建了子进程后,父进程该何去何从?一般有三个选择。第一是继续走自己的路,与子进程分道扬镳。只是如果子进程先于父进程去世,则由内核给父进程发一个报丧的信号(SIGCHILD)。第二个是停下了,也就是进入睡眠状态,等待子进程完成自己的使命而最终去世,然后父进程再运行。linux为此提供了两个系统调用,wait4(等待某个特定的子进程去世)和wait3(等待任何一个子进程去世)。第三个选择是“自行退出历史舞台”,结束自己的生命。linux为此提供了一个系统调用exit,(不过代码中一般很少直接调用exit,而是通过return语句从main函数中返回,gcc在编译和链接时会自动加上exit,详见APUE第7章)。
需要说明一点:父进程通过fork系统调用复制出一个子进程后,由于子进程有了自己的task_struct结构,所以子进程就会被调度器看到并可能被调度执行,且与父进程具有相同的返回地址(即返回到调用fork的下一条指令),所以当父进程和子进程受调度继续运行而从内核空间返回到用户空间时都返回到同一点上,但二者的返回值不一样,父进程的返回值为子进程的pid,而子进程的返回值为0。因此,代码中可以根据返回值来判断父子进程。
相关文章推荐
- 【进程管理】进程三部曲:创建,执行与消亡(综述)
- Linux内核源代码情景分析-进程的创建,执行,等待,消亡
- php进程管理--手动创建进程锁,防止重复执行某程序代码
- 进程的创建与可执行程序的加载
- 《linux下进程的创建,执行,监控和终止》
- Linux进程启动过程分析do_execve(可执行程序的加载和运行)---Linux进程的管理与调度(十一)
- 让linux程序后台执行(后台程序管理利器supervisior管理Flume进程)
- 进程管理之进程创建和删除(二)
- Linux内核笔记——进程管理之执行体
- 实验二:进程的创建与可执行程序的加载
- 进程的创建与可执行程序的加载
- php-fpm的pool, php-fpm慢执行日志,open_basedir,php-fpm进程管理
- 进程的创建与可执行程序的加载
- 进程的创建与可执行程序的加载
- [Linux进程]使用vfork创建子进程并且执行命令
- win7中右击“计算机”点“管理” 则出现“该文件没有与之关联的程序来执行操作。请安装一个程序,或者,如果已安装程序,请在‘默认程序’控制面板中创建关联。”
- Linux内核设计的艺术-进程2的创建及执行
- Node.js中创建和管理外部进程详解
- Delphi CreateProcess WIN32API函数CreateProcess用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件
- Linux进程管理(1):进程描述和进程创建