本文分析基于Android 15
Android世界中的Signal无处不在,譬如用于杀死进程的信号9(SIGKILL),又或者数据访问异常时所产生的信号11(SIGSEGV)。上层开发者对于它们或许很熟悉,但主要集中在注册和处理,底层的运作逻辑可能知之甚少。因此本文画了一张图,希望完整地展示这个过程。其中会涉及内核和CPU相关的知识,只有不把它们当黑盒,从根上梳理这件事情,理解才能够透彻。
阐述过程可能会比较冗长,甚至有些boring,如果不感兴趣的话可以只看如下几点总结。
- 信号有两种形式。一种是进程内的任意线程都可以处理的信号,譬如通过kill发送的信号;另一种是只能某个特定线程处理的信号,譬如通过tgkill发送的信号。
- 除了通过kill、tgkill这种方式主动发送信号,系统还可以自己产生信号。譬如数据访问异常,异常处理阶段(内核态)会产生SIGSEGV并发送给当前线程。
- 一个线程只会在内核态返回到用户态之前去检查信号。除了系统调用,中断、Abort都会产生用户态/内核态的状态切换。举个例子,当线程在时间片结束时还在运行,会有时钟中断到来让它进行状态切换,并在内核态将CPU让渡出来。因此,上层的视角中信号总会很快响应,是因为用户态/内核态的切换足够频繁。
- 如果在调用栈中发现”[vdso]”或”__kernel_rt_sigreturn”的帧,那通常是信号处理调用栈和常规调用栈的分界线。
- Fast unwind在穿越信号处理边界时可能会出现两帧相同的情况。
数据访问异常通常对应于ldr
和str
这样的内存操作指令,从上层的视角这条指令是原子操作,但深入到CPU内部,这条指令会被分解成多个操作,其中有个重要环节就是跟MMU(Memory Management Unit)打交道。MMU访问页表,将虚拟地址转换成物理地址并访问内存。如果这个过程出现异常,譬如访问的地址不可读写,那么MMU就会产生一个abort并告知CPU。Abort接着会触发CPU的异常(Exception),因此CPU将进入异常处理流程。
(一)异常状态切换,保存用户态上下文。
CPU处理异常时,处理器的硬件会自动将返回地址(此例子为PC值,表明异常处理结束后重新执行内存操作)存入ELR_EL1
系统寄存器,将PSTATE(Processor State)存入SPSR_EL1
。接着进行异常等级的切换,从EL0(Exception Level 0)切换到EL1,也即大家熟知的用户态切换到内核态。切换完成后,处理器会将对应的异常向量表入口地址装载到PC寄存器,并开始执行。这样一来,CPU就切换到了新的执行流。
异常处理的开头都会保存上下文(Context),这里的上下文指的是寄存器中的值。如上图所示,除了X0-X30这些通用寄存器,还有三个寄存器也会被保存,分别是SP_EL0
、ELR_EL1
和SPSR_EL1
。SP_EL0
是用户态的SP寄存器,之所以加个EL0后缀,是因为每个异常等级都有自己的SP寄存器,这样用户态和内核态的栈便由于SP的不同而分隔开来。另外如前文所述,ELR_EL1
里保存的是用户态的PC值,SPSR_EL1
保存的是用户态的PSTATE。之所以用这两个寄存器中转一道,而不是直接从原有寄存器里拿,是因为PC和PSTATE寄存器在异常处理过程中依然会被使用,原来的值会被破坏掉。最后,这些寄存器的信息会保存在内核栈的栈底。
(二)异常处理中,给当前线程发送SIGSEGV。
数据异常的处理会调用el0_da
,da表示data abort。这里面会给当前线程发送SIGSEGV。一个进程有多个线程,因此信号也有两种形式。一种是进程内的任意线程都可以处理的信号,譬如通过kill发送的信号;另一种是只能某个特定线程处理的信号,譬如通过tgkill发送的信号。每个线程在内核里都有一个对应的task_struct结构体,发送给进程的信号会塞入shared_pending字段,发送给线程的信号则会塞入pending字段。pending字段的背后是一个bitmap,用bit的1或0表示信号的有无。在这个例子里,el0_da
会将当前线程的pending的第11个bit置为1,表示发送了信号11。
(三)返回到用户态之前,检查信号、处理信号。
异常处理结束后,需要从内核态返回到用户态。这之前会去检查task_struct中的pending和shared_pending字段,一旦发现有信号就会对它们进行处理。需要注意的是,这里的处理表示的是内核态的处理,而非用户态我们自定义的处理函数。核心工作有两个:一是构造信号栈,将上下文及信号的具体信息都转存到信号栈的栈底。另一个是修改保存的上下文,为接下来调用用户态的信号处理函数做准备。信号处理函数一般具有三个参数,分别表示信号值、信号的具体信息以及之前的用户态上下文。
static void signal_handler(int signal_number, siginfo_t* info, void* context)
这些参数通过X0、X1和X2寄存器传递,因此我们需要对内核栈栈底的这三个寄存器值进行修改。此外,X30被修改为__kernel_rt_sigreturn
,表示信号处理结束后跳转到这个函数;SP_EL0填入siginfo的地址,也即信号栈往上增长的起始地址;ELR_EL1填入信号处理函数的指针。
(四)内核态返回到用户态,恢复用户态上下文。
将保存在内核栈栈底的上下文信息搬运到寄存器,接着完成异常状态切换,处理器会自动将ELR_EL1
的值装载进PC寄存器。由于ELR_EL1
里的值为信号处理函数的指针,因此接下来CPU的执行流会进入信号处理函数。此时,X30(又名Link Register,LR寄存器,里面存的值是函数的返回地址)存的是__kernel_rt_sigreturn
。
(五)信号处理函数中,修改原始上下文的PC值。
此时,保存在信号栈的原始上下文指的是异常发生时用户态的寄存器信息。通过信号处理函数的第三个参数,我们可以修改其中的值。这里我们将PC值改为PC+4,表示信号处理结束再次返回时将跳过之前出问题的那条ldr指令。此举仅仅是为了展示流程,不建议在实际信号处理函数中这么做。
(六)信号处理函数结束,调用__kernel_rt_sigreturn。
由于第四步中将__kernel_rt_sigreturn
作为LR寄存器的值,因此信号处理函数结束时ret
指令会跳入这个函数,即便这个函数之前没调用过,这是一个比较tricky的设计。__kernel_rt_sigreturn
函数位于vdso(virtual dynamic shared object)区域,它是内核在用户空间维护的一个小型共享库,其内部实现非常简单,表示通过系统调用进入内核空间。
mov x8, #__NR_rt_sigreturn
svc #0
(七)用户态切换到内核态,保存信号处理结束时的上下文。
这些上下文寄存器中,重要的只有SP一个。因为此时SP指向sigframe的起始地址,通过它可以访问到栈底的uc_mcontext信息,进而获取信号栈中保存的上下文。
(八)系统调用中,将信号栈保存的上下文信息转存到内核栈。
信号栈中保存的上下文是异常发生时的用户态上下文,不过我们可以在信号处理函数中对其进行修改。__NR_rt_sigreturn
是系统调用号,值为139。它所对应的内核函数为rt_sigreturn
,其中做的事就是将信号栈中保存的上下文转存到内核栈中。
(九)内核态返回到用户态,恢复上下文。
兜兜转转,我们终于要回到一开始的地方了。当初用户态的上下文存在内核栈,之后又搬运到信号栈,接着再次存回内核栈,如今,它终于要回到寄存器里了。和当初不同的是,PC值被信号处理函数改成了PC+4,因此再次返回用户态时它将执行ldr
后面的一条指令。
后记
很早之前我就有个感觉,一个应用工程师和内核工程师在一起讨论问题,经常是鸡同鸭讲。每个人都局限在自己的视角,好比盲人摸象,有人说这是一根粗壮的腿,有人说这是扑扇的耳朵。但很多基础的概念是贯穿全局的,局限在任何一个位置都无法看清它的全貌。因此,我碰到过太多的工程师将不同的名词混杂在一块,对很多基础的概念支支吾吾。这让我想起一个更加庞大的话题,即工作对人的异化,流水线上的每个人只看得见自己手头上的那点事,他们不再去思考前因后果,也扼杀了所有的求知欲,剩下的只是无尽的麻木,和面对金钱时的两眼放光。
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/22059,转载请注明出处。
评论0