1、信号的介绍
信号是事件发生时对进程的通知机制。一个进程可以向另一个进程发送信号。在Linux内核中,使用一个唯一的整数来区分不同的信号。
可以在终端输入“kill -l”命令来查看所有的信号及其对应的编号。
(1)信号的四要素:编号、信号名称、对应事件和默认动作
在终端执行“man 7 signal”可以查看到所有信号的相关信息
例如:SIGINT信号的编号为2,名称为SIGINT,对应事件为在键盘输入ctrl+c,默认动作为终止进程。
2、用户进程对信号的响应方式
(1)执行默认操作:遵循信号原有的含义。相当于用户进程对信号没有任何处理。通常情况下会导致用户进程的退出;
(2)忽略信号:忽略信号的原有含义。对信号不做任何处理;
(3)捕捉信号:实现对信号的重定义。用户定义与该信号对应的信号处理函数,当收到该信号时,执行相应的处理函数。
注意:信号SIGKILL和SIGSTOP不能被忽略,也不能被捕捉,只能进行默认处理结束当前进程。
3、信号在内核中的表示
信号的处理动作称为信号递达(Delivery),信号从产生到递达之间的状态,称为信号未决(Pending)。
进程可以选择阻塞(Block)某个信号(SIGKILL信号和SIGSTOP信号不能被阻塞)。被阻塞的信号产生之后将一直处于未决状态,直到被阻塞的信号的解除阻塞,该信号才能被递达。
注意:阻塞和忽略是两个不同的动作,阻塞说明这个信号不会递达。而忽略说明这个信号递达之后的处理动作是忽略。
未决信号集:保存没有被当前进程处理的信号
阻塞信号集:保存需要阻塞的信号
在进程的PCB中使用未决信号集和阻塞信号集的来处理信号。信号产生时,内核在进程控制块中的未决信号集设置该信号对应的标志,直到信号递达之后才清除该标志。如果该信号的阻塞信号集对应标志为有效,则这个信号被阻塞,不能被递达,同时该信号在未决信号集的标志会被保留,直到解除该信号的阻塞。
如图所示,信号的处理过程如下:
(1)SIGHUP信号没有产生也没有阻塞,递达之后的处理方式为默认处理(SIG_DFL)
(2)SIGINT信号已经产生并处于未决状态,但是阻塞信号集中相应位置为1,信号被阻塞
(3)SIGQUIT信号已经产生,没有被阻塞,会调用用户定义的signal_handler信号处理函数,函数返回之后清除未决信号集中的SIGQUIT信号
4、常用函数
改变信号的处理方式:signal函数
函数名称:sighandler_t signal(int signum, sighandler_t handler);
函数功能:指定信号的处理方式
头文件:#include
输入参数:
signum:要修改处理方式的信号编号
handler:
SIG_DFL:将该信号的处理方式恢复为默认值,适用于将之前调用signal函数所改变的信号处理方式还原
SIG_IGN:忽略该信号,用户进程在收到该信号时直接丢弃
自定义函数名称:有用户自定义信号的处理方式
返回值:
输入参数handler传递的是用户自定义的函数,返回值为该函数的地址
输入参数handler传递的是SIG_DFL,返回值也是SIG_DFL
输入参数handler传递的是SIG_IGN,返回值也是SIG_IGN
调用失败返回SIG_ERR,并设置error
其他:
typedef void (*__sighandler_t) (int);
#define SIG_ERR ((__sighandler_t) -1) /* Error return. */
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
signal函数的示例代码:#include #include #include void signal_func(int sig){ printf("signal handler(%d)\n", sig); /* 修改信号处理方式为默认处理 */ signal(sig, SIG_DFL);}int main(int argc, char *argv[]){ static unsigned int cnt = 0; /* 修改SIGINT信号的处理方式,在接收到SIGINT信号时,将调用signal_func函数 */ if (signal(SIGINT, signal_func) == SIG_ERR) { perror("signal handler set failed!\n"); return -1; } while(1) { printf("cnt = %d\n", cnt++); sleep(1); } return 0;}
等待信号:pause函数
function | int pause(void); |
head file | #include |
description | 阻塞进程的执行,直到收到信号,并且信号处理函数成功返回 |
input | void |
ret value | 收到信号后,返回-1,并将error设置为EINTR |
示例代码:
#include #include #include void signal_function(int sig){ static unsigned int sigint_cnt = 0; static unsigned int sigquit_cnt = 0; switch (sig) { case SIGINT: printf("Recv SIGINT signal cnt is %d\n", ++sigint_cnt); break; case SIGQUIT: printf("Recv SIGQUIT signal cnt is %d\n", ++sigquit_cnt); break; default: break; }}int main(int argc, char *argv[]){ static unsigned int recv_signal_cnt = 0; int ret = 0; /* 修改SIGINT信号的处理方式 */ if (signal(SIGINT, signal_function) == SIG_ERR) { perror("signal handler set failed!\n"); return -1; } /* 修改SIGQUIT信号的处理方式 */ if (signal(SIGQUIT, signal_function) == SIG_ERR) { perror("signal handler set failed!\n"); return -1; } while(1) { /* 只有在捕获到信号,并且对应的信号处理函数以及返回时,pause函数才会返回 * 在这种情况下,pause函数会返回-1,并且errno设置为EINTR */ ret = pause(); printf("pause() return is %d\n", ret); perror("pause() return"); printf("Recved signal cnt = %d\n", ++recv_signal_cnt); } return 0;}
闹钟函数:alarm函数
function | unsigned int alarm(unsigned int seconds); |
head file | #include |
description | 为进程提供一个闹钟的功能,进程传入一个定时时间,在经过定时时间之后,内核会向该进程发送一个SIGALRM的信号。 如果传入的时间为0,则将取消之前设置的定时时间 |
input | 定时时间,单位为秒 |
ret value | 以前设置的定时时间的余留秒数 ret = alarm(10); /* ret = 0; */ sleep(2); ret = alarm(5); /* ret = 8 */ |
示例代码:
#include #include #include void sig_alarm_func(int sig){ printf("sig_alarm_func %d\n", sig);}int main(int argc, char *argv[]){ unsigned int cnt = 0; if (signal(SIGALRM, sig_alarm_func) == SIG_ERR) { perror("signal handler set failed!\n"); return -1; } alarm(10); while(1) { printf("run cnt = %d\n", cnt++); sleep(1); } return 0;}
信号发送函数1:raise函数
function | int raise(int sig); |
head file | #include |
description | 进程或线程为自己发送一个signal 对于单线程的程序,等同于kill(getpid(), sig) 对于多线程的程序,等同于pthread_kill(pthread_self(), sig) |
input | sig:要发送的信号编号 |
ret value | 返回0成功,返回非0值失败 |
other | 如果sig设置了信号处理函数,则在信号处理函数返回之后,raise函数才会返回 |
示例代码:
#include #include #include void signal_func(int sig){ printf("signal handler\n");}int main(int argc, char *argv[]){ unsigned int cnt = 0; signal(SIGINT, signal_func); while(1) { sleep(1); printf("run cnt = %d\n", cnt++); if (!(cnt % 10)) { raise(SIGINT); } } return 0;}
信号发送函数2:kill函数
function | int kill(pid_t pid, int sig); |
head file | #include #include |
description | 向指定进程发送信号 |
input | pid:目标进程的pid sig:要发送的信号 |
ret value | 成功返回0,失败返回-1,并设置errno |
other | kill函数返回-1的三大原因 (1)给定的信号无效(errno=EINVAL) (2)发送权限不够(errno=EPERM) (3)目标进程不存在(errno=ESRCH) |
示例代码:
#include #include #include #include void signal_func(int sig){ printf("child process recv sig %d\n", sig); _exit(0);}int main(int argc, char *argv[]){ pid_t pid = 0; pid = fork(); if (pid > 0) { /* parent process */ sleep(5); kill(pid, SIGINT); while(1); } else if (pid == 0) { /* child process */ signal(SIGINT, signal_func); while(1) { sleep(1); printf("child process\n"); } } else { } return 0;}
定时函数:setitimer函数
function | int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); | |
head file | #include | |
description | 为系统提供一个两次定时的功能。第一次定时时间到达之后,周期的设置第二次定时时间 | |
input | which | ITIMER_REAL:以系统真是时间来计算,定时时间到达时,发送SIGALRM信号 |
ITIMER_VIRTUAL:以该进程在用户态下花费的时间来计算,定时时间到达时,发送SIGVTALRM信号 | ||
ITIMER_PROF:以该进程在用户态和内核态花费的时间来计算,定时时间到达时,发送SIGPROF信号 | ||
new_value | 传入定时的时间参数 | |
old_value | 传出上一次定时的时间参数 | |
ret value | 设置成功返回0 设置失败返回-1 | |
other | struct itimerval { struct timeval it_interval; /* 第一次定时之后的周期定时时间 */ struct timeval it_value; /* 第一次定时时间 */ }; struct timeval { time_t tv_sec; /* 单位:秒 */ suseconds_t tv_usec; /* 单位:微秒 */ }; |
示例代码:
#include #include #include #include #include void sig_alarm_func(int sig){ printf("Recv signal: %d\n", sig);}int main(int argc, char *argv[]){ unsigned int cnt = 0; struct itimerval time; signal(SIGALRM, sig_alarm_func); memset(&time, 0, sizeof(time)); /* 第一次定时时间10s */ time.it_value.tv_sec = 10; time.it_value.tv_usec = 0; /* 第二次定时时间2s */ time.it_interval.tv_sec = 2; time.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &time, NULL); while (1) { printf("run cnt = %d\n", ++cnt); sleep(1); } return 0;}
信号集相关处理函数
头文件: #include
函数名称:int sigemptyset(sigset_t *set);
函数功能:初始化set所指向的信号集,使其中所有bit都为0
函数名称:int sigfillset(sigset_t *set);
函数功能:初始化set所指向的信号集,使其中所有bit都为1
函数名称:int sigaddset(sigset_t *set, int signo);
函数功能:向信号集中添加一个有效信号
函数名称:int sigdelset(sigset_t *set, int signo);
函数功能:从信号集中删除一个有效信号
输入参数:set:信号集的存储空间首地址
signo:具体信号的编号
返回值:成功返回0,失败返回-1,并设置errno。
函数名称:int sigismember(const sigset_t *set, int signo);
头文件: #include
函数功能:判断一个信号是否在信号集中
输入参数:set:信号集的存储空间首地址
signo:具体信号的编号
返回值:如果这个信号在信号集中返回1;不在这个信号集中返回0;出错返回-1,并设置errno
头文件: #include
函数名称:int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
头文件: #include
函数功能:修改PCB中的阻塞信号集
输入参数:how:
SIG_BLOCK :设置当前进程的阻塞信号集中的数据 mask = mask | set
SIG_UNBLOCK:清除当前进程的阻塞信号集中的数据 mask = mask & ~set
SIG_SETMASK:设置当前进程的阻塞信号集为set所指向的值,mask = set
set:阻塞信号集
输出参数:oset:返回当前进程的阻塞信号集
返回值:成功返回0;失败返回-1,并设置errno
头文件: #include
函数名称:int sigpending(sigset_t *set);
函数功能:获取当前进程的未决信号集
输出参数:set:信号集的存储空间首地址
返回值:成功返回0;失败返回-1,并设置errno
示例代码:
#include #include #include int main(int argc, char *argv[]){ int cnt = 0; int ret = 0; sigset_t sigset; sigset_t sigset_tmp; /* 初始化一个信号集,初值为0 */ sigemptyset(&sigset); /* 向信号集中添加一个SIGINT信号 */ sigaddset(&sigset, SIGINT); /* 判断SIGINT信号是否在sigset信号集中 */ ret = sigismember(&sigset, SIGINT); if (ret) printf("SIGINT set success!\n"); else printf("SIGINT set failed!\n"); /* 设置进程的阻塞信号集 */ ret = sigprocmask(SIG_SETMASK, &sigset, NULL); if (ret != 0) { perror("sigset set failed!\n"); return -1; } signal(SIGINT, SIG_IGN); while(1) { cnt++; /* 读取当前进程的阻塞信号集 */ ret = sigpending(&sigset_tmp); if (ret != 0) { perror("get pending signal failed!\n"); return -1; } /* 打印当前进程的阻塞信号的数据 */ for (int i = 1; i < 32; i++) { ret = sigismember(&sigset_tmp, i); printf("%d", ret ? 1 : 0); } printf("\n"); if (cnt > 10) { cnt = 0; /* 清除进程的阻塞信号集中的数据 */ sigprocmask(SIG_UNBLOCK, &sigset, NULL); } sleep(1); } return 0;}