1.Sending signals with the kill() function
#define N 5
int main()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) { //子进程陷入死循环
/* Child: Infinite Loop */
while(1);
}
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT); //将终止信号发送给对应PID为pid[i]的进程
}
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status); //等待所有的进程
if (WIFEXITED(child_status)) //如果子进程是正常退出则输出子进程的PID及其退出状态
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);//否则子进程是非正常退出的
}
return 0;
}
进程可通过调用kill函数发送给信号给其他进程,形式为int kill(pid_t pid,int sig); 如果pid大于0,那么kill函数发送信号号码sig给进程pid。如果pid等于0,那么kill函数发送信号sig给调用进程组的每个进程,包括调用进程自己。如果pid小于0,kill发送信号sig给进程组|pid|(pid的绝对值)中的每个进程。程序中的SIGINT信号代表的默认行为是终止。
程序中用kill函数发送终止信号给对应的5个子进程,因为是接收SIGINT信号才退出的,所以是非正常退出。
运行结果如下:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
2.Simple signal handler example
#define N 5
void int_handler(int sig) //信号处理程序,显示接收该信号的进程ID及接收信号的号码
{
printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
exit(0);
}
int main()
{
pid_t pid[N];
int i;
int child_status;
signal(SIGINT, int_handler);
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
/* Child: Infinite Loop */
while(1);
}
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
return 0;
}
这个程序可以帮助我们理解发送信号和接收信号。首先要先了解每个信号都会有一个对应的唯一的号码,几种常见的信号如下:SIGINT--2、SIGKILL--9、SIGALRM--14、SIGCHLD--17。当进程在前台运行的时候,你键入ctrl+c,那么内核会发送一个SIGINT信号给这个前台的每一个进程;一个进程可以通过向另一个进程发送一个SIGKILL信号强行终止它;当一个进程终止或停止时,内核会发送一个SIGCHLD信号给父进程;当alarm函数定时器计时结束后,内核会发送SIGALRM终止对应的进程。
当目的进程被内核强迫以某种方式对信号的发送做出反应时,它就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。
sighandler_t signal(int signum, sighandler_t handler);第一个参数代表信号的名称,接收信号后要执行的程序。
程序中子进程一旦接收到了信号就去执行handler函数,执行handler函数后是以状态0正常退出。
运行结果如下:
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
将上面的信号处理程序换为下列代码:
int ccount = 0;
void child_handler(int sig)
{
int child_status;
pid_t pid = wait(&child_status);
ccount--;
printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */
fflush(stdout); /* Unsafe */
}
运行结果为:
在fork函数的解析(一)中提到过,当wait函数返回值为-1的时候表示程序出错,这个程序一直不会出现命令行,即使按下Ctrl+c键,这里ctrl+c键其实还是SIGINT信号。按Ctrl+z键将其挂起,因为wait函数都等不到任何一个子进程的退出,所以会出错返回-1。
3.SIGCHLD handler that reaps one terminated child
#define N 5
int ccount = 0;
void child_handler(int sig)
{
int child_status;
pid_t pid = wait(&child_status);
ccount--;
printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */
fflush(stdout); /* Unsafe */
}
int main()
{
pid_t pid[N];
int i;
ccount = N;
signal(SIGCHLD, child_handler);
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) {
sleep(1);
exit(0); /* Child: Exit */
}
}
while (ccount > 0)
;
}
此处先解释一下sleep函数--unsigned int sleep(unsigned int secs) 参数为要休眠的秒数,它的作用是将一个进程挂起一段指定的时间。如果请求的时间量到了,sleep会返回0,若它被一个信号中断而过早地返回,它会返回还剩下要休眠的秒数。
SIGCHLD信号相应的事件是一个子进程终止或者停止,故当子进程正常退出时,转而就会去执行child_handler。
运行结果如下:
这里输出的结果断断续续地,原因如下:
sleep函数能被信号中断。当执行完中断程序后就直接执行下一条语句,导致sleep的休眠时间被打乱,这也是用sleep函数经常出现的问题,解决方法可看下段代码。(水平有限,解释的可能有些模糊)
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
4.Using a handler that reaps multiple children
#define N 5
int ccount = 0;
void child_handler2(int sig)
{
int child_status;
pid_t pid;
while ((pid = wait(&child_status)) > 0) {
ccount--;
printf("Received signal %d from process %d\n", sig, pid); /* Unsafe */
fflush(stdout); /* Unsafe */
}
}
int main()
{
pid_t pid[N];
int i;
ccount = N;
signal(SIGCHLD, child_handler2);
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
sleep(1);
exit(0); /* Child: Exit */
}
while (ccount > 0) {
pause();
}
}
运行结果如下:
这个程序与上个程序的区别在于加上了一个pause函数,该函数地作用是让调用函数休眠,直到该进程遇到一个信号。
用了pause函数,解决了上面的问题,所以程序运行后能完整地输出五条语句。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
5.Demonstration of using /bin/kill program
int main()
{
if (fork() == 0) {
printf("Child1: pid=%d pgrp=%d\n",
getpid(), getpgrp());
if (fork() == 0)
printf("Child2: pid=%d pgrp=%d\n",
getpid(), getpgrp());
while(1); //父进程和子进程最终都会进入死循环
}
}
getpid这个函数在上一篇文章中介绍过,这里不再重复。这边有个新的函数getpgrp()它的作用是返回当前进程的进程组ID,每个进程只属于一个进程组,进程组是由一个正整数的进程组ID来标识的。默认地,一个子进程和它的父进程同属一个进程组。一个进程可以通过set-pgid函数改变自己或其他进程的进程组:int setpgid(pid_t pid, pid_t pgid); 将pid的进程组改为pgid。若pid是0,就使用当前进程的PID,若pgid是0,那么就用pid指定的进程的PID作为进程组ID。
运行结果如下:
这里还有一个知识点 kill -9 -3104是指发送号码为9的信号即SIGKILL给进程组号为3104的所有进程,也就是让进程组号为3104的所有进程“死掉”。执行之后查看状态可知child1和child2都终止了。 kill -9 -3104要区别于 kill -9 3104,后者是“杀死”一个进程号为3104的进程。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------
6.Demonstration of using ctrl-c and ctrl-z
int main()
{
if (fork() == 0) {
printf("Child: pid=%d pgrp=%d\n",
getpid(), getpgrp());
}
else {
printf("Parent: pid=%d pgrp=%d\n",
getpid(), getpgrp());
}
while(1);
}
进行如下操作(ctrl+z)三次并用ps w(采用宽阔的格式来查看程序状态):
用ctrl+z后只是将程序挂起在后台运行,我们还是能查看到它的状态。
用ctrl+c后用ps w查看它的状态:
ctrl+z是把进程杀死,很明显用ps w已经看不到相应进程的状态了