信号
信号其实是一个很像中断的机制,事先注册号信号的处理函数,或者使用默认的处理函数, 当接收到奥信号的时候,调用对应的处理函数进行处理。中断呢,系统默认有一个中断向量表,存储中断处理程序,当接收到中断的时候,从中断向量表中获取中断处理程序进行处理。只是中断是在内核态处理,信号是在用户态处理。
信号的类型
操作系统中,为了响应各种各样的事件,也是定义了非常多的信号。我们可以通过 kill -l 命令,查看所有的信号。
1 |
|
上面的命令是在mac下面敲的,具体到linux显示会不一样,但是内容是代表一样的意义,都是信号的类型。具体类型的说明, 可以通过 ‘man signal’查看。
信号处理
一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。
1.执行默认操作。Linux 对每种信号都规定了默认操作
2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。
3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SEGSTOP,它们用于在任何时候中断或结束某一进程。
如果我们不想让某个信号执行默认操作,一种方法就是对特定的信号注册相应的信号处理函数,设置信号处理方式的是 signal 函数。我们在 Linux 下面执行 man signal 的话,会发现 Linux 不建议我们直接用这个方法,而是改用 sigaction。这两个函数的区别就是sigaction掌控的粒度更小,更灵活。而signal没有那么灵活而已。具体使用的时候,可以去看看函数的说明,决定使用哪个。
信号发送
有时候,我们在终端输入某些组合键的时候,会给进程发送信号,例如,Ctrl+C 产生 SIGINT 信号,Ctrl+Z 产生 SIGTSTP 信号。有的时候,硬件异常也会产生信号。比如,执行了除以 0 的指令,CPU 就会产生异常,然后把 SIGFPE 信号发送给进程。再如,进程访问了非法内存,内存管理模块就会产生异常,然后把信号 SIGSEGV 发送给进程。
对于硬件触发的,无论是中断,还是信号,肯定是先到内核的,然后内核对于中断和信号处理方式不同。一个是完全在内核里面处理完毕,一个是将信号放在对应的进程 task_struct 里信号相关的数据结构里面,然后等待进程在用户态去处理。当然有些严重的信号,内核会把进程干掉。但是,这也能看出来,中断和信号的严重程度不一样,信号影响的往往是某一个进程,处理慢了,甚至错了,也不过这个进程被干掉,而中断影响的是整个系统。一旦中断处理中有了 bug,可能整个 Linux 都挂了。
有时候,内核在某些情况下,也会给进程发送信号。例如,向读端已关闭的管道写数据时产生 SIGPIPE 信号,当子进程退出时,我们要给父进程发送 SIG_CHLD 信号等。
最直接的发送信号的方法就是,通过命令 kill 来发送信号了。
另外,我们还可以通过 kill 或者 sigqueue 系统调用,发送信号给某个进程,也可以通过 tkill 或者 tgkill 发送信号给某个线程。虽然方式多种多样,但是最终都是调用了 do_send_sig_info 函数,将信号放在相应的 task_struct 的信号数据结构中。
kill->kill_something_info->kill_pid_info->group_send_sig_info->do_send_sig_info
tkill->do_tkill->do_send_specific->do_send_sig_info
tgkill->do_tkill->do_send_specific->do_send_sig_info
rt_sigqueueinfo->do_rt_sigqueueinfo->kill_proc_info->kill_pid_info->group_send_sig_info->do_send_sig_info
当接收到信号的时候,内核会把信号放到task_struct结构里面, <32的信号放在信号集里面, 其他信号放在一个链表里面。 对于<32的信号可能会丢失,即不可信信号,其他的就是可信的信号。有信号之后,会置一个标志位,当任务从中断或者系统调用返回后,检查标志然后进行处理。
信号处理
从系统调用或者中断返回的时候,都会调用 exit_to_usermode_loop函数。
1 |
|
如果检查有信号标志_TIF_SIGPENDING, 就调用do_signal函数进行信号处理。 这个时候,我们要看,是否从系统调用中返回。如果是从系统调用返回的话,还要区分我们是从系统调用中正常返回,还是在一个非运行状态的系统调用中,因为会被信号中断而返回。如果是因为信号中断而返回的话, 会在栈桢里面添加中断信号相关的上下文,然后出栈直接调到信号处理函数,而不会调用系统调用前的代码行。
万变不离其宗,其实如果有一些基础的系统知识,不如这里知道栈的用处,那么读代码的时候就比较容易理解了。
行动,才不会被动!
欢迎关注个人公众号 微信 -> 搜索 -> fishmwei,沟通交流。