1.6 程序和进程
1. 程序
程序(program)是存放在磁盘上、处于某个目录中的一个可执行文件。使用6个exec函数中的一个由内核将程序读入存储器,并使其执行。8.10节将说明这些exec函数。
2. 进程和进程ID
程序的执行实例被称为进程(process)。本书的每一页几乎都会使用这一术语。某些操作系统用任务(task)表示正被执行的程序。
UNIX系统确保每个进程都有一个唯一的数字标识符,称为进程ID(process ID)。进程ID 总是一非负整数。
实例
程序清单1-4用于打印进程ID。
如果编译该程序,并将其结果送入a.out文件,然后执行它,则有:

此程序运行时,它调用函数getpid得到其进程ID。
3. 进程控制
有三个用于进程控制的主要函数:fork、exec和waitpid。(exec函数有六种变体,但经常把它们统称为exec函数。)
实例
UNIX系统的进程控制功能可以用一个较简单的程序(见程序清单1-5)说明。该程序从标准输入读命令,然后执行这些命令。它是一个类shell程序的简化实现。在这个30行的程序中,有很多功能需要思考:
• 用标准I/O函数fgets从标准输入一次读一行,当键入文件结束字符(通常是Ctrl+D)作为行的第1个字符时,fgets返回一个null指针,于是循环终止,进程也就终止。第18章将说明所有特殊的终端字符(文件结束、退格字符、整行擦除等等),以及如何改变它们。
• 因为fgets返回的每一行都以换行符终止,后随一个null字节,故用标准C函数strlen 计算此字符串的长度,然后用一个null字节替换换行符。这样做是因为execlp函数要求参数以null而不是以换行符结束。
• 调用fork创建一个新进程。新进程是调用进程的复制品,我们称调用进程为父进程,新创建的进程为子进程。fork向父进程返回新子进程的进程ID(非负),对子进程则返回0。因为fork创建一新进程,所以说它被调用一次(由父进程),但返回两次(分别在父进程及子进程中)。
• 在子进程中,调用execlp以执行从标准输入读入的命令。这就用新的程序文件替换了子进程原先执行的程序文件。fork和跟随其后的exec两者的组合是某些操作系统所称的产生(spawn)一个新进程。在UNIX系统中,这两个部分相互分隔,构成两个函数。第8 章将对这些函数作更多说明。
• 子进程调用execlp执行新程序文件,而父进程希望等待子进程终止,这一要求由调用11 waitpid实现,其参数指定要等待的进程(在这里,pid参数是子进程ID)。waitpid 12 函数返回子进程的终止状态(status变量)。在此简单程序中,没有使用该值。如果需
~
要,可以用此值准确地判定子进程是因何终止的。
• 该程序的最主要限制是不能向所执行的命令传递参数,例如不能指定要列表的目录名,只能对工作目录执行ls命令。为了传递参数,先要分析输入行,然后用某种约定把参数分开(很可能使用空格或制表符),然后将分隔后的各个参数传递给execlp函数。尽管如此,此程序仍可用来说明UNIX系统的进程控制功能。
如果运行此程序,则得到下列结果。注意,该程序使用了一个不同的提示符(%),以区别于shell的提示符。

^D表示一个控制字符。控制字符是特殊字符,其形成方法是:在键盘上按下控制键—通常被标记为Control或Ctrl,同时按另一个键。Control-D或^D是默认的文件结束符。在第18章中讨论终端时,会介绍更多的控制字符。
4. 线程和线程ID
通常,一个进程只有一个控制线程(thread),同一时刻只执行一组机器指令。对于某些问题,如果不同部分各使用一个控制线程,那么整个问题解决起来就容易得多。另外,多个控制线程也能充分利用多处理器系统的并行性。
在一个进程内的所有线程共享同一地址空间、文件描述符、栈以及与进程相关的属性。因为它们能访问同一存储区,所以各线程在访问共享数据时需要采取同步措施以避免不一致性。
与进程相同,线程也用ID标识。但是,线程ID只在它所属进程内起作用。一个进程中的线程ID在另一个进程中并无意义。当在一进程中对多个线程进行操纵时,我们用线程ID引用相应的线程。
控制线程的函数与控制进程的函数类似,但另有一套。在进程模型建立很久之后,线程模型才被引入到UNIX系统中,这两个模型之间存在复杂的相互作用,在第12章中,我们会对此有所说明。







