您的位置首页>企业动态>

概述linux异步信号handle

导读大家好,我是极客范的本期栏目编辑小友,现在为大家讲解概述linux异步信号handle问题。当我还是linux编程的初学者时,我一直认为异步信号处

音频解说

大家好,我是极客范的本期栏目编辑小友,现在为大家讲解概述linux异步信号handle问题。

当我还是linux编程的初学者时,我一直认为异步信号处理是一件神奇的事情。用户程序可以通过使用系统调用(如singal)为某个信号注册一个信号处理函数(handle function)。

程序的二进制代码在内存中有明确的执行流程。为什么程序收到异步信号后“中断”,然后跳转到句柄函数运行?内核怎么能让程序这样跳?暂时修改程序的可执行代码是不可能的?

后来学习了一些内核知识,才意识到原来的进程并不是收到信号就马上“中断”的,而是记录了在进程控制结构(task_struct)中收到了某个信号,然后在进程即将从内核状态返回到用户状态时,进程被“中断”并调用了handle函数。

用户进程何时从内核模式返回用户模式?一般有三种情况:系统调用(用户进程主动进入内核)、中断(用户进程被动进入内核)、调度执行(用户进程从等待执行变为正在执行)。

进程从内核模式返回到用户模式需要一定的时间。但是,这个时间通常很短,至少时钟中断会以较大的频率(例如,每毫秒一次)将用户进程带入内核(当然,只针对正在执行的进程)。

当进程即将从内核模式返回到用户模式时,如果有信号需要处理,就会调用相应的handle函数(当然可能没有注册handle,然后内核默认处理信号)。请注意,该进程仍处于内核模式。内核如何在用户模式下调用句柄函数?

直接打电话可以吗?当然不是。内核代码在高CPU特权级别下运行。如果直接调用句柄函数,句柄函数将在相同的CPU权限下执行。用户可以在手柄功能中做他想做的任何事情。

因此,调用句柄必须首先返回用户模式。但是,返回用户模式后,程序流程不受内核控制。内核真的可以临时改变用户进程的可执行代码吗?

内核的实际实践还是挺聪明的。在一个进程进入内核之后,用户会在它对应的内核堆栈上留下一个返回地址,这样进程就可以返回了。调用内核句柄函数的方式是暂时改变栈上的返回地址,然后按照原来返回用户模式的流程返回。结果一返回,就转到句柄函数。(当然,需要修改的不仅仅是返回地址,还有整个调用栈。)

虽然回邮地址已被临时更改,但用户流程最终将返回到原始回邮地址。那么,原始的返回地址及其调用栈应该保存在哪里呢?进程的内核栈空间有限,还需要处理handle函数中可能出现的系统调用,所以内核把这些信息放在内核栈上是不现实的,只能压到用户栈上。

当句柄函数被执行时,执行流程应该返回内核。同样,由于CPU的特权级别不同,从句柄函数返回内核时,RET指令不能简单地用来返回。需要执行系统调用。

为什么在执行完句柄后回到内核,然后从内核返回到原来的返回地址?直接回原回邮地址非常方便。而且这样做并不难,因为原来的返回地址及其调用栈已经被压到了用户栈上,内核只需要篡改句柄函数的调用栈。

1.回到原来的返回地址不是回到那个地址,而是恢复整个站点(主要是寄存器等。).当然,内核也可以在用户栈上按一些代码来完成这些事情;

2.现在可能要处理的信号不止一个,所以最好让用户进程返回内核,继续处理其他信号。

要返回内核,首先,内核在返回句柄函数之前,在用户堆栈上按下一个返回地址,这样它就可以在从句柄返回时返回到指定的地址。这个指定的地址实际上是在进程的用户栈上,内核在这个地址上放了几个指令(把可执行代码放在栈上),让进程调用一个叫做sigreturn的系统调用。

返回句柄函数之前的用户堆栈大致如下:

原始数据-“调用sigreturn的指令(将其地址设置为a)”-“原始返回地址及其调用堆栈-“返回地址(值为a)”-句柄的堆栈变量。

sigreturn指令放在内核句柄函数的调用栈上,这是linux2.4中的做法,每次调用用户的句柄函数,都需要将这些指令复制到用户栈中,这不是很好。

">linux2.6有一个叫vsyscallpage的页面,上面包含了内核为用户程序准备的一些指令,其中就包括调用sigreturn指令。这个vsyscall页被映射到每个进程的虚拟地址空间靠近末尾的部分,被所有用户进程共享,对于用户进程是只读的。这样,handle函数的调用栈上就不需要再塞入sigreturn指令了,直接将handle函数的返回地址设为vsyscall页中对应的代码即可。

为了让handle执行完以后自动调用sigreturn返回内核,内核做了很多事情。那么可不可以约定好,让用户自己去调用sigreturn呢?

当然,这是可以的。只是为了让信号处理机制成为一套完整的机制,内核并没有这么做。否则用户在handle函数里面忘记调用sigreturn的话,可能莫名其妙地进程就崩溃了。而编译器也很难找出这样的错误。

进程调用sigreturn系统调用重新进入内核后,压在用户栈上的原始返回地址及其调用栈被获取。最终内核又会修改栈,让进程返回用户空间时返回到这个原始返回地址上。

郑重声明:本文版权归原作者所有,转载文章仅为传播更多信息之目的,如作者信息标记有误,请第一时间联系我们修改或删除,多谢。