UNIX 进程揭秘( 二 )


p = fork();
if (p == 0) {
printf("In child process, pid=%d, ppid=%dn",
getpid(), getppid());
} else {
printf("In parent, pid=%d, fork returned=%dn",
getpid(), p);
}
}
sunbox$ gcc fork2.c -o fork2
sunbox$ ./fork2
Original program, pid=767
In child process, pid=768, ppid=767
In parent, pid=767, fork returned=768
清单 3 在每个步骤打印出 PID,并且该代码检查从 fork 返回的值来确定哪个进程是父进程,哪个进程是子进程 。对所打印的 PID 进行比较,可以看到原始进程是父进程 (PID 767),并且子进程 (PID 768) 知道其父进程是谁 。请注意子进程如何通过 getppid 来知道其父进程以及父进程如何使用 fork 来定位其子进程 。
现在您已经了解了复制某个进程的方法,下面让我们研究如何运行一个不同的进程 。fork 只是进程机制中的一半 。exec 系列系统调用运行实际的程序 。
使用 exec 系列系统调用
exec 的工作是将当前进程替换为一个新进程 。请注意“替换这个措词的含义 。在您调用 exec 以后,当前进程就消失了,新进程就启动了 。如果希望创建一个单独的进程,您必须首先运行 fork,然后在子进程中执行 (exec) 新的二进制文件 。清单 4 显示了这样一种情况 。
清单 4. 通过将 fork 与 exec 配合使用来运行不同的程序
sunbox$ cat exec1.c
#include
#include
int main (void) {
/* Define a null terminated array of the command to run
followed by any parameters, in this case none */
char *arg[] = { "/usr/bin/ls", 0 };
/* fork, and exec within child process */
if (fork() == 0) {
printf("In child process:n");
execv(arg[0], arg);
printf("I will never be calledn");
}
printf("Execution continues in parent processn");
}
sunbox$ gcc exec1.c -o exec1
sunbox$ ./exec1
In child process:
fork1.c exec1fork2exec1.c fork1
fork2.c 
Execution continues in parent process
清单 4 中的代码首先定义一个数组,其中第一个元素是要执行的二进制文件的路径,其余元素充当命令行参数 。根据手册页的描述,该数组以 Null 结尾 。在从 fork 系统调用返回以后,将指示子进程执行 (execv) 新的二进制文件 。
execv 调用首先取得一个指向要运行的二进制文件名称的指针,然后取得一个指向您前面声明的参数数组的指针 。该数组的第一个元素实际上是二进制文件的名称,因此参数实际上是从第二个元素开始的 。请注意,该子进程一直没有从 execv 调用返回 。这表明正在运行的进程已被新进程所替换 。
还存在其他执行 (exec) 某个进程的系统调用,它们的区别在于接受参数的方式和是否需要传递环境变量 。execv(2) 是替换当前映像的较简单方法之一,因为它不需要关于环境的信息,并且它使用以 Null 结尾的数组 。其他选项包括 execl(2)(它单独接受各个参数)或 execvp(2)(它也接受一个以 Null 结尾的环境变量数组) 。使问题复杂化的是,并非所有操作系统都支持所有变体 。关于使用哪一种变体的决定取决于平台、编码风格和是否需要定义任何环境变量 。
调用 fork 时,打开的文件会发生什么情况呢?
当某个进程复制它自身时,内核生成所有打开的文件描述符的副本 。文件描述符是指向打开的文件或设备的整数,并用于执行读取和写入 。如果在调用 fork 前,某个程序已经打开了一个文件,如果两个进程都尝试执行读取或写入,会发生什么情况呢?一个进程会改写另一个进程中的数据吗?是否会读取该文件的两个副本?清单 5 对此进行了研究,它打开两个文件——一个文件用于读取,另一个文件用于写入——并让父进程和子进程同时执行读取和写入 。

推荐阅读