system()、exec()、fork()三个与进程有关的函数的比较

启动新进程(system函数)

system()函数可以启动一个新的进程。

int system (const char *string )

这个函数的效果就相当于执行sh –c string。

一般来说,使用system函数远非启动其他进程的理想手段,因为它必须用一个shell来启动需要的程序。这样对shell的安装情况,以及shell的版本依赖性很大。

system函数的特点:

建立独立进程,拥有独立的代码空间,内存空间

等待新的进程执行完毕,system才返回。(阻塞)

替换进程映像(exec函数)

exec函数可以用来替换进程映像。执行exec系列函数后,原来的进程将不再执行,新的进程的PID、PPID和nice值与原先的完全一样。其实执行exec系列函数所发生的一切就是,运行中的程序开始执行exec调用中指定的新的可执行文件中的代码。

exec函数的特点:

当进程调用一种exec函数时,源进程完全由新程序代换,而新程序则从其main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。特别地,在原进程中已经打开的文件描述符,在新进程中仍将保持打开,除非它们的“执行时关闭标志”(close on exec flag)被置位。任何在原进程中已打开的目录流都将在新进程中被关闭。

复制进程映像(fork函数)

fork函数

头文件

  1. #include<unistd.h>
  2. #include<sys/types.h>

函数原型

  1. pid_t fork( void);  

返回值:

若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1

关于fork函数的作用,《Linux程序设计》中是这样解释的

我们可以通过调用fork创建一个新进程。这个系统调用复制当前进程,在进程表中新建一个新的表项,新表项中的许多属性与当前进程是相同的。新进程几乎与元进程一模一样,执行的代码也完全相同,但是新进程有自己的数据空间、环境和文件描述符。

这个解释其实过于笼统,很多细节问题都没有说。下面就简单说一下调用fork时发生的一些细节问题。或者叫fork函数的特点

首先,现在的UNIX系统和Linux系统都采用写时复制技术(COW:Copy On Write)。使用这种技术,当调用fork函数时,新的进程只是拥有自己的虚拟内存空间,而没有自己的物理内存空间。新进程共享源进程的物理内存空间。而且新内存的虚拟内存空间几乎就是源进程虚拟内存空间的一个复制。

我们知道,进程空间可以简单地分为程序段(正文段)、数据段、堆和栈四部分(简单这样理解)。采用写时复制的fork函数,当执行完fork后的一定时间内,新的进程(子进程)和源进程的进程空间关系如下图:

如上图,fork执行时,Linux内核会为新的进程P2创建一个虚拟内存空间,而新的虚拟空间中的内容是对P1虚拟内存空间中的内容的一个拷贝。而P2和P1共享原来P1的物理内存空间。

当然要理解“写时复制”中,上图中所展示的状态是会发生变化的。什么时候回发生变化呢?就是,父子两个进程中任意一个进程对数据段、栈区、堆区进行写操作时,上图中的状态就会被打破,这个时候就会发生物理内存的复制,这也就是叫“写时复制”的原因。发生的状态转变如下:

我们发现,P2有了属于自己的物理内存空间。值得注意的是,各个段之间发生的变化应当是独立的,也就是说,如果只有数据段发生了写操作那么就只有数据段进行写时复制。而堆、栈区域依然是父子进程共享。还有一个需要注意的是,正文段(程序段)不会发生写时复制,这是因为通常情况下程序段是只读的。子进程和父进程从fork之后,基本上就是独立运行,互不影响了。

此外需要特别注意的是,父子进程的文件描述符表也会发生写时复制。

还有一个叫vfork的函数,这个做法更加火爆,内核连子进程的虚拟地址空间结构也不创建了,直接共享了父进程的虚拟空间,当然了,这种做法就顺水推舟的共享了父进程的物理空间

system()exec()fork()函数比较

首先比较一下exec()函数和fork()。这两个函数一个是换药不换汤(execl函数),另一个是换汤不换药(fork函数)。那么什么是汤、什么又是药呢?我们知道进程是个很复杂的东西。从task_struct 结构体的代码量上就可以看出来(task_struct是Linux内核中用来描述进程的一个结构体,这个结构体光代码貌似就有好几屏)。我们可以把进程的PID、PPID和nice值等看作是汤,而把进程空间(简单理解就是正文段、数据段、堆、栈等)看作是药。

exec()函数是换药不换汤,就是说执行exec函数后,并没有产生新的进程,也就是汤还是那些汤,进程的PID、PPID和nice值等没有发生变化。但是exec()函数却将药换了,也就是将进程空间换掉了,新的进程空间是为了执行新的程序所准备的,所以新的进程空间与原进程空间并没有什么关系。

fork()函数是换汤不换药,意思是执行exec()函数后,产生了新的进程,新的进程的PID、PPID与原来原来的进程不同,说明父子进程是两个不同的进程,但是exec并没有把药换掉,而是将药复制了一份给子进程。fork刚执行后的一段时间内,父子进程有着相同的状态(进程空间中的东西都一样,因为fork采用“写时复制”,一开始父子进程共享物理内存空间)。但是一旦父子进程中有一个进程试图修改进程空间,这时父子进程就各自拥有了各自的进程空间,简单地理解,从这一时刻器,父子进程就是两个独立的进程,谁都不会影响谁(实际上还是有一定影响的,在这里可以忽略),父子进程之间的关联仅剩下它们共享的代码段了。

对于system函数,我们可以先看一下它的源代码:

int system(const char * cmdstring)
{
  pid_t pid;
  int status;

  if(cmdstring == NULL){

      return (1);
  }

  if((pid = fork())<0){

        status = -1;
  }
  else if(pid == 0){
    execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
    -exit(127); //子进程正常执行则不会执行此语句
    }
  else{
        while(waitpid(pid, &status, 0) < 0){
          if(errno != EINTER){
            status = -1;
            break;
          }
        }
    }
    return status;
}

我们看到system()函数实际上就是先执行了fork函数,然后新产生的子进程立刻执行了exec函数,我们前面说个fork函数换汤不换药,exec函数换药不换汤,那么system函数就是既换汤也换了药,也就是system函数会产生新进程,这就意味着新进程的PID、PPID等与原进程不同。system也会产生新的进程空间,而且新的进程空间是为新的程序准备的,所以和原进程的进程空间没有任何关系(不像fork新进程空间是对原进程空间的一个复制)。还要注意的是,system函数代码中else部分执行了wait函数,这就意味着,原进程会等待子进程执行完毕(阻塞)

最后还要注意的一个问题是关于文件描述符的。

exec函数执行后,原来打开的文件描述符依然存在。

fork函数执行后,原来打开的文件描述符会复制一份到新的进程中,之后两个进程之间的文件描述符就相对独立了。

system函数先执行fork函数,这之后两个进程的文件描述符就相对独立了。之后exec函数并不影响文件描述符。

时间: 2024-08-03 03:22:35

system()、exec()、fork()三个与进程有关的函数的比较的相关文章

读书笔记-APUE第三版-(8)进程控制

进程ID 每一个进程都有一个唯一的进程ID.几个特殊进程: 0号进程是内核进程,一般是调度进程swapper. 1号进程init,是用户进程(以root权限执行/sbin/init),负责初始化. 几个重要函数:getpid(进程ID)/getppid(父进程ID)/getuid(进程真有用户ID)/geteuid(进程有效用户ID)/getgid(进程真有用户组ID)/getegid(进程有效用户组ID). fork/exec/wait例程 fork家族函数用于创建子进程(父子进程关系下节详细

PHP 执行系统外部命令 system() exec() passthru()

PHP中调用外部命令,可以用如下三种方法来实现: 方法一:用PHP提供的专门函数(四个): PHP提供4个专门的执行外部命令的函数:exec(), system(), passthru(), shell_exec() 1)exec() 原型: string exec ( string $command [, array &$output [, int &$return_var ]] ) 说明: exec执行系统外部命令时不会输出结果,而是返回结果的最后一行.如果想得到结果,可以使用第二个参

Linux进程的创建函数fork()及其fork内核实现解析

进程的创建之fork() Linux系统下,进程可以调用fork函数来创建新的进程.调用进程为父进程,被创建的进程为子进程. fork函数的接口定义如下: #include <unistd.h> pid_t fork(void); 与普通函数不同,fork函数会返回两次.一般说来,创建两个完全相同的进程并没有太多的价值.大部分情况下,父子进程会执行不同的代码分支.fork函数的返回值就成了区分父子进程的关键.fork函数向子进程返回0,并将子进程的进程ID返给父进程.当然了,如果fork失败,

nginx学习十三 初始fork和nginx守护进程ngx_daemon

学习nginx已经有一个多月了,觉得越来越吃力了,主要原因自己总结了一下:1平台是基于linux的,以前几乎没有接触过linux,而nginx使用了很多linux的函数:2就是进程,这个东西接触的也很少,linux的多进程更不用说,而现在正好看到这里,觉得异常的吃力,这不看到nginx守护进程的建立,就找资料好好学习一下,所以本文已学习fork为主要内容. 好了,先看一下nginx的守护进程的建立,然后在学习fork. http://blog.csdn.net/xiaoliangsky/arti

读书笔记-APUE第三版-(7)进程环境

本章关注单进程运行环境:启动&终止.参数传递和内存布局等. 进程启动终止 如图所示: 启动:内核通过exec函数执行程序,在main函数运行之前,会调用启动例程(start-up routine),取得命令行参数和环境变量.可以把启动例程理解为exit(main(argc,argv)). 终止:五种正常终止方式(从main方法返回/exit/_exit/最后一个线程返回/最后一个线程退出):三种异常终止方式(abort/接收到信号/最后一个线程接收到取消请求). exit与_exit关系:exi

浅析三种特殊进程:孤儿进程,僵尸进程和守护进程.

其实有时想想linux内核的设计也蕴含着很多人生哲学,在linux中有这么几个特殊进程中,我们一开始见到它们的名字可能还会觉得很诧异,但在了解完了原理后,我们仔细想想,这样的命名也不无道理!下面我就给大家分别介绍一下这三种特殊的进程! 1.孤儿进程 如果父进程先退出,子进程还没退出那么子进程将被 托孤给init进程,这是子进程的父进程就是init进程(1号进程).其实还是很好理解的. #include <sys/types.h> #include <unistd.h> #inclu

嵌入式 Linux进程间通信(三)——守护进程

嵌入式 Linux进程间通信(三)--守护进程 一.守护进程简介 1.守护进程简介 守护进程(Daemon)是运行在后台.独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件的一种特殊进程.守护进程常常在系统引导装入时启动,在系统关闭时终止.Linux的大多数服务器就是用守护进程实现的.比如,Internet服务器inetd,Web服务器httpd等.同时,守护进程完成许多系统任务.比如,作业规划进程crond等.守护进程的创建本身并不复杂,复杂的是各种版本的Unix的实现机制不尽相同

(三十一)进程

一.进程的其他方法 Process对象的其他方法或属性:name,pid,is_alive(),terminate(). from multiprocessing import Process,Queue import os import time # def f1(): # print('aaaalkalknflkanlnflalkam') # # def f2(): # print(111, os.getpid())#获取当前进程的id # print(222, os.getppid())#

php 快速fork出指定个子进程

转:php 快速fork出指定个子进程 $pids = array(); $child_pid = pcntl_fork(); if ($child_pid == -1) { throw new Exception( __METHOD__ . "|" . __LINE__ . ": fork() error"); } else if ($child_pid) { //parent exit(0); } else { //child for($i=0;$i<3;