系统调用exec 和fork()联合起来为程序员提供了强有力的功能。我们可以先用fork()建立子进程,然后在子进程中使用exec,这样就实现了父进程运行一个与其不同的子进程,并且父进程不会被覆盖。
下面我们给出一个exec 和fork()联用的例子,从中我们可以清楚的了解这两个系统调用联用的细节。其程序清单如下:
1 #include <stdio.h> 2 #include <unistd.h> 3 main() 4 { 5 int pid; 6 /* fork 子进程 */ 7 pid=fork(); 8 switch(pid) { 9 case -1: 10 perror("fork failed"); 11 exit(1); 12 case 0: 13 execl("/bin/ls","ls","-l","--color",NULL); 14 perror("execl failed"); 15 exit(1); 16 default: 17 wait(NULL); 18 printf("ls completed "); 19 exit(0); 20 } 21 }
在程序中,在调用fork()建立一个子进程之后,马上调用了wait(),使父进程在子进程结束之前,一直处于睡眠状态。所以,wait()向程序员提供了一种实现进程之间同步的简单方法,我们将在下面对它作出更详细的讨论。
为了说明得更清楚一些,我们用图3-3 来解释程序的工作。图3-3 分为fork()调用前、fork()调用后和exec 调用后三个部分。
图1 exec()和fork()的联用
在fork()调用前,只有一个进程A,PC 指向将要执行的下一个语句。fork()调用后,就有了进程A 和进程B。A 是父进程,它正在执行系统调用wait(),使进程A 睡眠,直至进程B 结束。同时,B 正在用exec 装入命令ls。exec 调用后,进程B 的程序被ls 的代码取代,这时执行ls 命令的代码。进程B 的PC 指向ls 的第一个语句。由于A 正在等待B 的结束,所以它的PC 所指位置未变。
现在我们应该了解命令解释程序shell 的工作概况。当shell 从命令行接受到以正常方式(即前台运行)执行一个命令或程序的要求时,它就按上述方法调用fork()、exec 和ait(),以实现命令或程序的执行。当要求在后台执行一个命令或程序时,shell 就省略对wait 的调用,使得shell 和命令进程并发运行。
为了帮助读者进一步熟悉和掌握fork()和exec 的使用,我们再来看一个名为docommand的程序,这个程序仿真Linux 库调用system() ,它可以在程序中执行一个shell 命令。docommand 的主题是对fork()和exec 的调用。程序清单如下:
1 int docommand(char* command) 2 { 3 int pid; 4 switch(pid=fork()) 5 { 6 case -1: 7 return -1; 8 case 0: 9 execl("/bin/sh","sh","-c",command,NULL); 10 exit(127); 11 default: 12 wait(NULL); 13 } 14 return 0; 15 }
docommand 并没有通过exec 去直接执行指定的命令,而是通过exec 去执行shell(即/bin/sh),并由shell 再执行指定的命令。这是一种非常巧妙的方法,它使得docommand 能使用shell 提供的一系列特性(如文件名扩展等)。在引用shell 中使用的参数-c,表示从下一个参数中取得命令名,而不是从标准输入上取得。