Unix信号详解(Signal的信号说明)
signal信号机制是属于计算机异常处理机制中的一种。
signal信号属于一种异步处理异常的机制之一。
类似于我们平常在命令行上对于死循环的程序,按下ctrl-z暂时挂起,ctrl-c程序终止,这些挂起,终止信号都属于signal信号的一种,常见的几种signal信号如下图所示(供查询使用,平时并无用):
信号常见的术语:
1.发送信号:
先简单介绍一下进程组的概念,一个操作系统有很多进程所组成,有系统进程,应用进程。而这些进程又是以进程组来区分的。Shell里面又有前台进程组和后台进程组,前台进程组说明shell进程必须等待前台进程结束才可以读取并执行下一条指令,后台进程组顾名思义在后台进行。父进程与它的子进程处于同一进程组中,在shell上每次发送类似ctrl-c的信号都会发送到每个前台进程组中。
以下图片说明进程组的概念:
发送信号常用的两种方法;
通过/bin/kill程序发送信号:
/bin/kill -9 15213
发送信号给进程15213
/bin/kill -9 -15213
如果进程前面表示为负的,说明发送信号9给15213进程组的所有信号
还有一个同名函数也可以在程序中对指定进程发送信号:
#include<sys/type.h>
#include<signal.h>
int kill(pid_t pid, int sig);
从键盘发送信号
Unix用作业(job)这个抽象概念来表示对一个命令行求值而创建的进程。在任何时刻,至多只有一个前台进程和0个或多个后台进程,而ctrl-z或者ctrl-c则会向每一个前台进程发送信号。
通过alarm函数给自身进程发送信号
alarm的函数原型:
#include<unistd.h>
unsigned int alarm(unsigned int secs);
//返回:前一次闹钟剩余的秒数,若以前没有闹钟,则设置为0
该函数即是为了到了设置的固定时间发送一次信号消息
接受消息
接受消息的时候,通常会设置一个handler函数,表示自定义收到消息后的行为该如何
前面都是些对signal介绍,接下来到了重点和难点了呀!!!
信号处理问题
一个程序接受一个信号的时候似乎很好理解,且不会出现太大的问题,但是一旦一个进程接受了多个信号的时候,问题就出现了
以下是问题的说明:
该信号如果理解成现实生活中的信号,现实中的信号也永远是异步。例如,小张和小和正在说话,这是一个顺序执行的操作。如果,这时候上司(操作系统内核)突然说了声“小张,那个文件给我处理一下了”,这就是一个异步信号,小张在接受这个信号之后就会去处理这个文件,这个就是信号处理函数,如果上司说了“那个文件给我处理一下”,这时候另一个同事说“一起去吃饭”,这时候就有两个信号了,这时候小张就先去“处理文件”的信号,再去处理“一起去吃饭”的信号,这就是待处理信号被阻塞。如果上司一直说“那个文件给我处理一下”,“那个文件给我处理一下”,这就是相同类型的信号有很多个,这时候只有第一个有用,小张就去做第一个,这就是待处理信号不会排队等待的道理
- 待处理信号被阻塞:Unix信号函数会阻塞统一类型的待处理信号。就比如,一个程序接受了一个SIGCHLD的信号,那么在执行SIGCHLD的信号的时候,下一的SIGCHLD的信号就会被阻塞,成为待处理信号。
- 待处理信号不会排队等待。即针对同一类型的信号,只能有一个待处理信号。例如,一个进程接受了一个SIGCHLD的信号,在执行SIGCHLD的信号处理程序的时候,来了两个SIGCHLD信号,那么只有一个SIGCHLD会成为待处理信号。
- 系统调用可以被中断。像read,wait,accept这样的系统调用潜在地会阻塞进程一段时间,称为慢速系统调用。在某些系统中,当处理程序捕获到一个信号,被中断的系统调用在信号处理程序进行后不再返回,而是立即返回给用户一个错误条件,并将errno设置为EINTR.
接下来,用几个程序例子来说明这三个特性。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1<<16 /* max job ID */
#define MAXBUF 100
void handler1(int sig) {
pid_t pid;
if ((pid = waitpid(-1, NULL, 0)) < 0)
printf("waitpid error");
printf("Handler reaped child %d\n", (int)pid);
sleep(2);
return;
}
int main() {
int i, n;
char buf[MAXBUF];
if (signal(SIGCHLD, handler1) == SIG_ERR)
printf("signal error"); /* Parent creates children */
for (i = 0; i < 3; i++) {
if (fork() == 0) {
printf("Hello from child %d\n", (int)getpid());
sleep(1);
exit(0);
}
}
/* Parent waits for terminal input and then processes it */
if((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
printf("read");
printf("Parent processing input\n");
while (1)
;
exit(0);
}
输出结果为:
Hello from child 12575
Hello from child 12576
Hello from child 12577
Handler reaped child 12577
Handler reaped child 12576
<cr>
Parent processing input
可以从结果明显的看出只回收了两个孩子进程,剩下一个孩子进程变成僵尸进程
导致这个结果的原因是:等待信号不会排队.
父进程创建了三个子进程,当三个子进程结束的时候,三个进程成为僵尸进程,向父进程传递三个SIGCHLD信号,一个正在处理,一个待处理,因为同一个信号只有一个待处理信号,剩下一个信号就被抛弃,所以有一个子进程的SIGCHLD信号不被处理,一个子进程成为僵尸进程。
针对这个问题,写了一个改进版的signal1
如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1<<16 /* max job ID */
#define MAXBUF 100
void handler1(int sig) {
pid_t pid;
while((pid = waitpid(-1, NULL, 0)) > 0)
printf("Handler reaped child %d\n", (int)pid);
if(errno!=ECHILD)
printf("waitpid error");
sleep(2);
return;
}
int main() {
int i, n;
char buf[MAXBUF];
if (signal(SIGCHLD, handler1) == SIG_ERR)
printf("signal error"); /* Parent creates children */
for (i = 0; i < 3; i++) {
if (fork() == 0) {
printf("Hello from child %d\n", (int)getpid());
sleep(1);
exit(0);
}
}
/* Parent waits for terminal input and then processes it */
if((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
printf("read");
printf("Parent processing input\n");
while (1)
;
exit(0);
}
结果如下:
Hello from child 12648
Hello from child 12649
Hello from child 12650
Handler reaped child 12649
Handler reaped child 12648
Handler reaped child 12650
read:Interrupted system call
成功回收了三个子进程
重点的改动在于这几行代码:
while((pid = waitpid(-1, NULL, 0)) > 0)
printf("Handler reaped child %d\n", (int)pid);
if(errno!=ECHILD)
printf("waitpid error");
用while循环来尽可能个获取未被回收的进程
但是为什么会出现read:Interrupted system call
原因在于:系统调用可以被中断,但是返回后不能继续,即不能自动重启
它们会返回一个错误条件
再对handler进行改进
改进的版本如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1<<16 /* max job ID */
#define MAXBUF 100
void handler1(int sig) {
pid_t pid;
while((pid = waitpid(-1, NULL, 0)) > 0)
printf("Handler reaped child %d\n", (int)pid);
if(errno!=ECHILD)
printf("waitpid error");
sleep(2);
return;
}
int main() {
int i, n;
char buf[MAXBUF];
if (signal(SIGCHLD, handler1) == SIG_ERR)
printf("signal error"); /* Parent creates children */
for (i = 0; i < 3; i++) {
if (fork() == 0) {
printf("Hello from child %d\n", (int)getpid());
sleep(1);
exit(0);
}
}
/* Manually restart the read call if it is interrupted */
while ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
{
if (errno != EINTR)
unix_error"read error");
}
printf("Parent processing input\n");
while (1)
;
exit(0);
}
重点在与这一句
/* Manually restart the read call if it is interrupted */
while ((n = read(STDIN_FILENO, buf, sizeof(buf))) < 0)
{
if (errno != EINTR)
unix_error"read error");
}
用while尽可能的执行read,实现对read系统调用进行自动重启。
说明singal存在的竞争问题
以下是存在竞争问题的一个例子
这个看似正常的程序,实质上存在竞争问题
在父进程fork出子进程之后,子进程执行/bin/date程序,如果此时内核调度先执行的是子进程,那么如果子进程执行完,向父进程发出SIGCHLD信号,并执行handler,handler删除了子进程的任务,此时在调度进父进程,执行addjob,此时,就会添加一个根本不存在的进程。发生了竞争错误
解决此问题的方法是:在父进程执行addjob代码之前,把SIGCHLD阻塞起来,当addjob之后再解除阻塞,当然子进程也会继承父进程的阻塞信号集,此时需要在子进程解除阻塞信号,让子进程正常执行。
表现为代码,即是如下代码:
接下来放一个比较如何验证竞争问题的Fork代码,实质上就是在Fork之后,分别有50%的几率执行父进程或者子进程
以下是代码:
上一篇: Linux —— 进程信号
下一篇: Linux下信号详解