进程管理实验
实验目的
本次实验主要进行了进程有关的实验,利用 Linux 下的系统 API——fork()
函数来在 Linux 下创建一个新的进程,运行一个小程序来观察进程的争用资源和互相通信的现象。
实验步骤
进程的软中断通信
要求
使用系统调用 fork()
创建两个子进程,再用系统调用 signal()
让父进程捕捉键盘上来的中断信号(即按 Delete 键),当父进程接受到这两个软中断的其中某一个后,父进程用系统调用 kill()
向两个子进程分别发送整数值为 16 和 17 软中断信号,子进程获得对应软中断信号后,分别输出下列信息后终止:
1 | Child process 1 is killed by parent!! |
父进程调用 wait()
函数等待两个子进程终止后,输出以下信息后终止:
1 | Parent process is killed!! |
1 |
|
进程的管道通信
要求
使用系统调用 pipe()
建立一条管道线,两个子进程分别向管道写一句话:
1 | Child process 1 is sending a message! |
而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。父进程先接收子进程 P1 发来的消息,然后再接收子进程 P2 发来的消息。
1 |
|
实验关键里程碑数据与结果
进程的软中断通信
前面的运行结果中,虽然 pid1
总是小于 pid2
,但是两个进程结束的顺序总是不一样的。我们来把程序后台运行一下,观察程序中所有的 pid
值和行为。
我们运行了 ps -aux
命令来查看系统中的后台进程,在最下方找到了 process
进程,其父进程 pid
为 4473,而两个子进程 pid
分别为 4475 和 4476。
之所以两个进程结束的顺序不同,是因为 kill
发出的信号没有被及时处理或可能被封锁。虽然调用 kill
的顺序是先 1 后 2,但是发送的都是软中断信号,它们都需要等待所有相关资源的释放。而释放的顺序是不确定的,由于存在资源争夺,信号接受的先后也是不确定的。
观察它们在后台表现的程序名发现,它们都是由中括号括起的,后面有一个 defunct
标志,同时又显示为 "Z" 状态,表明此进程为僵尸进程,直接 kill
其 pid
将不会奏效。需要对其父进程 pid
进行 kill
操作,才可以正确结束进程。
进程的管道通信
仍然将程序置于后台运行,观察后台 pid
和行为。
以上后台输出表明,在管道通信时,实质上启动了三个 pipe
进程,其中一个是父进程,它开启了下面的两个子进程,并监听管道中子进程传达的消息。在这个过程中,进程均为 "S" 状态,为休眠状态。
那为什么每次运行程序时,子进程发送信息的先后总是不同呢?这其中应该也存在资源的争用问题。虽然 pid1
的创建一定早于 pid2
,但是调用 lockf
的先后顺序是不一定的,如果子进程 1 率先执行到 lockf(fd[1],1,0)
,那么它将先阻塞管道,把信息传入管道中,供父进程读出并打印在终端中。子进程 2 此时即使创建成功,有 pid
号,但是因为管道被阻塞,无法进入管道,故阻塞在它的 lockf(fd[1],1,0)
语句中,等到子进程 1 释放管道时,才可进入管道。反之相似。
实验难点与收获
- 这次的实验步骤不是很多,主要难点在于编程和对进程资源争用的理解上。
- 掌握了 Linux 下进程管理相关的 C 语言 API。
- 对于进程的并发执行有了更深层次的理解。
实验思考
子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。那么会不会因为父进程太忙来不及 wait
子进程,或者说不知道子进程什么时候结束,而丢失子进程结束时的状态信息呢?
查阅资料得知:“不会。因为 Linux 提供了一种机制可以保证,只要父进程想知道子进程结束时的状态信息,就可以得到。这种机制就是:当子进程走完了自己的生命周期后,它会执行 exit()
系统调用,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息 (包括进程号、退出码、退出状态、运行时间等),这些数据会一直保留到系统将它传递给它的父进程为止,直到父进程通过 wait
/ waitpid
来取时才释放。
也就是说,当一个进程死亡时,它并不是完全的消失了。进程终止,它不再运行,但是还有一些残留的数据等待父进程收回。当父进程 fork()
一个子进程后,它必须用 wait()
(或者 waitpid()
) 等待子进程退出。正是这个 wait()
动作来让子进程的残留数据消失。”
根据这个资料,我想到了一个问题:假如进程通信代码中去掉父进程的 wait
调用,fork
出来的两个子进程还能正确结束吗?
仍然正确地结束了两个进程。出现这样的现象可能是因为 Linux 系统中有一些特殊的机制来保证 “僵尸进程” 被正确地接管 (具体来说,由 init 进程来接管,其 pid 为 1)。
Windows 对应的 API
对应于 Linux 的 fork()
API
1 | BOOL CreateProcess( |
对应于 Linux 的 waitpid()
1 | DWORD WaitForSingleObject( |
对应于 exit()
1 | void ExitProcess(UINT uExitCode); |
Windows 也使用 signal.h
并也有 signal
函数作为信号控制函数。
1 | void __cdecl *signal(int sig, int (*func)(int, int)) |
有关进程终止,Windows 使用 TerminateProcess
函数
1 | BOOL TerminateProcess( |