信号的阻塞、未达:
linux中进程1向进程2发送信号,要经过内核,内核会维护一个进程对某个信号的状态,如下图所示:
当进程1向进程2发送信号时,信号的传递过程在内核中是有状态的,内核首先要检查这个信号是不是处于阻塞状态,然后检查这个信号是不是处于未决状态,最后检查是不是忽略该信号。
更详细的信号传递过程如下:
一个信号送到进程2时,先检查这个进程的信号屏蔽字block,如果该信号对应位是1,表示进程把这个信号是屏蔽(阻塞)了,然后内核就将pending状态字的相应位置为1,表示信号未抵达,当我们在进程2中调用一个函数将block中的相应位置为0时,pending中的对应位就会被置为0,这时候刚才未达的信号就可以继续往后走了,然后检查进程2对这个信号是不是忽略,如果不是忽略就调用相应的信号处理函数。
下面介绍几个操作信号集的函数:
int sigemptyset(sigset_t *set) 把信号集(64bit)全部清零
int sigfillset(sigset_t *set) 把信号集全部置为1
int sigaddset(sigset_t *set, int signo) 根据signo,把信号集中的相应位置为1
int sigdelset(sigset_t *set, int signo) 根据signo,把信号集中相应的位清0
int sigismember(const sigset_t *set, int signo) 判断signo是否在信号集中
获取block状态字状态的函数:
int sigprocmask(int how, const sigset_t *set, sigset_t *oset) 读取或者更改进程的信号屏蔽状态字(block)
若成功则返回0,若出错则返回-1
如果oset是非空指针,则读取进程的当前信号屏蔽状态字通过oset传出。如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。如果oset和set都是非空指针,则先将原来的状态字备份到oset里,然后根据set和how参数更改信号屏蔽字,假设当前的信号屏蔽字为mask,下表说明了how的可选值。
int sigpending(sigset_t *set) 获取进程没有抵达的状态字
信号阻塞、未达示例程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <signal.h> 5 6 7 8 void my_handler(int num) 9 { 10 if(SIGINT == num) 11 { 12 printf("recv signal SIGINT "); 13 } 14 else if(SIGQUIT == num) 15 { 16 sigset_t uset; 17 sigemptyset(&uset); 18 sigaddset(&uset, SIGINT); 19 sigprocmask(SIG_UNBLOCK, &uset, NULL); 20 printf("recv signal num = %d ", num); 21 } 22 23 } 24 25 void printsigset(sigset_t *set) 26 { 27 int i = 0; 28 for(i = 1; i < 65; i++) 29 { 30 if(sigismember(set, i)) 31 { 32 putchar('1'); 33 } 34 else 35 { 36 putchar('0'); 37 } 38 } 39 40 printf(" "); 41 } 42 43 int main() 44 { 45 sigset_t bset; 46 sigset_t pset; 47 48 sigemptyset(&bset); 49 sigaddset(&bset, SIGINT); 50 51 if(signal(SIGINT, my_handler) == SIG_ERR) 52 { 53 perror("signal error"); 54 exit(0); 55 } 56 57 if(signal(SIGQUIT, my_handler) == SIG_ERR) 58 { 59 perror("signal error"); 60 exit(0); 61 } 62 63 sigprocmask(SIG_BLOCK, &bset, NULL); 64 65 for(;;) 66 { 67 sigpending(&pset); 68 printf("bset : "); 69 printsigset(&bset); 70 printf("pset : "); 71 printsigset(&pset); 72 73 sleep(2); 74 } 75 76 return 0; 77 }
在主函数中,我们将SIGINT设置为阻塞,执行程序时,当没有按下ctrl+c(产生SIGINT)时,pending状态字为全0,说明没有未达信号,当按下ctrl+c时,由于block中将SIGINT设置为了阻塞,所以当产生SIGINT时,pending中的第2位(SIGINT对应的位,信号从第1位开始算起)被置为了1。执行程序,结果如下:
ctrl+c产生SIGINT, ctrl+产生SIGQUIT。按下ctrl+触发信号处理函数,产生SIGQUIT信号,解除对SIGINT的阻塞,而刚刚那个未达的SIGINT被送达,再一次触发信号处理函数,打印出recv signal SIGINT。SIGINT被送达后,相应的pending位被清0了。我们在信号处理函数中将block中的阻塞位清除,但是并没有起作用(原因未知,在信号处理函数中设置block,只是临时起作用,例如:现在有一个未达信号SIGINT,我们产生SIGQUIT进入信号处理函数,临时将block中的阻塞位解除,然后处理这个未达信号,处理完之后,将阻塞位恢复原样,然后继续执行。 从执行结果也可以看出,是先打印出recv signal SIGINT,又打印出recv signal num = 3)。
block中设置SIGINT屏蔽字,按下ctrl+c,pending相应位置为1,按下ctrl+,SIGQUIT被送达一次,信号处理函数被调用,SIGINT又被送达一次(因为刚才处于pending),SIGQUIT被送达的那次,在信号处理函数中将block中SIGINT位清0,并恢复SIGINT的处理函数为默认。再次按下ctrl+c,应该退出程序,但是没有退出,而是pending中又被置为1,再次按下ctrl+,则信号处理函数中SIGQUIT相关的打印被输出,然后程序退出。
sigaction注册信号处理函数:
sigaction也用来做信号的安装,该函数功能比signal更强大。函数原型如下:
int sigaction(int signum, const struct sigaction *act, const struct sigaction *old)
该函数的第一个参数为信号的值,可以为除SIGKILL和SIGSTOP外的任何一个有效信号值(为这两个信号定义自己的处理函数,将导致安装错误)。
第二个参数为指向sigaction的一个实例的指针,在结构sigaction中指定了对特定信号的处理,可以为空,进程会以缺省方式对信号进行处理。
第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定为NULL。
第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等。
struct sigaction
{
void (*sa_handler)(int) //信号处理程序,不接受额外数据,老的处理函数
void (*sa_sigaction)(int, siginfo_t *, void *) // 信号处理程序,能接受额外数据,和sigqueue配合使用
sigset_t sa_mask;
int sa_flags; //影响信号的行为,SA_SIGINFO表示能接受数据,如果进程想要接收额外数据,则应设置该位
void (*sa_restorer)(void) //废弃
}
sa_handler和sa_sigaction不能同时存在,两个都赋值的话,优先调用sa_sigaction。
示例程序:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <signal.h> 5 6 7 8 void my_handler(int num) 9 { 10 printf("recv signal num = %d ", num); 11 } 12 13 14 int main() 15 { 16 struct sigaction act = {0}; 17 18 act.sa_handler = my_handler; 19 20 sigaction(SIGINT, &act, NULL); 21 22 for(;;) 23 { 24 sleep(2); 25 } 26 27 return 0; 28 }
执行结果如下:
赋值sa_sigaction的示例程序:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <signal.h> 5 6 7 8 void my_handler(int num) 9 { 10 printf("recv signal num = %d ", num); 11 } 12 13 void my_sa_sigaction(int num, siginfo_t *info, void *p) 14 { 15 printf("recv sig : %d ", num); 16 } 17 18 int main() 19 { 20 struct sigaction act = {0}; 21 22 act.sa_handler = my_handler; 23 act.sa_sigaction = my_sa_sigaction; 24 25 sigaction(SIGINT, &act, NULL); 26 27 for(;;) 28 { 29 sleep(2); 30 } 31 32 return 0; 33 }
执行结果如下:
sigqueue函数:
新的发送信号的系统调用,主要是针对实时信号提出的信号带有参数,与函数sigaction()配合使用。比kill函数强大,函数原型如下:
int sigqueue(pid_t pid, int sig, const union sigval value)
第一个参数指定接收信号的进程id,第二个参数确定即将发送的信号,第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
sigval联合体如下:
typedef union sigval
{
int sival_int;
void *sival_ptr;
}sigval_t;
下面我们编写带有额外数据的信号发送处理函数,先给出信号处理函数中第二个参数siginfo_t的具体定义:
siginfo_t {
int si_signo; /* Signal number */
int si_errno; /* An errno value */
int si_code; /* Signal code */
int si_trapno; /* Trap number that caused
hardware-generated signal
(unused on most architectures) */
pid_t si_pid; /* Sending process ID */
uid_t si_uid; /* Real user ID of sending process */
int si_status; /* Exit value or signal */
clock_t si_utime; /* User time consumed */
clock_t si_stime; /* System time consumed */
sigval_t si_value; /* Signal value */
int si_int; /* POSIX.1b signal */
void *si_ptr; /* POSIX.1b signal */
int si_overrun; /* Timer overrun count; POSIX.1b timers */
int si_timerid; /* Timer ID; POSIX.1b timers */
void *si_addr; /* Memory location which caused fault */
int si_band; /* Band event */
int si_fd; /* File descriptor */
}
程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <signal.h> 5 6 7 8 void my_handler(int num) 9 { 10 printf("recv signal num = %d ", num); 11 } 12 13 void my_sa_sigaction(int num, siginfo_t *info, void *p) 14 { 15 int myintnum = 0; 16 myintnum = info->si_value.sival_int; 17 printf("%d %d ", myintnum, info->si_int); 18 } 19 20 int main() 21 { 22 pid_t pid; 23 struct sigaction act = {0}; 24 25 act.sa_sigaction = my_sa_sigaction; 26 sigemptyset(&act.sa_mask); 27 act.sa_flags = SA_SIGINFO; 28 29 if (sigaction(SIGINT, &act, NULL) < 0) 30 { 31 perror("sigaction error"); 32 exit(0); 33 } 34 35 pid = fork(); 36 37 if(pid == -1) 38 { 39 perror("fork error"); 40 } 41 42 if(pid == 0) 43 { 44 union sigval usig; 45 usig.sival_int = 100; 46 int n = 5; 47 while(n > 0) 48 { 49 sigqueue(getppid(), SIGINT, usig); 50 n--; 51 sleep(2); 52 } 53 exit(0); 54 } 55 56 for(;;) 57 { 58 sleep(2); 59 } 60 61 return 0; 62 }
执行结果如下:
如果子进程不睡眠,而是一直发信号,则可能造成信号丢失,因为SIGINT是不可靠信号。
实时信号与非实时信号示例程序如下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <signal.h> 5 6 7 void my_sigaction(int signum, siginfo_t *info, void *p) 8 { 9 if(SIGINT == signum) 10 { 11 printf("recv SIGINT, num = %d ", signum); 12 } 13 else if(SIGRTMIN == signum) 14 { 15 printf("recv SIGRTMIN num = %d ", signum); 16 } 17 else if(SIGUSR1 == signum) 18 { 19 sigset_t set; 20 sigemptyset(&set); 21 sigaddset(&set, SIGINT); 22 sigaddset(&set, SIGRTMIN); 23 sigprocmask(SIG_UNBLOCK, &set, NULL); 24 } 25 else 26 { 27 printf("recv else "); 28 } 29 } 30 31 int main() 32 { 33 pid_t pid; 34 int ret = 0; 35 struct sigaction act = {0}; 36 sigemptyset(&act.sa_mask); 37 act.sa_flags = SA_SIGINFO; 38 act.sa_sigaction = my_sigaction; 39 40 if (sigaction(SIGINT, &act, NULL) < 0) 41 { 42 perror("sigaction error"); 43 exit(0); 44 } 45 46 if(sigaction(SIGRTMIN, &act, NULL) < 0 ) 47 { 48 perror("sigaction error"); 49 exit(0); 50 } 51 52 if(sigaction(SIGUSR1, &act, NULL) < 0 ) 53 { 54 perror("sigaction error"); 55 exit(0); 56 } 57 58 sigset_t bset; 59 60 sigemptyset(&bset); 61 sigaddset(&bset, SIGINT); 62 sigaddset(&bset, SIGRTMIN); 63 64 sigprocmask(SIG_BLOCK, &bset, NULL); 65 66 pid = fork(); 67 68 if(pid == -1) 69 { 70 perror("fork error"); 71 exit(0); 72 } 73 74 if(pid == 0) 75 { 76 int i = 0; 77 union sigval v; 78 v.sival_int = 200; 79 80 for(i = 0; i < 3; i++) 81 { 82 ret = sigqueue(getppid(), SIGINT, v); 83 if(ret != 0) 84 { 85 printf("sent SIGINT failed "); 86 } 87 else 88 { 89 printf("sent SIGINT success "); 90 } 91 92 } 93 94 v.sival_int = 300; 95 for(i = 0; i < 3; i++) 96 { 97 ret = sigqueue(getppid(), SIGRTMIN, v); 98 if(ret != 0) 99 { 100 printf("sent SIGRTMIN failed "); 101 } 102 else 103 { 104 printf("sent SIGRTMIN success "); 105 } 106 } 107 108 v.sival_int = 400; 109 kill(getppid(), SIGUSR1); 110 111 exit(0); 112 } 113 114 while(1) 115 { 116 sleep(1); 117 } 118 119 printf("end main ... "); 120 121 return 0; 122 }
我们注册了非实时信号SIGINT和实时信号SIGRTMIN,还有一个用户自定义信号SIGUSR1,在本例中负责发送解除命令。它们为同一个处理程序,只是有不同的分支,在子进程中发送了3次SIGINT和三次SIGRTMIN,一开始,这两个信号都是阻塞的,因此发送完成后它们都处于未决状态,当发送SIGUSR1后,临时解除阻塞,未决信号重新被发送,但是非实时信号只被发送了一次,属于不可靠信号。而实时信号发送原来的次数,属于可靠信号。执行结果如下所示:
实时的信号会存储在进程的结构中,所以不会丢失,会缓存到内核中,但是存储的数量也是有上限的,超过缓存上限后,再到来的信号会直接扔掉而不会将之前的冲掉,具体上限可以使用ulimit -a查看,pending signals如下所示:
而非实时信号,linux内核是不缓存的,发送多少也无所谓,最终只缓存一条。