Linux 信号
https://blog.csdn.net/w903414/article/details/109802539
信号
产生信号的情况:
信号的3种处理方式:
系统默认动作“终止+core”表示在进程当前工作目录的core文件中复制了该进程的内存映像。大多数UNIX系统调试程序都使用core文件检查进程终止时的状态。
信号名 |
信号值 |
默认处 理动作 |
发出信号的原因 |
SIGHUP |
1 |
A |
终端挂起或者控制进程终止 |
SIGINT |
2 |
A |
键盘中断Ctrl+c |
SIGQUIT |
3 |
C |
键盘的退出键被按下 |
SIGILL |
4 |
C |
非法指令 |
SIGABRT |
6 |
C |
由abort(3)发出的退出指令 |
SIGFPE |
8 |
C |
浮点异常 |
SIGKILL |
9 |
AEF |
采用kill -9 进程编号 强制杀死程序。 |
SIGSEGV |
11 |
C |
无效的内存引用 |
SIGPIPE |
13 |
A |
管道破裂:写一个没有读端口的管道 |
SIGALRM |
14 |
A |
由alarm(2)发出的信号 |
SIGTERM |
15 |
A |
采用“kill 进程编号”或“killall 程序名”通知程序。 |
SIGUSR1 |
30,10,16 |
A |
用户自定义信号1 |
SIGUSR2 |
31,12,17 |
A |
用户自定义信号2 |
SIGCHLD |
20,17,18 |
B |
子进程结束信号 |
SIGCONT |
19,18,25 |
进程继续(曾被停止的进程) |
|
SIGSTOP |
17,19,23 |
DEF |
终止进程 |
SIGTSTP |
18,20,24 |
D |
控制终端(tty)上按下停止键 |
SIGTTIN |
21,21,26 |
D |
后台进程企图从控制终端读 |
SIGTTOU |
22,22,27 |
D |
后台进程企图从控制终端写 |
处理动作一项中的字母含义如下
- A 缺省的动作是终止进程。
- B 缺省的动作是忽略此信号,将该信号丢弃,不做处理。
- C 缺省的动作是终止进程并进行内核映像转储(core dump),内核映像转储是指将进程数据在内存的映像和进程在内核结构中的部分内容以一定格式转储到文件系统,并且进程退出执行,这样做的好处是为程序员 提供了方便,使得他们可以得到进程当时执行时的数据值,允许他们确定转储的原因,并且可以调试他们的程序。
- D 缺省的动作是停止进程,进入停止状况以后还能重新进行下去。
- E 信号不能被捕获。
- F 信号不能被忽略。
不可靠信号(1~32)和可靠信号(34~64)
不可靠信号有以下问题:
- 存在信号丢失问题。进程收到信号不做排队处理,相同信号多次到来会合并成一个。
- (早期,现在内核已经不再如此)每次处理完信号后,恢复成默认处理函数。
信号处理函数被中断
当一个信号到达后,调用处理函数,如果这时候有其他信号发生,会中断之前的处理函数,等新的信号处理函数执行完成后再继续执行之前的处理函数。使用sigaction可以在执行中断处理函数同时设置屏蔽字。
但是,同一个信号会排队阻塞,而不是中断同类信号的处理函数。
信号的阻塞
如果不希望在当前处理函数,也不希望忽略该信号,而是延时一段时间再处理这个信号,这种情况可以通过阻塞信号实现。
被阻塞的信号不会影响进程的行为,信号只是暂时被阻止传递。
实际执行信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞(Block)某个信号。被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行递达的动作。注意,阻塞和忽略是不同的,只要信号被阻塞就不会递达,而忽略是、在递达之后可选的一种处理动作。
// 获取当前进程未决信号,通过set参数传递。成功返回0,失败返回-1。
int sigpending(sigset_t *set);
详细介绍
信号的详细介绍:
处理信号
使用 sigaction 代替 signal
事件发生时为进程产生一个信号,内核通常在进程表中以某种形式设置一个标志。当对信号采取了这种动作,称为向进程传递了一个信号。信号产生和递送之间的时间间隔内称信号是未决的。
进程可以选用“阻塞信号递送”。如果为进程产生了一个阻塞信号,而且对该信号的动作是系统默认动作或捕捉该信号,则此信号保持未决状态,直到该进程对此信号解除阻塞,或将动作改为忽略。内核在递送一个原来被阻塞的信号给进程时(而不是在产生此信号时),才决定对它的处理方式。于是进程在信号递送给它之前仍可以改变对该信号的动作。进程调用sigpending函数来判定哪些信号是设置为阻塞并处于未决状态的。
发送信号 kill raise
raise(signo)
等于 kill(getpid(), signo)
信号集
表示多个信号的数据类型。因数量太多,不能用整形量的一位或一个整形量表示信号。使用sigset_t表示信号集。
初始化时使用sigemptyset或sigfillset将set设为空或所有信号集合。
sigprocmask 信号阻塞
仅为单线程定义,多线程中信号屏蔽使用另一个函数
pthread_sigmask
一个进程的信号屏蔽字规定了当前阻塞而不能传递给该进程的信号集。调用sigprocmask可以检测修改信号屏蔽字。
how的可选值:
SIG_BLOCK是或操作,SIG_UNBLOCK是异或,SIG_SETMASK是赋值
sigpending
返回一信号集,对于调用进程,其中各信号是阻塞不能递送的,因而也一定是当前未决的。该信号集通过set参数返回。
在sleep休眠期间产生了某信号,那么此时该信号是未决的,但是不再受阻塞。
sigaction
检查、修改与指定信号关联的处理动作。用于取代sinal函数
当sa_handler字段包含一个信号捕捉函数的地址(不是SIG_IGN或SIG_DFL),则sa_mask字段说明了一个信号集,调用信号捕捉函数前这各信号集要加到进程的信号屏蔽字中,当信号捕捉函数返回时再恢复屏蔽字。这样调用信号处理函数时可以阻塞某些信号。某种信号被阻塞时,如果它发生了多次,那么这种信号解除阻塞后,其信号处理函数通常只会被调用一次。
sa_flags 可选项:
SA_RESTART
恢复中断的系统调用,很有用
再sigaction结构中使用了 SA_SIGINFO
标志时,使用 sa_sigaction
字段的替代处理程序。sa_sigaction
与 sa_handler
只能选用一个。sa_sigaction
的特点是能够传递数据。
sigsetjmp siglongjmp
用于非局部返回的setjmp和longjmp函数,再信号处理函数中返回时会导致屏蔽正在被处理的信号。
当捕捉到一个信号后,进入信号捕捉函数,此时当前信号被自动加到进程的信号屏蔽字中,这阻止了后来产生的这种信号中断信号处理程序。如果使用longjmp跳出信号处理函数,信号屏蔽字不会被保存和恢复。
sigsuspend sigpending
sigpending(sigset_t *set))
获得当前已递送到进程,却被阻塞的所有信号,在set指向的信号集中返回结果。sigsuspend(const sigset_t *mask))
用于在接收到某个信号之前, 临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。该系统调用始终返回-1,并将errno设置为EINTR。
在一个原子操作中先恢复屏蔽字,然后使进程休眠。
abort
使程序异常终止。将SIGABRT信号发送给调用进程(进程不应忽略此信号)。用于在进程终止前由其执行所需的清理操作。
作业控制信号
竞争状态
问题:
本意是终端某个阻塞的recvform,但时钟信号可能是在运行到printf等时发出,这样无法跳出循环
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) {
int n;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
// 设置广播
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
// 超时时直接返回,不做任何处理
Signal(SIGALRM, [](int signo) { return; });
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
alarm(5); // 5秒超时时间
for (;;) {
len = servlen;
n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
if (n < 0) {
if (errno == EINTR) // 超时退出循环
break;
else
err_sys("recvfrom error");
} else {
recvline[n] = 0;
printf("from %s: %s", Sock_ntop_host(preply_addr, len), recvline);
}
}
}
free(preply_addr);
}
- (错误)在 recvfrom 前后指定阻塞信号
解阻塞、目的操作、阻塞信号是三个不同操作
如果SIGALRM信号在 recvfrom 和阻塞信号之间递交,之后无法退出循环
可以增加一个全局变量,在信号处理函数中修改变量,在进行recvfrom前判断。但如果信号在判断成功后、recvfrom调用前递交,依旧会出错 pselect
其中最后一个参数指定阻塞时信号掩码,函数返回时恢复原信号掩码。这三个操作可看作一个原子操作sigsetjmp
siglongjmp
siglong为非局部调整,可从一个函数跳转到另一个函数
可以正常结束循环,但是可能打断printf的执行,使得printf的私有数据结构前后不一致,可以结合信号阻塞和解阻塞的使用
应避免使用此方法- 创建一个管道,信号处理向其中写数据,使用select同时监听管道和套接字描述符
解法1 (错误)
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) {
int n;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
sigset_t sigset_alrm;
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
Sigemptyset(&sigset_alrm);
Sigaddset(&sigset_alrm, SIGALRM);
Signal(SIGALRM, recvfrom_alarm);
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
alarm(5);
for (;;) {
len = servlen;
Sigprocmask(SIG_UNBLOCK, &sigset_alrm, NULL); // 接收信号
// 前后两个函数之间可以接收信号,循环内的其余部分不接收信号
n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
Sigprocmask(SIG_BLOCK, &sigset_alrm, NULL); // 开始阻塞信号
if (n < 0) {
if (errno == EINTR)
break;
else
err_sys("recvfrom error");
} else {
recvline[n] = 0;
printf("from %s: %s", Sock_ntop_host(preply_addr, len), recvline);
}
}
}
free(preply_addr);
}
解法2 pselect
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) {
int n;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
fd_set rset;
sigset_t sigset_alrm, sigset_empty;
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
FD_ZERO(&rset);
Sigemptyset(&sigset_empty);
Sigemptyset(&sigset_alrm);
Sigaddset(&sigset_alrm, SIGALRM);
Signal(SIGALRM, recvfrom_alarm);
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
Sigprocmask(SIG_BLOCK, &sigset_alrm, NULL);
alarm(5);
for (;;) {
FD_SET(sockfd, &rset);
n = pselect(sockfd + 1, &rset, NULL, NULL, NULL, &sigset_empty);
if (n < 0) {
if (errno == EINTR)
break;
else
err_sys("pselect error");
} else if (n != 1)
err_sys("pselect error: returned %d", n);
len = servlen;
n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
recvline[n] = 0;
printf("from %s: %s", Sock_ntop_host(preply_addr, len), recvline);
}
}
free(preply_addr);
}
解法3 (不推荐) siglongjmp
static sigjmp_buf jmpbuf;
static void recvfrom_alarm(int signo) {
siglongjmp(jmpbuf, 1); // 跳转到 jmpbuf 处
}
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) {
int n;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
Signal(SIGALRM, recvfrom_alarm);
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
alarm(5);
for (;;) {
if (sigsetjmp(jmpbuf, 1) != 0) // 设置 jmpbuf
break;
len = servlen;
n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
recvline[n] = 0;
printf("from %s: %s", Sock_ntop_host(preply_addr, len), recvline);
}
}
free(preply_addr);
}
使用pipe进行IPC,使用select同时监听pipe和socket
static int pipefd[2];
static void recvfrom_alarm(int signo) {
Write(pipefd[1], "", 1);
return;
}
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen) {
int n, maxfdp1;
const int on = 1;
char sendline[MAXLINE], recvline[MAXLINE + 1];
fd_set rset;
socklen_t len;
struct sockaddr *preply_addr;
preply_addr = Malloc(servlen);
Setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
Pipe(pipefd);
maxfdp1 = max(sockfd, pipefd[0]) + 1;
FD_ZERO(&rset);
Signal(SIGALRM, recvfrom_alarm);
while (Fgets(sendline, MAXLINE, fp) != NULL) {
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
alarm(5);
for (;;) {
FD_SET(sockfd, &rset);
FD_SET(pipefd[0], &rset);
if ((n = select(maxfdp1, &rset, NULL, NULL, NULL)) < 0) {
if (errno == EINTR)
continue;
else
err_sys("select error");
}
if (FD_ISSET(sockfd, &rset)) {
len = servlen;
n = Recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len);
recvline[n] = 0;
printf("from %s: %s", Sock_ntop_host(preply_addr, len), recvline);
}
if (FD_ISSET(pipefd[0], &rset)) {
Read(pipefd[0], &n, 1);
break;
}
}
}
free(preply_addr);
}