上面的停止程序模拟了一个多进程系统的优雅退出,在这个例子中,这个系统由一个父进程和一个子进程组成。这次模拟的工作流程如下:
- 父进程尝试去
fork 一个子进程。假如这个 fork 操作成功了,每个进程就执行它自己的代码:子进程就执行函数 child_code ,而父进程就执行函数 parent_code 。
- 子进程将会进入一个潜在的无限循环,在这个循环中子进程将睡眠一秒,然后打印一个信息,接着再次进入睡眠状态,以此循环往复。来自父进程的一个
SIGTERM 信号将引起子进程去执行一个信号处理回调函数 graceful 。这样这个信号就使得子进程可以跳出循环,然后进行子进程和父进程之间的优雅终止。在终止之前,进程将打印一个信息。
- 在
fork 一个子进程后,父进程将睡眠 5 秒,使得子进程可以执行一会儿;当然在这个模拟中,子进程大多数时间都在睡眠。然后父进程调用 SIGTERM 作为第二个参数的 kill 函数,等待子进程的终止,然后自己再终止。
下面是一次运行的输出:
% ./shutdown Parent sleeping for a time... Child just woke up, but going back to sleep. Child just woke up, but going back to sleep. Child just woke up, but going back to sleep. Child just woke up, but going back to sleep. Child confirming received signal: 15 ## SIGTERM is 15 Child about to terminate gracefully... Child terminating now... My child terminated, about to exit myself...
对于信号的处理,上面的示例使用了 sigaction 库函数(POSIX 推荐的用法)而不是传统的 signal 函数,signal 函数有移植性问题。下面是我们主要关心的代码片段:
-
假如对 fork 的调用成功了,父进程将执行 parent_code 函数,而子进程将执行 child_code 函数。在给子进程发送信号之前,父进程将会等待 5 秒:
puts("Parent sleeping for a time..."); sleep(5); if (-1 == kill(cpid, SIGTERM)) { ...sleepkillcpidSIGTERM...
假如 kill 调用成功了,父进程将在子进程终止时做等待,使得子进程不会变成一个僵尸进程。在等待完成后,父进程再退出。
-
child_code 函数首先调用 set_handler 然后进入它的可能永久睡眠的循环。下面是我们将要查看的 set_handler 函数:
void set_handler() { struct sigaction current; /* current setup */ sigemptyset(¤t.sa_mask); /* clear the signal set */ current.sa_flags = 0; /* for setting sa_handler, not sa_action */ current.sa_handler = graceful; /* specify a handler */ sigaction(SIGTERM, ¤t, NULL); /* register the handler */ }
上面代码的前三行在做相关的准备。第四个语句将为 graceful 设定为句柄,它将在调用 _exit 来停止之前打印一些信息。第 5 行和最后一行的语句将通过调用 sigaction 来向系统注册上面的句柄。sigaction 的第一个参数是 SIGTERM ,用作终止;第二个参数是当前的 sigaction 设定,而最后的参数(在这个例子中是 NULL )可被用来保存前面的 sigaction 设定,以备后面的可能使用。
使用信号来作为 IPC 的确是一个很轻量的方法,但确实值得尝试。通过信号来做 IPC 显然可以被归入 IPC 工具箱中。
这个系列的总结
在这个系列中,我们通过三篇有关 IPC 的文章,用示例代码介绍了如下机制:
- 共享文件
- 共享内存(通过信号量)
- 管道(命名和无名)
- 消息队列
- 套接字
- 信号
(编辑:ASP站长网)
|