Linux中"system"和"exec"之间的区别是什么?

79

systemexec命令族的区别是什么?尤其是我想知道它们中哪一个会创建子进程来工作?

12个回答

104

system() 调用 sh 处理你的命令行,因此您可以获得通配符扩展等功能。而 exec() 及其相关函数替换当前进程映像为一个新的进程映像。

使用 system(),您的程序会继续运行,并返回一些关于所调用外部命令的状态信息。使用 exec(),则会将您的进程消除掉。

总的来说,你可以把 system() 视为更高层次的接口。您可以使用 fork()exec()wait() 的某种组合来复制其功能。

回答您的最后一个问题,system() 会创建一个子进程,而 exec() 系列不会。您需要使用 fork() 来实现。


system 调用是否也会新建一个 shell 来执行给定的命令,还是在同一 shell 中执行命令? - Krishna Oza
@Krishna_Oza - 除非调用system()的程序本身就是一个shell,否则不存在“同一shell”。我不确定我是否理解了。我的文档在这里说:“system()函数将参数命令交给命令解释器sh(1)。” - Carl Norum
3
system POSIX手册中引用的内容:*system()函数的行为就像使用fork()创建了一个子进程,并且该子进程使用以下方式使用execl()调用了sh实用程序:execl(<shell path>, "sh", "-c", command, (char *)0);*。 - patryk.beza

21

当exec函数成功时,它会替换当前正在运行的进程镜像,不会创建子进程(除非您之前使用了fork())。而system()函数则会创建一个子进程,并在所提供的命令执行完毕或出现错误时返回。


8

system() 会在一个新的子进程中执行提供的命令。而 exec() 则会用你指定的新可执行文件来替换当前进程。如果你想使用 exec 来生成一个子进程,你需要先使用 fork() 复制一份当前进程。


6
创建进程的方法:
  • fork(2),直接调用内核的系统调用
执行程序并替换当前进程:
  • execve(2),直接调用内核的系统调用,通常称为exec
等待子进程结束:
  • wait(2),直接调用内核的系统调用
在子进程中运行shell程序并等待其结束:
  • system(3),一个库函数
获取上述所有内容的手册页:man pages
   $ man 2 fork execve wait
   $ man 3 system

4

system()函数将调用您系统的默认命令shell,该shell将执行作为参数传递的命令字符串,该字符串本身可能会创建更多进程,这取决于命令和系统。无论如何,至少会创建一个命令shell进程。

使用system()函数可以调用任何命令,而使用exec()函数只能调用可执行文件。Shell脚本和批处理文件必须由命令shell执行。

基本上,它们用于不同的目的,完全不同。此外,exec()函数会替换调用进程,并且不会返回。更有用的比较应该是system()和spawn()之间的比较。虽然system()函数可能更简单易用,但它返回一个值,告诉您是否调用了命令shell,并且不会告诉您命令本身的成功与否。使用spawn()函数,可以获取进程的退出代码;按照惯例,非零用于指示错误条件。与exec()函数一样,spawn()函数必须调用可执行文件,而不能调用shell脚本或内置命令。


2

int system(const char *cmdstring);

例如:system("date > file");


一般情况下,system 函数通过调用 fork、exec 和 waitpid 实现,返回值有三种类型。

  • 如果 fork 失败或 waitpid 返回除 EINTR 之外的错误,system 返回 -1,并设置 errno 表示错误。
  • 如果 exec 失败,表示无法执行 shell,则返回值与 shell 执行 exit(127) 的返回值相同。
  • 否则,所有三个函数——fork、exec 和 waitpid 都成功,并且 system 的返回值是 shell 的终止状态,以 waitpid 指定的格式呈现。

fork 函数用于创建一个新进程(子进程),然后通过调用其中的一个 exec 函数来启动另一个程序。当一个进程调用其中的一个 exec 函数时,该进程将被全新的程序完全替换,而新程序则在其 main 函数开始执行。进程 ID 在 exec 中不会改变,因为并未创建新进程。exec 只是将当前进程的文本、数据、堆和栈段替换为磁盘上的全新程序。

共有六种不同的 exec 函数


int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );

int execv(const char *pathname, char *const argv []);

int execle(const char *pathname, const char *arg0, .../* (char *)0, char *const envp[] */ );

int execve(const char *pathname, char *const argv[], char *const envp []);

int execlp(const char *filename, const char *arg0,... /* (char *)0 */ );

int execvp(const char *filename, char *const argv []);


2

JonSpencer的回答很好,除了child_status必须是整数(不是整数指针),并且必须通过引用传递给等待函数。

因此,代码基本相同,只需更改这些内容:

#include <unistd.h>
#include <sys/wait.h>
#define NUMARGS 2

int main (int argc, char *argv[])
{
  pid_t child_pid, wait_pid;
  int child_status;
  char * exec_path = "/path/to/executable";
  char * child_args[NUMARGS] = {0,0};

  child_pid = fork();
  if (0 == child_pid)
  { // In child process
     ...
     int child_ret_code = execv(exec_path, child_args);  //or whichever flavor of exec() that floats your boat
     ... // if child_ret_code = -1, process execv() error return
  }
  else if (-1 == child_pid)
  {
     ... //process error return from fork
  }
  else if (0 < child_pid)
  {  // Parent process
     wait_pid = wait(&child_status);
     if (-1 == wait_pid)
     {
       ... //Process error return from wait()
     }
     else
     {  //  Good fork/exec/wait
        if (WIFEXITED(child_status))  // Child exited normally and hopefully returned exit code
        {
           int child_ret_code = WEXITSTATUS(child_status);
           ...  // Continue on as you would after call to system(3)
                //   except now you have the return code you needed
        }
     }
  }
}

请指出,我还没有足够的声誉来评论Jon的帖子,所以我编辑了它。一些人拒绝了这个修改,并要求我回答问题而不是编辑它,但在这种情况下,仅仅更正一个小错误就可以编辑现有的代码比编写完整的复制/粘贴/修改答案要简单、实用和清晰得多。

无论如何,感谢JonSpencer的回答,对我非常有用!


1
在使用exec(2)system(3)时需要注意一些重要的区别。 system()返回给调用者,而exec()将现有代码替换为新映像。这已经在上面解释过了。
然而,当您想运行一个过程,然后返回到现有代码并接收从被调用过程返回的返回代码时,就会出现不太明显的差异。 system()确实提供了一个返回代码,但该返回代码只能用于检测错误条件,不能用于恢复返回代码。
一种可能的正确系统调用序列是:
#include <unistd.h>
#include <sys/wait.h>
#define NUMARGS 2

int main (int argc, char *argv[])
{
  pid_t child_pid, wait_pid;
  int * child_status;
  char * exec_path = "/path/to/executable";
  char * child_args[NUMARGS] = {0,0};

  child_pid = fork();
  if (0 == child_pid)
  { // In child process
     ...
     int child_ret_code = execv(exec_path, child_args);  //or whichever flavor of exec() that floats your boat
     ... // if child_ret_code = -1, process execv() error return
  }
  else if (-1 == child_pid)
  {
     ... //process error return from fork
  }
  else if (0 < child_pid)
  {  // Parent process
     wait_pid = wait(child_status);
     if (-1 == wait_pid)
     {
       ... //Process error return from wait()
     }
     else
     {  //  Good fork/exec/wait
        if (WIFEXITED(child_status))  // Child exited normally and hopefully returned exit code
        {
           int child_ret_code = WEXITSTATUS(child_status);
           ...  // Continue on as you would after call to system(3)
                //   except now you have the return code you needed
        }
     }
  }
}

这个序列还有其他微妙之处,可以通过仔细阅读相关的man页面来确定,但是在没有信号、多个子进程等情况下,该代码将正常工作。此外,内联声明可能会限制变量的范围,但包含在内以允许该代码用作模板(您可以使用不同的编码风格:-)。


1

exec() 函数用正在执行的函数进程图像替换当前运行的进程。只有可执行文件才能使用此函数。

system() 函数隐式地分叉出一个新进程来服务请求,并返回它通过最初分叉的子进程获得的值。它使用系统默认的 shell 来执行操作。


0

System()函数会创建子进程并调用其他子shell,而exec()不会创建子进程。下面的示例将清楚地展示它们之间的区别。

一些代码...

exec('ls -l')

echo "1 2 3" //这不会在bash中执行(因为exec命令使用相同的shell)

一些代码...

system(ls -l) echo "1 2 3" //这将在完成System子进程后被执行,因为它们与父PID不同。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接