linux 信号 0,linux0.11信号机制

本文简单描述linux0.11信号机制的实现

一:有关信号

当进程收到一个信号后,进程根据相关设定调用信号处理函数。

有三类信号处理方式:默认处理方式、忽略信号方式、执行用户设定的信号处理函数。

发送信号的方式:按下相应的键(如CTRL+C)、使用kill命令或函数向指定进程发送信号。

typedef void sig_func(int);

sig_func *signal(int signr, sig_func *handler);

当在进程中调用signal(signo, handler)之后,

如果进程收到信号signo,则进程会执行handler指向的函数。

假设有进程A和进程B,当进程A给进程B发送了一个信号后,那进程B的信号处理函数什么时候执行?

若要执行进程B的信号处理函数,则进程B必须处于执行状态。也就是说只有当调度程序调度到了进程B后,

才可能执行进程B的信号处理函数。否则进程B不处于可执行状态,收到了信号也没用。

若进程B自己调用kill函数,给自己发送了一个信号,我们会假定这个信号处理函数会立即执行。

因此在执行系统函数kill之后会立即处理进程B的信号。

从这两点可以意识到,内核需要在时钟中断和系统调用后对当前进程的信号进行处理。

需要在时钟中断时是因为时钟中断会调用schedule函数,因为这是分时系统,

如果进程A给B发了信号,而且现在调度到了B,那理所当然要执行B的信号处理函数。

二:linux0.11的信号机制

以 kill 函数为例来简单说明大致流程, 下面再来详细描述内核中的do_signal函数。

当以kill函数给当前进程发送一个信号之后。

因为这是个系统函数,因此会执行int 0x80进入system_call的入口点

_system_call:

cmpl    $nr_system_calls-1,    %eax        # %eax保存kill函数的调用号

ja        bad_sys_call                              # 无效的系统调用

push    %ds

push    %es

push    %fs

push    %edx

push    %ecx

push    %ebx                                      # 相关数据入栈

.....................

call    _sys_call_table(,%eax,4)               # 执行系统调用, 这里就是 sys_kill 函数了

pushl    %eax                                      # 系统调用的返回值入栈,也即是 sys_kill 的返回值

......................

ret_from_sys_call:

.....

pushl    %ecx         # %ecx中保存了信号的信号值。

call    _do_signal     # 对信号进行处理

popl    %eax          #  将信号值出栈

popl    %eax          #  将系统调用返回值出栈,  也就是sys_kill的返回值存入%eax寄存器

popl    %ebx

popl    %ecx

popl    %edx

pop    %fs

pop    %es

pop    %ds

iret

可见每次系统调用之后,可能会执行ret_from_sys_call,进而对信号进行处理。

除了在_system_call里会这样, 在一些中断下也会调用ret_from_sys_call,时钟中断就是其中之一。

现在已经知道内核是“何时”来处理进程的信号了。

三:do_signal函数。

do_signal的功能主要是设置了内核的堆栈和应用的用户堆栈,设置好堆栈后,

当执行ret_from_sys_call最下面的iret指令的时候,去自动执行进程的信号处理函数。当信号处理函数执行完成后,又会接着进程的下一条指令去执行。

下图是《Linux0.11内核完全注释》一书里的,很好的显示调用do_signal前后的堆栈变化。

bcfe624819c22681027c73df4ebb46cb.png

左边的为内核态堆栈,就是在执行call _do_signal之前的堆栈内容。

do_signal执行如下操作

1:将堆栈中的eip值,保存到old_eip中,old_eip就指向了用户程序中即将执行代码

2:将eip执行信号处理函数。这样当执行ret_from_sys_call中的iret时,会执行cs:eip指向的代码,也就是信号处理函数。

3:将用户态堆栈的esp的值,向下移7或8个长字(32位)

4:然后将sa_resotrer, signr等值放入堆栈, 见图右边的用户堆栈。

完成上述操作后,do_signal执行完毕,返回到ret_from_sys_call中,

ret_from_sys_call执行一些pop操作后执行iret指令, 这时会跳转到信号处理函数去执行。

当信号处理函数执行完后,会执行ret操作(函数的返回使用ret,中断的返回使用iret),这时会将sa_restorer存入eip,

因此接下来就会执行sa_restorer

sa_restorer会恢复用户堆栈

__sig_restore:

addl    $4,    %esp

popl    %eax              # 将系统调用的返回值存入eax

popl    %ecx

popl    %edx

popfl

ret

当执行完popfl之后,明显用户堆栈里面只剩下old_eip了, 因此执行ret,程序就会跳转到cs:old_eip去执行,也就是系统调用的下一条用户指令了。

至此信号处理函数已经执行,系统调用也已返回,用户程序无忧无虑的继续执行。

阅读(3141) | 评论(0) | 转发(0) |

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值