使用 kill -l命令可以查看系统定义的信号列表:
以上就是Linux下的62个基本信号。每个信号都有一个编号和一个宏定义名称,这些宏定义都可以在 signal.h 中找到。
其中编号34以上的都是实时信号,这里不做过多讨论。
管态(系统态,特态):指操作系统管理程序运行的状态,具有较高的特权级别。
目态(用户态,普态):指用户程序运行时的状态,具有较低的特权级别。
SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump。
【Core Dump】:吐出核心转储文件(车祸现场)
当⼀个进程要异常终⽌时,可以选择把进程的⽤户空间内存数据全部保存到磁盘上,⽂件名通常是core,这叫做Core Dump。
我们可以认为 core dump 是“内存快照”,但实际上,除了内存信息之外,还有些关键的程序运行状态也会同时 dump 下来,例如寄存器信息(包括程序指针、栈指针等)、内存管理信息、其他处理器和操作系统状态和信息。core dump 对于编程人员诊断和调试程序是非常有帮助的,因为对于有些程序错误是很难重现的,例如指针异常,而 core dump 文件可以再现程序出错时的情景。
进程异常终⽌通常是因为有Bug,⽐如⾮法内存访问导致段错误,事后可以⽤调试器检查core⽂件以查清错误原因,这叫做Post-mortem Debug(事后调试)。⼀个进程允许产⽣多⼤的core⽂件取决于进程的Resource Limit(这个信息保存 在PCB中)。默认是不允许产⽣core⽂件的,因为core⽂件中可能包含⽤户密码等敏感信息,不安全。在开发调试阶段可以⽤ulimit命令改变这个限制,允许产⽣core⽂件。 ⾸先⽤ulimit命令改变Shell进程Resource Limit,允许core⽂件最⼤为1024K: $ ulimit -c 1024
如下图所示:
通过写一个死循环程序产生一个Core Dump的例子:
signal.c:
#include
int main(){
printf("pid is : %d\n",getpid());
while(1)
;
return 0;
}
makefile:
signal:signal.c
gcc signal.c -o signal
.PHONY:clean
clean:
rm signal.i signal.s signal.o signal
结果:产生Core Dump
首先在后台执行上述例子中的死循环程序,然后使用kill命令给它发SIGSEGV信号。
结果如下图所示:
kill命令:
kill命令是调⽤kill函数实现的。kill函数可以给⼀个指定的进程发送指定的信号。raise函数可以给当前进程发送指定的信号(⾃⼰给⾃⼰发信号)。
include <signal.h>
int kill(pid_t pid, int signo);
//pid 是当前进程ID,signo表示要发送的几号信号
int raise(int signo);
//这两个函数都是成功返回0,错误返回-1。
abort函数使当前进程接收到信号⽽异常终⽌。
include <stdlib.h>
void abort(void);
//就像exit函数⼀样,abort函数总是会成功的,所以没有返回值。
CPU: 例如整数除以0
MMU: 例如段错误,访问非法内存,操作系统会发送11号信号
alarm信号:
#include
unsigned int alarm(unsigned int seconds);
//调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程SIGALRM信号, 该信号的默认处理动作是终止当前进程。
//这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
一个关于闹钟的小程序:
【功能】:在一秒钟之内不停地数数,一秒钟到了就被SIGALRM信号终止
#include
#include
size_t count = 0;
int main(){
alarm(1);
while(1){
++count;
printf("%lu\n",count);
}
return 0;
}
【部分结果】:
信号在内核中的表示示意图:
sigset_t:
signal函数:
【原型】:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
【作用】:
【参数】:
信号集操作函数:
sigsett类型对于每种信号⽤⼀个bit表⽰“有效”或“⽆效”状态,⾄于这个类型内部如何存储这些bit则依赖于系统实现,从使⽤者的⾓度是不必关⼼的,使⽤者只能调⽤以下函数来操作sigset t变量,⽽不应该对它的内部数据做任何解释,⽐如⽤printf直接打印sigset_t变量是没有意义的。
#include
int sigemptyset(sigset_t *set); //所有位 置0
int sigfillset(sigset_t *set); //所有位 置1
int sigaddset(sigset_t *set,int signo); //指定信号位从0置1
int sigdelset(sigset_t *set,int signo); //指定信号位从1置0
int sigismember(const sigset_t *set,int signo); //判定signo number是否在位图中
sigprocmask:
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
#include
int sigprocmask(int how,const sigset_t *set,sigset_t *oset);
//返回值:若成功则为0,若出错则为-1
【参数】:
【注意】:
SIG_BLOCK | set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set |
---|---|
SIG_UNBLOCK | set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值,相当于mask=set |
sigpending:
#include
int sigpending(sigset_t *set);
//函数调用成功返回0,否则返回-1
sigpending函数返回在送往进程的时候被阻塞挂起的信号集合。这个信号集合通过参数set返回。