UNIX 进程揭秘

分配给系统管理员的许多工作之一是确保用户的程序正确运行 。因为系统上存在其他并发运行的程序,所以此任务变得更加复杂 。由于种种原因,这些程序可能会失败、挂起或行为异常 。在构建更可靠的系统时,了解 Unix?环境如何创建、管理和销毁这些作业是至关重要的步骤 。
开发人员还必须积极了解内核如何管理进程,因为与系统的其他部分和睦相处的应用程序会占用更少的资源,并且不会频繁地给系统管理员带来麻烦 。由于导致僵死进程(将在稍后对其进行描述)而频繁重新启动的应用程序明显是不可取的 。对控制进程的 UNIX 系统调用的了解可以使开发人员编写能够在后台自动运行的软件,而不是需要一个始终保持在用户屏幕上的终端会话 。
管理这些程序的基本构件就是进程 。进程是赋予某个由操作系统执行的程序的名称 。如果您熟悉 ps 命令,则您应该熟悉进程清单,如清单 1 所示 。
清单 1. ps 命令的输出
sunbox#ps -ef
UIDPID PPIDCSTIME TTY TIME CMD
root 0 00 20:15:23 ?0:14 sched
root 1 00 20:15:24 ?0:00 /sbin/init
root 2 00 20:15:24 ?0:00 pageout
root 3 00 20:15:24 ?0:00 fsflush
 daemon240 10 20:16:37 ?0:00 /usr/lib/nfs/statd
...
前三列对这里的讨论非常重要 。第一列列出用于运行该进程的用户身份,第二列列出进程的 ID,第三列列出该进程的父进程 ID 。最后一列是进程的描述,通常是所运行的二进制文件的名称 。每个进程都被分配一个标识符,称为进程标识符(Process IdentifIEr,PID) 。进程还有父进程,在大多数情况下就是启动它的进程的 PID 。
父 PID (PPID) 的存在意味着这是一个由别的进程创建的进程 。最初创建进程的原始进程名为 init,它始终被赋予 PID 1 。init 是将在内核启动时启动的第一个实际进程 。启动系统的其余部分是 init 的工作 。init 和其他具有 PPID 0 的进程属于内核 。
使用 fork 系统调用
fork(2) 系统调用创建一个新进程 。清单 2 显示了一个简单 C 代码片段中使用的 fork 。
清单 2. 简单的 fork(2) 用法
sunbox$ cat fork1.c
#include
#include
int main (void) {
pid_t p; /* fork returns type pid_t */
p = fork();
printf("fork returned %dn", p);
}
sunbox$ gcc fork1.c -o fork1
sunbox$ ./fork1
fork returned 0
fork returned 698
fork1.c 中的代码不过就是发出 fork 调用,并通过一个 printf 调用来打印整数结果 。虽然只发出了一个调用,但是打印了两次输出 。这是因为在 fork 调用中创建了一个新进程 。现在有两个单独的进程在从该调用返回结果 。这通常被描述为“调用一次,返回两次 。
fork 返回的值非常有趣 。其中一个返回 0;另一个返回一个非零值 。获得 0 的进程称为子进程,非零结果属于原始进程,即父进程 。您将使用返回值来确定哪个是父进程,哪个是子进程 。由于两个进程都在同一空间中继续运行,唯一有实际意义的区别是从 fork 返回的值 。
0 和非零返回值的基本原理在于,子进程始终可以通过 getppid(2) 调用来找出其父进程是谁,但是父进程要找出它的所有子进程却很困难 。因此,要告诉父进程关于其新的子进程的信息,而子进程可在需要时查找其父进程 。
考虑到 fork 的返回值,现在该代码可以检查确定它是父进程还是子进程,并进行相应的操作 。清单 3 显示了一个基于 fork 的结果来打印不同输出的程序 。
清单 3. 更完整的 fork 用法示例
sunbox$ cat fork2.c
#include
#include
int main (void) {
pid_t p;
printf("Original program, pid=%dn", getpid());

推荐阅读