本章简单描述信号。信号是Linux系统中,内核和进程通讯的一种方式。若是内核但愿打断进程的顺序执行,那么内核会在进程的PCB中记录信号。而当这个进程被分配到CPU,进入执行状态时,首先会检查是否有信号,若是有信号,那么进程会先尝试执行信号处理函数。bash
内核须要打断进程运行的时机:session
进程没法继续了函数
按下ctrl+c
ctrl+c实际上是bash向前台进程组发送SIGINTspa
运行该程序后,再按Ctrl+c,结果是四个进程所有退出rest
有了signal的处理以后,ctrl+c发送的SIGINT不会致使进程退出。code
经过kill -l
命令能够看到系统定义的信号类型,信号值小于32的是传统的信号,称之为非实时信号,而大于32的称之为实时信号。这里只讨论非实时信号。blog
能够经过signal函数,注册信号处理函数。若是没有注册信号处理函数,那么按照默认方式处理。
也能够经过signal设置忽略信号。进程
信号 | 默认处理动做 | 发出信号的缘由 |
---|---|---|
SIGHUP | A | 进程session leader退出时,同session的其余进程会收到这个信号 |
SIGINT | A | Ctrl+C |
SIGQUIT | C | Ctrl+D |
SIGILL | C | 非法指令 |
SIGABRT | C | 调用abort函数产生的信号 |
SIGFPE | C | 浮点异常 |
SIGKILL | AEF | Kill信号 |
SIGSEGV | C | 无效的内存引用 |
SIGPIPE | A | 管道破裂: 写一个没有读端口的管道 |
SIGALRM | A | 由alarm(2)发出的信号 |
SIGTERM | A | 终止信号 |
SIGUSR1 | A | 用户自定义信号1 |
SIGUSR2 | A | 用户自定义信号2 |
SIGCHLD | B | 子进程状态变化会给父进程发送SIGCHLD信号 |
SIGCONT | 进程继续(曾被中止的进程) | |
SIGSTOP | DEF | 暂停进程 |
SIGTSTP | D | 控制终端(tty)上按下中止键 |
SIGTTIN | D | 后台进程企图从控制终端读 |
SIGTTOU | D | 后台进程企图从控制终端写 |
A 缺省的动做是终止进程
B 缺省的动做是忽略此信号
C 缺省的动做是终止进程并进行内核映像转储(dump core)
D 缺省的动做是中止进程
E 信号不能被捕获
F 信号不能被忽略内存
#include <stdio.h> #include <signal.h> #include <stdlib.h> void signal_handle(int a) { if(a == SIGINT) printf("signal_handle\n"); else if(a == SIGABRT) printf("abrt\n"); else if(a == SIGALRM) printf("alarm\n"); else if(a == SIGCHLD) printf("child\n"); else if(a == SIGUSR1) printf("usr1 signal\n"); } int main() { // SIGINT 2 signal(SIGINT, signal_handle); signal(SIGABRT, signal_handle); signal(SIGALRM, signal_handle); signal(SIGCHLD, signal_handle); signal(SIGUSR1, signal_handle); pid_t pid = fork(); if(pid == 0) return 0; // 给本身发送一个abrt信号 //abort(); alarm(1); while(1) { sleep(1); } }
信号值小于32的都是不可靠信号,假如进程收到一个信号来不及处理,这时候又收到一个一样的信号,那么这两个信号会合并成一个信号,这个缘由是由于进程保存该信号的值只有1位。rem
假如一个进程调用了某系统调用致使该进行处于挂起状态,而此时该进程接收到一个信号,那么该系统调用被唤醒。一般该系统调用会返回-1,错误码是EINTR
。
也有些系统调用,能够设置打断后重启,这样就不会被信号打断,具体参考man 7 signal
若是使用signal函数注册信号处理函数,默认被中断的系统调用是自动重启的。
#include <signal.h> #include <stdio.h> #include <errno.h> void handle(int v){ printf("ctrl+c\n"); } int main() { signal(SIGINT, handle); char buf; int ret = read(0, buf, sizeof(buf)); // read被中断打断了 printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR }
信号会致使可重入问题,好比一个全局链表。
以上代码在必定状况下会崩溃,在main函数中不停调用push_back,若是在push_back执行一半时,被中断打断,而后去执行中断处理函数时,那么该中断处理函数的push_back会崩溃。
有些系统调用自己带有局部静态变量,所以那些函数不能在信号处理函数中调用,好比strtok
,readdir
等,对应的可重入的函数是strtok_r
,readdir_r
。
能够经过kill函数发送信号。
kill也能够进程组发送信号
#include <sys/types.h> #include <signal.h> int main() { kill(27054, SIGUSR1); }
//掩盖不可靠信号 #include <signal.h> #include <stdio.h> #include <stdlib.h> // 掩盖SIGINT // 掩盖一个可靠信号 void handle(int v) { printf("sigint \n"); } int main() { signal(SIGINT, handle); sigset_t set; // 将set集合设置为空 sigemptyset(&set); // 将SIGINT信号加入集合 sigaddset(&set, SIGINT); // 把这个集合表明的信号,加入信号掩码 sigprocmask(SIG_BLOCK, &set, NULL); // 今后,该进程收到SIGINT,不会被处理 sleep(5); printf("remove SIGINT from mask\n"); // 去掉SIGINT的掩码 sigprocmask(SIG_UNBLOCK, &set, NULL); while(1) { sleep(1); } }
#include <signal.h> #include <stdio.h> #include <stdlib.h> // 掩盖34 // 掩盖一个可靠信号 void handle(int v) { printf("hahahaha \n"); } int main() { signal(34, handle); sigset_t set; // 将set集合设置为空 sigemptyset(&set); // 将34信号加入集合 sigaddset(&set, 34); // 把这个集合表明的信号,加入信号掩码 sigprocmask(SIG_BLOCK, &set, NULL); // 今后,该进程收到34,不会被处理 kill(getpid(), 34); kill(getpid(), 34); kill(getpid(), 34); kill(getpid(), 34); kill(getpid(), 34); sleep(5); printf("remove 34 from mask\n"); // 去掉34的掩码 sigprocmask(SIG_UNBLOCK, &set, NULL); while(1) { sleep(1); } }
#include <signal.h> void handle(int v) { } int main() { // 表示忽略SIGINT // 忽略信号和掩盖信号是有区别: // // 未决的信号:已经发出可是没有被处理的信号叫未决的信号 signal(SIGINT, SIG_IGN); // 从这里开始就不忽略 signal(SIGINT, handle); // 设置一个处理函数 signal(SIGINT, SIG_DFL); // 恢复成默认处理 while(1) { sleep(1); } }
以上例子,忽略SIGPIPE信号,那么进程收到SIGPIPE后,不会有任何反应。
屏蔽和忽略不一样,忽略意味着在忽略期间,接收的信号就被忽略了。而屏蔽的意思,是暂时屏蔽,屏蔽期间收到的信号依旧在,若是某一时刻该信号再也不忽略时,该信号的处理程序会被调用。
设置屏蔽集合,使用sigprocmask
SIGCHLD信号产生于子进程退出和状态变化,父进程一般在该信号的处理函数中,调用wait来回收子进程的PCB,这样能够避免阻塞。
#include <signal.h> void chld_handle(int v) { // 有子进程退出了 // wait(NULL); // while(1) // 使用while(1)是避免有多个子进程同时退出,因为SIGCHLD是不可靠信号,函数只会调用一次 { int ret = waitpid(-1, NULL, WNOHANG); // 每次回收都应该用非阻塞方式去回收 if(ret == -1) // 没有僵尸进程了,以前僵尸进程已经被回收了 break; } } int main() { // 等待子进程的结束,问题是:ddddd // 它阻塞主进程的执行,影响效率 // wait(NULL); // signal(SIGCHLD, chld_handle); pid_t pid = fork(); if(pid == 0) return 0; while(1) { sleep(1); } }
sigaction和signal同样用来注册信号处理函数,siqqueue和kill同样,用来发送信号,可是sigaction比signal功能强大,signal比较简单。
强大:
能够传递参数
能够得到发送信号的进程信息
能够设置SA_RESTART
#include <signal.h> #include <stdio.h> #include <errno.h> //void handle(int v){} // // 新的信号处理函数 void handle(int v, siginfo_t* info, void* p) { printf("ctrl+c\n"); } int main() { // 默认的signal已经有SA_RESTART这个flag了 //signal(SIGINT, handle); struct sigaction sig; sig.sa_handler = NULL; sig.sa_sigaction = handle; sigemptyset(&sig.sa_mask); // sig.sa_flags = 0; sig.sa_flags = SA_RESTART; // 让阻塞的系统调用,被这个信号打断以后,要重启 sig.sa_restorer = NULL; // 在Linux下没用,直接填NULL就能够了 sigaction(SIGINT, &sig, NULL); char buf; int ret = read(0, buf, sizeof(buf)); // read被中断打断了 printf("ret = %d, errno=%d, EINTR=%d\n", ret, errno, EINTR); // EINTR }
#include <stdio.h> #include <stdlib.h> #include <signal.h> char buf[1024]; void handle_data() { printf("user input is %s\n", buf); } void handle_chld1(int v) { while(1) { int ret = waitpid(-1, NULL, WNOHANG); if(ret < 0) break; } } void handle_chld(int v, siginfo_t* info, void* p) { handle_chld1(v); } int main() { // signal(SIGCHLD, handle_chld); struct sigaction act; act.sa_handler = NULL; act.sa_sigaction = handle_chld; sigemptyset(&act.sa_mask); act.sa_flags = 0; act.sa_restorer = NULL; sigaction(SIGCHLD, &act, NULL); while(1) { // char* ret = fgets(buf, sizeof(buf), stdin); // if(ret == NULL) break; int ret = read(0, buf, sizeof(buf)); if(ret < 0) { // 说明read出错,不是真正的出错,而是被中断打扰了 // 那此时,应该从新调用read函数,去获取信息 if(errno == EINTR) { continue; } // 若是是其余错误缘由,就break break; } pid_t pid = fork(); if(pid == 0) { handle_data(); // 建立一个子进程去处理数据 exit(0); } } }
#include <signal.h> #include <stdio.h> void handle(int v, siginfo_t* info, void* p) { printf("recv SIGINT, arg=%d\n", info->si_value.sival_int); } int main() { struct sigaction act; act.sa_handler = NULL; act.sa_sigaction = handle; sigemptyset(&act.sa_mask); act.sa_flags = SA_SIGINFO|SA_RESTART; act.sa_restorer = NULL; // 注册信号处理函数 sigaction(SIGINT, &act, NULL); union sigval v; v.sival_int = 99; // 至关于kill,可是它能够传递一个参数 sigqueue(getpid(), SIGINT, v); getchar(); }
#include <signal.h> #include <stdio.h> int main() { char* p = malloc(100); union sigval v; // v.sival_int = 98; v.sival_ptr = p; // 至关于kill,可是它能够传递一个参数 sigqueue(28360, SIGINT, v); getchar(); }
signal:注册信号处理函数
kill:发送信号
sigprocmask:设置信号掩码
sigemptyset:清空信号集
sigfillset:设满信号集
sigaddset:往信号集增长一个信号
sigdelset:从信号集删除一个信号
sigismember:判断信号不然在信号集
sigaction:注册更增强大的处理函数
sigqueue:发送信号
abortalarmpause