- 1.信号的产生
- 2.信号的存储与执行
- 参考资料
使用 kill -l 可查看所有的信号,其中1- 31是普通信号(对于两个以上的相同信号会丢失,因为只有一位位图保存);34 - 64是实时信号(用链表队列形式将实时信号进行保存,可以保存多份)。
| Number | Name | Default action | Corresponding event |
|---|---|---|---|
| 1 | SIGHUP | terminate | 终端线挂起 |
| 2 | SIGINT | terminate | 来自键盘的中断(Ctrl + c) |
| 3 | SIGQUIT | terminate | 来自键盘的退出(Ctrl + ) |
| 4 | SIGILL | terminate | 非法指令 |
| 5 | SIGTRAP | terminate and dump core | 跟踪陷阱 |
| 6 | SIGABRT | terminate and dump core | 来自abort函数的终止信号 |
| 7 | SIGBUS | terminate | 总线错误 |
| 8 | SIGFPE | terminate and dump core | 浮点异常( 计算1 / 0,Floating point exception) |
| 9 | SIGKILL | terminate* | 杀死进程(无法被signal自定义捕捉,系统级杀死进程) |
| 10 | SIGUSR1 | terminate | 用户定义的信号1 |
| 11 | SIGSEGV | terminate and dump core | 无效的存储器引用(野指针、segmentation fault) |
| 12 | SIGUSR2 | terminate | 用户定义的信号2 |
| 13 | SIGPIPE | terminate | 向一个没有读用户的管道写操作 |
| 14 | SIGALRM | terminate | 来自alarm函数的定时信号 |
| 15 | SIGTERM | terminate | 软件终止信号 |
| 16 | SIGSTKFLT | terminate | 协处理器上的栈故障 |
| 17 | SIGCHLD | ignore | 一个子进程暂停或终止 |
| 18 | SIGCONT | ignore | 继续进程如果该进程停止 |
| 19 | SIGSTOP | stop until next SIGCONT* | 不来自终端的暂停信号 |
| 20 | SIGTSTP | stop until next SIGCONT | 来自终端的暂停信号(Ctrl + z) |
| 21 | SIGTTIN | stop until next SIGCONT | 后台进程从终端读 |
| 22 | SIGTTOU | stop until next SIGCONT | 后台进程从终端写 |
| 23 | SIGURG | ignore | 套接字上的紧急情况 |
| 24 | SIGXCPU | terminate | CPU时间限制超出 |
| 25 | SIGXFSZ | terminate | 文件大小限制超出 |
| 26 | SIGVTALRM | terminate | 虚拟定时器期满 |
| 27 | SIGPROF | terminate | 剖析定时器期满 |
| 28 | SIGWINCH | ignore | 窗口大小变化 |
| 29 | SIGIO | terminate | 在某个描述符上可执行I/O操作 |
| 30 | SIGPWR | terminate | 电源故障 |
所有的信号都是由操作系统发送的,因为操作系统会考虑独立和安全
- 键盘按键输入,但是只能用来终止前台进程。
以下程序可以用来查看按键输入的信号对应的number
#include#include #include #include void handler(int signum) { printf("receive signal: %dn", signum); exit(1); } int main() { for (int i = 1; i <= 31; i++) { signal(i, handler); } while(1) { printf("------------------------------n"); sleep(1); } return 0; }
- 程序中的异常问题
本质是程序中存在异常问题导致软件或硬件的错误,被操作系统识别后,操作系统向对应的进程发送相应的信号。
Floating point exception(8) segmentation fault(11)
异常退出会设置退出码和退出信号,有时也会设置core dump标志位为1(不是所有情况)。
ulimit -a # 查看core的大小,若core file size = 0,则不允许core dump。 ulimit -c 1024 # 设置core的大小,使得运行core dump gdb main
core.1331——1331是进程的PID。
(gdb) core-file core.1331 # 使用gdb事后调试,直接查看异常的位置
程序查看异常退出码以及core dump标志位:
#include#include #include #include #include int main() { int status = 0; if(fork() == 0) { while(1) { printf("child PID:%dn",getppid()); int *p = NULL; *p = 10; } } waitpid(-1, &status, 0); printf("exit code: %d , exit signal: %d , core dump : %dn", (status>>8)&0xff, status & 0x7f, (status >> 7) & 0x01); return 0; }
- 由系统函数产生
int kill(pid_t pid, int sig); //给进程发相应的信号 int raise(int sig); // 给自己发相应的信号 void abort(void); //SIGABRT
- 由alarm函数产生
#include2.信号的存储与执行#include #include int main() { int ret = alarm(1); // 返回值为剩余的几时数 while(1) { printf("hello worldn"); alarm(0); // 取消闹钟 } return 0; }
进程收到信号后不会立即处理,会将信号以位图的形式存储在进程的PCB中;当进程从内核态返回用户态的时候,进行检测并处理。
用户的数据和代码会被加载到内存,对应的虚拟内存和物理内存的映射叫用户级页表;而操作系统的数据和代码也会被加载到内存,对应的映射关系叫系统级(内核)页表,而这个页面只有一份,所有进程共享一份操作系统的内存。进程可以通过CPU中相应的寄存器切换用户和内核的访问权限。
内核态:执行系统的代码和数据,使用内核级页表
用户态:执行用户的代码和数据,使用用户级页表
信号的处理过程分为:
- 实际执行——递达:
- 默认动作(SIG_DFL : 0)
- 忽略 ( SIG_IGN : 1)
- 自定义捕捉(handler)
- 产生到递达间——未决:在信号位图中暂存
- 信号阻塞
OS可以使用系统调用接口设置位图的数据结构:sigset_t
#include#include #include int main() { sigset_t isset, osset; sigset_t pending; //清空结构体内容 sigemptyset(&isset); sigemptyset(&osset); //设置3号信号的屏蔽字——block位图 sigaddset(&isset, 3); //设置 sigprocmask(SIG_SETMASK, &isset, &osset); while(1) { printf("hello worldn"); sigemptyset(&pending); sigpending(&pending); // 读出pending的位图 int count = 0; int i = 1; for(; i <= 31; i++) { if(sigismember(&pending, i)) printf("1"); else printf("0"); } printf("n"); count++; if(count == 10) { sigprocmask(SIG_SETMASK, &osset, NULL); //将旧的信号屏蔽字写回 } sleep(1); } return 0; }
使用ctrl + 发现无法终止该程序,原因是3号信号被屏蔽了。另外使用kill -3 + PID也无法杀死该进程,但是会使得对应的pending位图置1。过10秒后,信号屏蔽字被重新置0,进程会默认立即终止,不会再执行后面的语句。
信号执行的过程可以总结为:
用户态进入内核态调用系统函数,从内核态返回用户态进行相应的信号检测。若无信号产生直接返回用户态,若产生信号,则执行信号的相应处理过程(默认、忽略、自定义捕捉),信号捕捉在用户态执行,执行完重新返回内核。
信号捕捉的接口函数:
sighandler_t signal(int signum, sighandler_t handler);
int sigaction(int signum, const struct sigaction *act, struct sigaction *oact);
struct sigaction
{
void (*sa_handler) (int); // 捕捉函数
...
..
sigset_t sa_mask; //设置进程捕捉过程中的屏蔽字
}
#include参考资料#include #include #include void handler(int signum) { while(1) { printf("signal number:%dn",signum); sleep(1); } } int main() { struct sigaction act; memset(&act, 0, sizeof(act)); //act.sa_handler = SIG_IGN; //将某一个信号设置成忽略 //act.sa_handler = SIG_DFL; // 将某一个信号设置成默认 act.sa_handler = handler; //在执行信号自定义捕捉时,系统会设置信号屏蔽字,防止自定义捕捉被调用多次 sigemptyset(&act.sa_mask); //手动设置信号的屏蔽字,当执行3号信号捕捉时,该2号的信号被屏蔽 sigaddset(&act.sa_mask, 2); sigaction(3, &act, NULL); // 注册3号信号 return 0; }
[1]: 《Computer Systems:A Programmer’s Perspective》



