目录
3.1 进程的控制操作
3.1.1 进程的终止
系统调用 exit()实现进程的终止。exit()在 Linux 系统函数库 stdlib.h 中的函数声明如下:
void exit(int status);
exit()只有一个参数 status,称作进程的退出状态,父进程可以使用它的低 8 位。exit() 的返回值通常用于指出进程所完成任务的成败。如果成功,则返回 0;如果出错,则返回非 0 值。
exit()除了停止进程的运行外,它还有一些其它作用,其中最重要的是,它将关闭所有已打开的文件。如果父进程因执行了 wait()调用而处于睡眠状态,那么子进程执行 exit()会重新启动父进程运行。另外,exit()还将完成一些系统内部的清除工作,例如缓冲区的清除工作等。
除了使用 exit()来终止进程外,当进程运行完其程序到达 main()函数末时,进程会自动终止。当进程在 main()函数内执行一个 return 语句时,它也会终止。
在 Linux 中还有一个用于终止进程的系统调用_exit()。它在 Linux 系统函数库 unistd.h
中被声明:
void _exit(int status)
其使用方法与 exit()完全相同,但是它执行终止进程的动作而没有系统内部的清除工作。因此,只有那些对系统内部了解比较深的程序员才使用它。
3.1.2 进程的同步
系统调用 wait()是实现进程同步的简单手段,它在 Linux 系统函数库 sys/wait.h 中的函数声明如下:
pid_t wait(int *status)
我们在前面已经看到了,当子进程执行时,wait()可以暂停父进程的执行,使起等待。一旦子进程执行完,等待的父进程就会重新执行。如果有多个子进程在执行,那么父进程中的 wait()在第一个子进程结束时返回,恢复父进程执行。
通常情况下,父进程调用 fork()后要调用 wait()。例如:
pid=fork();
if (!pid){
/* 子进程 */
} else {
/* 父进程 */ wait(NULL);
}
当希望子进程通过 exec 运行一个完全不同的进程时,就要进程fork()和wait()的联用。wait()的返回值通常是结束的那个子进程的进程标识符。如果 wait()返回-1,表示没有子进程结束,这时 errno 中含有出错代码 ECHILD。
wait()有一个参数,它可以是一个指向整型数的指针,也可以是一个 null 指针。如果参数用了 null 指针,wait 就忽略它。如果参数是一个有效的指针,那么 wait 返回时,该指针就指向子进程退出时的状态信息。通常,该信息就是子进程通过 exit 传送出来的出口信息。下面的程序 status 就给出了这种情况下,wait 的使用方法。
#include <stdio.h> #include <unistd.h> #include <sys/wait.h>
main()
{
int pid,status,exit_status;
if ((pid=fork()) <0)
{
perror("fork failed"); exit(1);
}
if (!pid)
/* 子进程 */ sleep(4);
exit(5); /* 使用非零值退出,以便主进程观察 */
}
/* 父进程 */
if (wait(&status) <0) { perror("wait failed"); exit(1);
}
/* 将 status 与 0xFF(255)与来测试低 8 位 */ if (status & 0xFF)
printf("Somne low-roderbits not zero\n"); else {
exit_status=status >> 8; exit_status &=0xFF;
printf("Exit status from %d was %d\n", pid,exit_status);
}
exit(0);
}
虽然这个过程看起来有一点复杂,但是其含义十分清除:通过 exit 返回给父进程之值存放在 exit_status 的低位中,为了使其有意义,exit_status 的高 8 位必须为 0(注意,在整型量中,低 8 位在前,高 8 位在后)。因此从 wait()返回后,就可以用按位与操作进行测试, 如果它们不为 0,表示该子进程是被另一个进程用一种称为信号的通信机构停止的,而不是通过 exit()结束的。
3.1.3 进程终止的特殊情况
我们在前面讨论了用 wait()和 exit()联用来等待子进程终止的情况。但是,还有两种进程终止情况值得讨论。这两种情况为:
1. 子进程终止时,父进程并不正在执行 wait()调用。
2. 当子进程尚未终止时,父进程却终止了。
在第一种情况中,要终止的进程就处于一种过渡状态(称为 zombie ),处于这种状态的进程不使用任何内核资源,但是要占用内核中的进程处理表那的一项。当其父进程执行wait()等待子进程时,它会进入睡眠状态,然后把这种处于过渡状态的进程从系统内删除, 父进程仍将能得到该子进程的结束状态。
在第二种情况中,一般允许父进程结束,并把它的子进程(包括处于过渡状态的进程)
交归系统的初始化进程所属。
3.1.4 进程控制的实例
在这一部分,我们将利用前面介绍的进程控制的知识,来构造一个简单的命令处理程序,取名为 smallsh。这样作有两个目的:第一,可以巩固和发展我们在这一章中介绍的概念;第二,它展示了标准的 Linux 系统程序也没 有什么特别的东西。特别是,它表明了 shell 也只是一个在用户注册时调用的普通程序。
smallsh 的基本功能是:它能在前台或后台接收命令并执行它们。它还能处理由若干个命令组成的命令行。它还具有文件名扩展和 I/O 重定向等功能。
smallsh 的基本逻辑如下:
while (EOF not typed) {
从用户终端取得命令行执行命令行
}
我们把取命令行内容用一个函数来完成,并取名为 userin。userin 能显示提示符,然后等待用户从键盘输入一命令行信息。它接收到的输入内容应存入程序的一个缓冲区中。
我们可以忽略到一些初始化工作,但是,userin 的基本步骤是:首先显示提示符,提示符的具体内容由用户通过参数传送给函数;然后每次从键盘读一个字符,当遇到换行符或文件结束符(用 EOF 符号表示)时,就结束。
我们用的基本输入例程是 getchar,它实际上是标准 I/O 库中的一个宏(macro),它从程序的标准输入读入一个字符,userin 把每个读入的字符都存入字符型数组