信号signal,是Linux进程间通信(IPC)的基础机制之一,也是Linux内核的进程管理的关键机制之一。
例如:
1,程序中经常出现的段错误SIGSEGV,就是系统内核检测到内存错误之后发给进程的信号,结果就是进程退出并留下core文件,以备程序员查BUG。
2,手工杀进程时发送的kill -9 进程pid,也是给目标进程发送信号9(SIGKILL),让进程退出。
3,还一个是TCP连接被对方关闭之后的SIGPIPE,也是常见的信号。
在服务器程序里需要忽略该信号,防止服务被它打断。
4,检测另一个进程是否还存在,则可以发送信号0。如果不存在时会返回-1并且设置ESRCH错误码,存在则返回0。
5,最后一个就是子进程退出时会给父进程发送SIGCHILD,用于提示父进程调用wait()回收子进程的遗留资源。
下图为Linux man手册对发送信号的kill()函数的介绍,只有两个参数,一个是进程号,一个是信号数字。
系统有默认的信号处理函数,一般都是让进程退出,也可以自定义或者忽略某些信号(SIGKILL和SIGSTOP除外,这俩不能忽略,不可拦截)。
信号处理函数的设置是signal()系统调用,它的定义见下图:
两个参数,一个是信号数字,一个是处理函数的指针。如果把处理函数的指针设为SIG_IGN则忽略该信号。
处理函数只有一个参数,int型的信号数字,返回值类型为void,即没有返回值。
它是在进程收到信号之后由Linux系统使用的函数,与进程的代码是异步执行,没法把信号处理结果同步返回给进程代码,所以返回值为void。
在我们上篇模拟fork()的程序上稍加修改,就可以模拟信号处理。
如下图,先在task_t里加一个signal变量。
这里用了intptr_t类型,它的字节数与void*一样都是8,计算偏移量比较简单。
这次因为参数太多了,我们把task_t的指针传给switch_to()函数,然后用偏移量去访问它的各个成员变量。
下图是信号处理函数t_signal(),和发送函数t_kill(),为了简单直接把task_t的指针传过去,就不需要根据id去找这个指针了。
发送信号的t_kill()函数就是设置t->signal = signal,t为目标进程的task_t指针,这样信号就发送到了。
信号处理函数t_signal()在进程调度的时候异步调用,与进程的主代码无关。
我们在进程每次被调度执行的时候,在返回主代码之前,处理信号。
24(%rsi)存的就是主代码的返回地址。
%rsi存的就是即将被调度执行的进程task_t的指针。
第16行的%rdi就是信号处理函数t_signal()的参数,它只有一个参数。
1,我们先把主代码的返回地址压栈,
2,然后把参数task_t* t放到%rdi里,
它之前作为switch_to()函数的第二个参数在%rsi里,
3,最后跳转t_signal()函数执行,
这样在t_signal()返回时就会把栈上的主代码返回地址弹出,返回主代码。
t_signal()函数可以写得更复杂一点,例如根据信号值选择不同的处理,不同信号的处理函数可以作为指针设置在task_t结构里,这样就类似Linux的样式了。
实际上,Linux系统因为有内核态和用户态,调度器是在内核态运行的,信号处理函数是在用户态运行的,还有个从内核返回用户态的过程。
运行效果图:
举报/反馈