延伸阅读:Linux命令——trap Linux命令——killall 、kill 、pkill、xkillhtml
什么是信号ubuntu
信号是一种通知进程某件事情发生了的一种通讯机制,经过向进程发送某个信号,能够告诉进程发生了什么事情,进程收到这个信号后,就知道某事情发生了,进程能够作出相应的响应(处理)。与IPC中其余进程通讯方式不一样的是,信号属于不精确通讯,信号只能告诉进程大概发生了什么事情,可是不能准确的告诉进程详细的细节信息。Linux下边定义了不少的信号,全部的信号都是一个整数编号,不过为了好辨识,Linux系统给这些整数编号都定义了对应的宏名,宏名都是以SIG开头,好比SIGABRT。缓存
谁会向进程发信号ide
总结起来,会有三个“人”会向进程发送信号,分别是“另外一进程”、“OS内核”、“硬件”。函数
另外一个进程发送信号post
eg:在命令行终端窗口经过kill命令向某个进程发送一个信号将其终止。测试
内核发送信号url
发生了某个事件,Linux内核可能会发送该事件对应的信号给某个进程spa
eg:管道通讯中,当全部读文件描述符被关闭,进程会被内核发送一个SIGPIPE信号,提示读管道出错了。
这个过程再详细一点?
计算机内部全部硬件链接在总线上。全部部件按照仲裁总线 或 中断总线上给出的信号来判断这个时刻总线能够由哪一个部件来使用。产生仲裁总线 或 者中断点位的能够是CPU,也能够是总线上的其余设备。若是CPU要向某个设备作输出操做,那么就由CPU主动作中断。若是某个设备请求向CPU发信号,则由这个设备主动产生中断信号来通知CPU 。CPU运行操做系统内核的设备管理程序,从而产生了这些信号。
底层硬件发送信号
底层硬件发生了某个事件,会向进程发送对应的某个信号。
eg:按下ctrl+c按键终止进程时,内核收到ctrl+c按键后,会向正在运行的进程发送SIGINT信号,将其异常终止。
注意:无论进程是被哪个信号给终止了,只要是被信号终止的,都是异常终止。
通常来讲,大多数发送信号的缘由,都是由于内核、硬件发生了某些事件时,才会向某个进程发送该事件专用的信号,告诉该进程这个事件发生了。对于咱们本身写的进程来讲,其实更可能是接收信号,而不是发送信号。
收到信号后进程如何应对
忽略
“鸵鸟策略”的作法,进程就当信号历来没有发生过。
捕获
进程会调用相应的处理函数,进行相应的处理。
默认
若是不忽略也不捕获的话,此时进程会使用系统设置的默认处理方式来处理信号。
Linux系统有哪些信号
查看Linux系统下有哪些信号
[root@localhost ~]# kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
总共62个信号,也就是说每一个进程能够接收的信号种类有62种,1~64为信号的编号,SIG***为信号的宏名。每一个信号表明着某种事件,通常状况下,当进程收到某个信号时,就表示该信号所表明的事件发生了。
1~34:也不是全部的信号都要掌握,咱们只须要关心其中经常使用的信号。
35~64:这些信号是Linux后期增设的信号,这些个信号不须要关心,因此不用了解。
经常使用信号
常见由用户发出终止进程的信号
Ctrl + C 发送SIGINT
Ctrl + \ 发送SIGQUIT
kill pid 发送SIGTERM
只有当进程有占用命令行终端时,才能Ctrl + C、Ctrl + \ 来终止。当没法使用Ctrl + C、Ctrl + \ 来终止进程时,每每就使用kill命令来终止进程。
kill 和 pkill
Linux命令——killall 、kill 、pkill、xkill
kill这个命令名字很吓人。其实kill只是发送信号,至于进程会不会被终止,这就看信号的处理方式,处理方式若是是终止,那么就会终止进程。若是把kill起名为send估计更好理解些,由于kill所起到的做用只是发送信号。
发送信号的完整格式:kill -信号编号 PID
信号编号写数字和宏名均可以。若是不写明信号编号的话:kill PID,默认发送的是15(SIGTERM)信号,等价于kill -SIGTERM PID或者kill -15 PID。只有发送15这个信号时才能省略信号编号,发送其它信号时必须写明信号编号。
pkill用法与kill差很少,只不过kill是按照PID来识别进程的,pkill是按照名字来识别进程的。
发送信号的完整格式:pkill -信号编号 名字
一样,若是不写明信号编号的话,默认发送的是15(SIGTERM)这个信号。
core文件
用于保存程序(进程)在当前结束的这一刻,进程在内存中的代码和数据,core文件能够用于分析进程在结束时的情况,不过因为进程代码和数据都是二进制的,因此把core文件直接打开后咱们是看不懂的,通常须要特殊软件翻译后才能看懂。并非全部的信号在终止进程时都会产生core文件,只有某个些信号在终止进程时才会产生core文件,不过通常状况下并不会建立这个文件,由于系统默认将产生core的设置给关闭了,只有打开后这个设置后才会保存core文件。因此当你看到提示core dumped,这就表示这个信号终止进程时,会产生core文件,只不过因为关闭了设置,所以core文件被丢弃了,dumped就是丢弃的意思。
[root@localhost ~]# ./a.out
^\Quit (core dumped)
Linux信号处理API
signal函数
函数原型
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
功能
设置某个信号的处理方式。处理方式能够被设置为忽略,捕获,默认。
进程的进程表(task_struct)中会有一个“信号处理方式登记表”,专门用于记录信号的处理方式,调用signal函数设置某个信号的处理方式时,会将信号的处理方式登记到该表中。每一个进程拥有独立的task_struct结构体变量,于是每一个进程的“信号处理方式登记表”都是独立的,因此每一个进程对信号的处理方式天然也是独立的,互不干扰。
参数
signum:
信号编号。handler个函数指针类型变量,函数指针类型是void (*)(int)
handler:
能够取值
#define SIG_DFL ((void (*)(int))0) #define SIG_IGN ((void (*)(int))1) #define SIG_ERR ((void (*)(int))-1)
这几个宏定义在了<signal.h>头文件中。
①忽略:SIG_IGN
除了SIGKILL(无条件终止一个进程)这个信号外,其它全部的信号均可被忽略和捕获。
②默认:SIG_DFL
③捕获:填写类型为void (*)(int)的捕获函数的地址,当信号发生时,会自动调用捕获函数来进行相应的处理。固然这个捕获函数须要咱们本身来实现,捕获函数的int参数,用于接收信号编号。捕获函数也被称为信号处理函数。
void signal_fun1(int signo){...} void signal_fun2(int signo){...} int main(void) { signal(SIGINT, signal_fun1); signal(SIGSEGV, signal_fun2); return 0; }
信号捕获函数调用时机:
进程接收到信号时就调用,调用时会中断进程的正常运行,当调用完毕后再会返回进程的正常运行。
更正前面的一个说法
在一开始提到:无论进程是被哪个信号给终止了,只要是被信号终止的,都是异常终止。
这种表达方式不严谨,应该说若是是在信号处理函数里面调用exit、_exit来终止进程的这种方式的话,准确来说不该该算是“”信号异常终止进程”的状况。信号异常终止进程,准确来说指的是信号默认的终止方式,这种状况才是“异常终止”。
返回值
成功:返回上一次的处理方式
失败:返回宏值SIG_ERR,而且设置errno。
kill
函数原型
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);
功能
向PID所指向的进程发送指定的信号。
返回值
成功返回0,失败返回-1,errno被设置。
raise
函数原型
#include <signal.h> int raise(int sig);
功能
向当前进程发送指定信号。
返回值
成功返回0,失败返回非0。
alarm
alarm函数不会阻塞
函数原型
#include <unistd.h> unsigned int alarm(unsigned int seconds);
功能
设置一个定时时间,当所设置的时间到后,内核会向调用alarm的进程发送SIGALRM信号。SIGALRM的默认处理方式是终止。
测试代码
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include<signal.h> int main(int argc,char**argv,char**environ) { alarm(5); while(1); return 0; }
返回值
返回上一次调用alarm时所设置时间的剩余值。若是以前没有调用过alarm,又或者以前调用alarm所设置的时间早就到了,那么返回的剩余值就是0。
pause
函数原型
#include <unistd.h> int pause(void);
功能
调用该函数的进程会永久挂起(阻塞或者休眠),直至被信号(任意一个信号)唤醒为止。
返回值
只要一直处于休眠状态,表示pause函数一直是调用成功的。当被信号唤醒后会返回-1,表示失败了,errno的错误号被设置EINTR(表示函数被信号中断)。
sleep
函数原型
#include <unistd.h> unsigned int sleep(unsigned int seconds);
功能
调用该函数的进程会休眠若干seconds,休眠期间能够被信号唤醒
返回值
若是sleep时间到了,返回0。若是sleep期间被信号中断了,返回剩余秒数。
abort
函数原型
#include <stdlib.h> void abort(void);
功能
向当前进程发一个SIGABRT信号,这个信号的默认处理方式是终止,所以若是不忽略和捕获的话,会将当前进程终止掉。abort至关于raise的特例,只发送SIGABRT信号自当前进程。这个函数有个绰号:自杀函数
返回值
无
进程的休眠与唤醒
调用sleep、pause等函数时,这些函数会使进程进入休眠状态。借助信号机制能够实现唤醒功能 ,这须要咱们建立空捕获函数(给信号登记一个空捕获函数,函数内容通常都是空的)。其唤醒过程以下:
当信号发送给进程后,会中断当前休眠的函数,而后去执行捕获函数,捕获函数执行完毕返回后,再也不调用休眠函数,而是执行休眠函数以后的代码,这样函数就被唤醒了。
若是唤醒后想继续休眠怎么办?
借助goto语句造成循环
学C的时候不是很反对用goto吗?
pause休眠唤醒后继续休眠
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <errno.h> void signal_fun(int signo) { printf("!!!!!!!\n"); } int main(int argc, char **argv, char **environ) { int ret = 0; signal(SIGINT, signal_fun); lable: ret = pause(); if(ret == -1 && errno == EINTR) { goto lable; } char buf[100] = {0}; read(0, buf, sizeof(buf)); printf("hello\n"); while(1); return 0; }
sleep休眠唤醒后继续休眠
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <errno.h> void signal_fun(int signo) { printf("!!!!!!!\n"); } int main(int argc, char **argv, char **environ) { int ret = 0; signal(SIGINT, signal_fun); ret = 10; lable1: ret =sleep(ret); if(ret != 0) { printf("ret = %d\n", ret); goto lable1; } char buf[100] = {0}; read(0, buf, sizeof(buf)); printf("hello\n"); while(1); return 0; }
pause、sleep致使的休眠,唤醒后想再次休眠须要手动重启,全部函数(会致使休眠的)都须要手动重启吗?
并非,对于绝大多数休眠函数来讲,被信号中断后,若是你想继续休眠的话,须要本身去手动重启,不然就会继续向后运行。这里说一个比较特殊的函数read
read
函数原型
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
功能
从fd指向的文件中,将数据读到应用缓存buf中
参数
fd:指向打开的文件
buf:读取到数据后,用于存放数据的应用缓存的起始地址
count:缓存大小(字节数)
返回值
成功:返回读取到的字符的个数
失败:返回-1,并自动将错误号设置给errno。
信号的发送、接收和处理的过程
信号屏蔽字
做用
用来屏蔽信号,有点像公司前台,信号来了先问前台(屏蔽字),我能被当即处理不,能就当即处理,不能处理就去未处理集呆着
每一个进程可以接收的信号有62种,信号屏蔽字的每一位记录了每一个信号是被屏蔽的仍是被打开的。
若是是打开的就当即处理。
若是是屏蔽的就暂不处理
信号屏蔽字放在那里
每个进程都有一个信号屏蔽字,它被放在了进程表(task_struct结构体变量)中。
如何修改信号屏蔽字
工做原理
定义一个64位的 与屏蔽字相似的 变量,将该变量设置为要的值,再经过sigprocmask函数将某信号对应的位设置为0或者为1。
如何设置这么个变量
函数原型
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum); int sigdelset(sigset_t *set, int signum);
set就是咱们前面说的变量,至于变量名也能够定义为其它的名字,不必定非要叫set。
功能
设置变量的值
1)sigemptyset:将变量set的64位所有设置为0。
2)sigfillset:将变量set的64位所有设置为1。
3)sigaddset:将变量set中,signum(信号编号)对应的那一位设置为1,其它为不变。
4)sigdelset:将变量set的signum(信号编号)对应的那一位设置为0,其它位不变。
返回值
调用成功返回0,失败返回-1,而且errno被设置。
sigprocmask
函数原型
#include <signal.h> int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能
使用设置好的变量set去修改信号屏蔽字。
参数
①how:修改方式,有三种。
一、SIG_BLOCK:屏蔽某个信号
屏蔽字=屏蔽字 | set
二、SIG_UNBLOCK:打开某个信号(不要屏蔽),实际就是对屏蔽字的某位进行清0操做。
屏蔽字=屏蔽字&(~set)
三、SIG_SETMASK:直接使用set的值替换掉屏蔽字
②set:set的地址
③oldset:保存修改以前屏蔽字的值
若是写为NULL的话,就表示不保存。
返回值
函数调用成功返回0,失败返回-1。
代码演示
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <signal.h> void signal_fun(int signo) { sigset_t set; printf("hello\n"); sigemptyset(&set); sigaddset(&set, SIGINT); sigprocmask(SIG_UNBLOCK, &set, NULL); sleep(3); printf("world\n"); } int main(int argc, char **argv, char **environ) { pid_t ret = 0; signal(SIGINT, signal_fun); while(1); return 0; }
执行结果,按下Ctrl+C时触发信号处理函数。这个进程杀不掉,必须用kill pid的方式
root@ubuntu:~# ./a.out ^Chello world ^Chello world ^Chello world Terminated
未处理信号集
跟屏蔽字同样,也一个64位的无符号整形数,专门用于记录未处理的信号。“未处理信号集”一样也是被放在了进程的进程表中(task_struct)。
信号来了,当进程的信号处理机制,检查该信号在屏蔽字中的对应位时发现是1,表示该信号被屏蔽了,暂时不能被处理,此时就会将“未处理信号集”中该信号编号所对应的位设置为1,这个记录就表示,有一个信号未被处理。若是该信号发送了屡次,可是每一次都由于被屏蔽了而没法处理的话,在“未处理信号集”中只记录一次。当屏蔽字中该信号的位变成0时(被打开了),此时就回去检查“未处理信号”,看该信号有没有未决的状况,有的话就处理它。