信号是 Linux 进程间通信的最古老的方式之一,是事件发生时对进程的通知机制,有时也称之为软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。
View Code
View Code
View Code
View Code
View Code
发往进程的诸多信号,通常都是源于内核。引发内核为进程产生信号的各类事件如下:
- 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C通常会给进程发送一个中断信号。
- 硬件发生异常,即硬件检测到一个错误条件并通知内核,随即再由内核发送相应信号给相关进程。比如执行一条异常的机器语言指令,诸如被 0 除,或者引用了无法访问的内存区域。
- 系统状态变化,比如 alarm 定时器到期将引起 SIGALRM 信号,进程执行的 CPU时间超限,或者该进程的某个子进程退出。
- 运行 kill 命令或调用 kill 函数。
使用信号的两个主要目的是:
- 让进程知道已经发生了一个特定的事情。
- 强迫进程执行它自己代码中的信号处理程序。
查看系统定义的信号列表: kill –l (前 31 个信号为常规信号,其余为实时信号)
信号相关的函数
alarm
/* #include <unistd.h> unsigned int alarm(unsigned int seconds); - 功能:设置定时器(闹钟)。函数调用,开始倒计时,当倒计时为0的时候, 函数会给当前的进程发送一个信号:SIGALARM - 参数: seconds: 倒计时的时长,单位:秒。如果参数为0,定时器无效(不进行倒计时,不发信号)。 取消一个定时器,通过alarm(0)。 - 返回值: - 之前没有定时器,返回0 - 之前有定时器,返回之前的定时器剩余的时间 - SIGALARM :默认终止当前的进程,每一个进程都有且只有唯一的一个定时器。 alarm(10); -> 返回0 过了1秒 alarm(5); -> 返回9 alarm(100) -> 该函数是不阻塞的 */ #include <stdio.h> #include <unistd.h> int main() { int seconds = alarm(5); printf("seconds = %d\n", seconds); // 0 sleep(2); seconds = alarm(2); // 不阻塞 printf("seconds = %d\n", seconds); // 3 while(1) { } return 0; }
setitimer
/* #include <sys/time.h> int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value); - 功能:设置定时器(闹钟)。可以替代alarm函数。精度微妙us,可以实现周期性定时 - 参数: - which : 定时器以什么时间计时 ITIMER_REAL: 真实时间,时间到达,发送 SIGALRM 常用 ITIMER_VIRTUAL: 用户时间,时间到达,发送 SIGVTALRM ITIMER_PROF: 以该进程在用户态和内核态下所消耗的时间来计算,时间到达,发送 SIGPROF - new_value: 设置定时器的属性 struct itimerval { // 定时器的结构体 struct timeval it_interval; // 每个阶段的时间,间隔时间 struct timeval it_value; // 延迟多长时间执行定时器 }; struct timeval { // 时间的结构体 time_t tv_sec; // 秒数 suseconds_t tv_usec; // 微秒 }; 过10秒后,每个2秒定时一次 - old_value :记录上一次的定时的时间参数,一般不使用,指定NULL - 返回值: 成功 0 失败 -1 并设置错误号 */ #include <sys/time.h> #include <stdio.h> #include <stdlib.h> // 过3秒以后,每隔2秒钟定时一次 int main() { struct itimerval new_value; // 设置间隔的时间 new_value.it_interval.tv_sec = 2; new_value.it_interval.tv_usec = 0; // 设置延迟的时间,3秒之后开始第一次定时 new_value.it_value.tv_sec = 3; new_value.it_value.tv_usec = 0; int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的 printf("定时器开始了...\n"); if(ret == -1) { perror("setitimer"); exit(0); } getchar(); return 0; }
kill
/* #include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig); - 功能:给任何的进程或者进程组pid, 发送任何的信号 sig - 参数: - pid : > 0 : 将信号发送给指定的进程 = 0 : 将信号发送给当前的进程组 = -1 : 将信号发送给每一个有权限接收这个信号的进程 < -1 : 这个pid=某个进程组的ID取反 (-12345) - sig : 需要发送的信号的编号或者是宏值,0表示不发送任何信号 kill(getppid(), 9); kill(getpid(), 9); int raise(int sig); - 功能:给当前进程发送信号 - 参数: - sig : 要发送的信号 - 返回值: - 成功 0 - 失败 非0 kill(getpid(), sig); void abort(void); - 功能: 发送SIGABRT信号给当前的进程,杀死当前进程 kill(getpid(), SIGABRT); */ #include <stdio.h> #include <sys/types.h> #include <signal.h> #include <unistd.h> int main() { pid_t pid = fork(); if(pid == 0) { // 子进程 int i = 0; for(i = 0; i < 5; i++) { printf("child process\n"); sleep(1); } } else if(pid > 0) { // 父进程 printf("parent process\n"); sleep(2); printf("kill child process now\n"); kill(pid, SIGINT); } return 0; }
signal
/* #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); - 功能:设置某个信号的捕捉行为 - 参数: - signum: 要捕捉的信号 - handler: 捕捉到信号要如何处理 - SIG_IGN : 忽略信号 - SIG_DFL : 使用信号默认的行为 - 回调函数 : 这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。 回调函数: - 需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义 - 不是程序员调用,而是当信号产生,由内核调用 - 函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置就可以了。 - 返回值: 成功,返回上一次注册的信号处理函数的地址。第一次调用返回NULL 失败,返回SIG_ERR,设置错误号 SIGKILL SIGSTOP不能被捕捉,不能被忽略。 */ #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> void myalarm(int num) { printf("捕捉到了信号的编号是:%d\n", num); printf("xxxxxxx\n"); } // 过3秒以后,每隔2秒钟定时一次 int main() { // 注册信号捕捉 // signal(SIGALRM, SIG_IGN); // signal(SIGALRM, SIG_DFL); // void (*sighandler_t)(int); 函数指针,int类型的参数表示捕捉到的信号的值。 signal(SIGALRM, myalarm); struct itimerval new_value; // 设置间隔的时间 new_value.it_interval.tv_sec = 2; new_value.it_interval.tv_usec = 0; // 设置延迟的时间,3秒之后开始第一次定时 new_value.it_value.tv_sec = 3; new_value.it_value.tv_usec = 0; int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的 printf("定时器开始了...\n"); if(ret == -1) { perror("setitimer"); exit(0); } getchar(); return 0; }
sigaction
/* #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); - 功能:检查或者改变信号的处理。信号捕捉 - 参数: - signum : 需要捕捉的信号的编号或者宏值(信号的名称) - act :捕捉到信号之后的处理动作 - oldact : 上一次对信号捕捉相关的设置,一般不使用,传递NULL - 返回值: 成功 0 失败 -1 struct sigaction { // 函数指针,指向的函数就是信号捕捉到之后的处理函数 void (*sa_handler)(int); // 不常用 void (*sa_sigaction)(int, siginfo_t *, void *); // 临时阻塞信号集,在信号捕捉函数执行过程中,临时阻塞某些信号。 sigset_t sa_mask; // 使用哪一个信号处理对捕捉到的信号进行处理 // 这个值可以是0,表示使用sa_handler,也可以是SA_SIGINFO表示使用sa_sigaction int sa_flags; // 被废弃掉了 void (*sa_restorer)(void); }; */ #include <sys/time.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> void myalarm(int num) { printf("捕捉到了信号的编号是:%d\n", num); printf("xxxxxxx\n"); } // 过3秒以后,每隔2秒钟定时一次 int main() { struct sigaction act; act.sa_flags = 0; act.sa_handler = myalarm; sigemptyset(&act.sa_mask); // 清空临时阻塞信号集 // 注册信号捕捉 sigaction(SIGALRM, &act, NULL); struct itimerval new_value; // 设置间隔的时间 new_value.it_interval.tv_sec = 2; new_value.it_interval.tv_usec = 0; // 设置延迟的时间,3秒之后开始第一次定时 new_value.it_value.tv_sec = 3; new_value.it_value.tv_usec = 0; int ret = setitimer(ITIMER_REAL, &new_value, NULL); // 非阻塞的 printf("定时器开始了...\n"); if(ret == -1) { perror("setitimer"); exit(0); } // getchar(); while(1); return 0; }
信号集
许多信号相关的系统调用都需要能表示一组不同的信号,多个信号可使用一个称之为信号集的数据结构来表示,其系统数据类型为 sigset_t。
信号集相关函数
在 PCB 中有两个非常重要的信号集。一个称之为 “阻塞信号集” ,另一个称之为 “未决信号集” 。这两个信号集都是内核使用位图机制来实现的。但操作系统不允许我们直接对这两个信号集进行位操作。而需自定义另外一个集合,借助信号集操作函数 来对 PCB 中的这两个信号集进行修改。
信号的 “未决” 是一种状态,指的是从信号的产生到信号被处理前的这一段时间。
信号的 “阻塞” 是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。
信号的阻塞就是让系统暂时保留信号留待以后发送。由于另外有办法让系统忽略信号,所以一般情况下信号的阻塞只是暂时的,只是为了防止信号打断敏感的操作。
/* 以下信号集相关的函数都是对自定义的信号集进行操作。 int sigemptyset(sigset_t *set); - 功能:清空信号集中的数据,将信号集中的所有的标志位置为0 - 参数:set,传出参数,需要操作的信号集 - 返回值:成功返回0, 失败返回-1 int sigfillset(sigset_t *set); - 功能:将信号集中的所有的标志位置为1 - 参数:set,传出参数,需要操作的信号集 - 返回值:成功返回0, 失败返回-1 int sigaddset(sigset_t *set, int signum); - 功能:设置信号集中的某一个信号对应的标志位为1,表示阻塞这个信号 - 参数: - set:传出参数,需要操作的信号集 - signum:需要设置阻塞的那个信号 - 返回值:成功返回0, 失败返回-1 int sigdelset(sigset_t *set, int signum); - 功能:设置信号集中的某一个信号对应的标志位为0,表示不阻塞这个信号 - 参数: - set:传出参数,需要操作的信号集 - signum:需要设置不阻塞的那个信号 - 返回值:成功返回0, 失败返回-1 int sigismember(const sigset_t *set, int signum); - 功能:判断某个信号是否阻塞 - 参数: - set:需要操作的信号集 - signum:需要判断的那个信号 - 返回值: 1 : signum被阻塞 0 : signum不阻塞 -1 : 失败 */ #include <signal.h> #include <stdio.h> int main() { // 创建一个信号集 sigset_t set; // 清空信号集的内容 sigemptyset(&set); // 判断 SIGINT 是否在信号集 set 里 int ret = sigismember(&set, SIGINT); if(ret == 0) { printf("SIGINT 不阻塞\n"); } else if(ret == 1) { printf("SIGINT 阻塞\n"); } // 添加几个信号到信号集中 sigaddset(&set, SIGINT); sigaddset(&set, SIGQUIT); // 判断SIGINT是否在信号集中 ret = sigismember(&set, SIGINT); if(ret == 0) { printf("SIGINT 不阻塞\n"); } else if(ret == 1) { printf("SIGINT 阻塞\n"); } // 判断SIGQUIT是否在信号集中 ret = sigismember(&set, SIGQUIT); if(ret == 0) { printf("SIGQUIT 不阻塞\n"); } else if(ret == 1) { printf("SIGQUIT 阻塞\n"); } // 从信号集中删除一个信号 sigdelset(&set, SIGQUIT); // 判断SIGQUIT是否在信号集中 ret = sigismember(&set, SIGQUIT); if(ret == 0) { printf("SIGQUIT 不阻塞\n"); } else if(ret == 1) { printf("SIGQUIT 阻塞\n"); } return 0; }