蜗窝科技

请问一下关于中断发生内核堆栈切换的问题。

蜗窝讨论区存档 · Linux kernel技术问答 · 楼主 alone-wyr · 2016-08-08 · 6 帖

本文是原「蜗窝讨论区」的历史存档(2016-08-08),来自版块「Linux kernel技术问答」,共 6 帖。讨论区已停止服务,此处仅供查阅。

alone-wyr · 2016-08-08 23:58

对于X86架构来说,发生中断,可以进行切换,每个进程都有它的TSS(忘记了)大概就是 会有地方去初始化SP,就是设置了内核堆栈。

但是对于ARM架构来说,中断发生,硬件上并没有这个初始化sp_irq(或者说是sp_svc)这个动作。 在看了下中断发生的相关代码,也没有看到哪里有赋值的动作。

如果是在内核启动期间,设置好了sp_svc寄存器的值,给每个进程共用。 那就是内核堆栈的虚拟地址都是相同的? 进程切换会刷新页表,然后都会通过映射指向自己的内核堆栈... 那如果真的这样..应该还要保证进入和退出中断时候,sp_svc应该不要改变的?

是我疏忽了哪个地方?或者说是哪里理解错误。 望博主指点一二。 谢过~~

linuxer · 2016-08-09 16:21

这个问题在/irq_subsystem/irq_handler.html中有描述,你可以看看。

简单的说: 1、内核初始化的时候设置了sp_irq的值,也就是设定了irq mode下的stack,只有12个字节,所有的中断共用这个中断栈 2、中断栈只是一个跳板,最终要切换到发生中断那个点的current thread的内核栈上,也就是设定sp_svc

alone-wyr · 2016-08-09 20:42

谢谢你的回复。

确实sp_irq只是作为一个过渡,只保存几个寄存器,然后就会切换到了svc模式下了..也就切换为使用sp_svc. 我比较困惑的是,在x86,发生中断会通过每个进程的tss相应字段来设置sp0(内核堆栈).那就是说,在创建进程的时候,设置了tss内相应字段为分配thread_info的页帧地址上。这样在发生中断,其实就设置好了sp0了。

但是对于ARM来说,它并没有类似TSS这样会在发生中断的时候设置sp_irq或者是sp_svc.. 因此,我只能假设,所有进程发生中断的时候都是使用相同的sp_irq和sp_svc的值(虚拟地址.).(映射到各个进程对应的物理地址) 但是要是这样的话,就需要保证中断进入和退出的时候,不应该改变过sp_irq和sp_svc的值。

你的文章里面有写到函数cpu_init会设置idle任务的sp_irq sp_svc 等...

假设有这样的情况,CPU初始化sp_irp = a。进程A在执行时候发生中断,然后一些堆栈操作改变了sp_irp = b. 退出中断,接着切换到进程B执行。。进程B执行过程发生中断,它的sp_irq就是等于b了。。这样就有问题了。 因为我才假设,每个进程的虚拟地址a都是作为sp_irq的堆栈..然后进程发生中断后,到退出中断。都应该保证说sp_irq=a..

x86不同的是,发生中断硬件上会设置sp0.因此没有这些条条框框。

不知道,你是否明白我描述的疑问。

谢谢。

linuxer · 2016-08-10 09:48

发生了中断,最重要的是保存现场,在中断处理完之后,能够恢复现场就OK了,硬件现场(hardware context)的保存是由硬件和软件共同完成的。

对于ARM,当发生中断的那一刻,硬件会进行如下的动作: 1、将发生中断那一刻的CPSR保存在SPSR寄存器中 2、将返回地址保存在lr寄存器中(注意:这个lr寄存器是IRQ mode的lr寄存器,可以表示为lr_irq)

真正将hardware context保存到内核栈上是软件的行为。当然,目标是一开始就设定好了,将发生中断那一刻的hardware context保存到current task的内核栈上。不同的CPU有不同的设计,提供不同的软件和硬件的接口,对于X86,硬件帮忙做的事情更多,而ARM更希望你自己能够自力更生(更简单的HW logic意味着更少的晶体管,更少的功耗,而这也是ARM在移动平台上能够横扫Intel的根本原因)。怎么办?hardware context包括了cpu中的各种寄存器,想要将hardware context压入内核栈首先要获取current thread的内核栈指针,任何对cpu寄存器的使用将破坏硬件上下文,因此,linux kernel采用的方法是借用12个字节的中断栈。方法如下: 1、在中断栈上保存了发生中断那一点的r0值、PC值以及CPSR值。你可能会觉得:PC和CPSR需要保存吗?不是硬件已经帮忙保存lr_irq(虽然不是PC值,但是和PC值有固定的偏移关系)和spsr_irq中了吗?之所以保存spsr_irq和lr_irq的值,那是因为随后在切换到svc mode的时候需要修改spsr_irq和lr_irq的值。之所以保存r0,是因为后续会修改r0的值,把它做为一个scratch register。 2、切换到svc mode,具体完成下面两个步骤: (A)将处理器模式切换到svc mode (B)根据发生中断那一点的处理器模式,将pc设定为__irq_usr或者__irq_svc 在上面的过程中,lr_irq,spsr_irq和r0被破坏了,但是没有关系,相关信息已经保存在了中断栈上了(sp_irq) 3、一旦切换到SVC mode,ARM处理器看到的寄存器已经发生变化,这里的sp已经变成了sp_svc了。问题来了:sp_svc是什么值?在进程切换的时候就已经设定好了,sp_svc被设定为current thread的内核栈。这时候,除了被破坏的三个寄存器保存在中断栈上,其他的寄存器毫发无伤,软件只要将这些hardware context压入current thread的内核栈上即可。

回答你提出的具体问题: ---------------- 假设有这样的情况,CPU初始化sp_irp = a。进程A在执行时候发生中断,然后一些堆栈操作改变了sp_irp = b. 退出中断,接着切换到进程B执行。。进程B执行过程发生中断,它的sp_irq就是等于b了。。这样就有问题了。 因为我才假设,每个进程的虚拟地址a都是作为sp_irq的堆栈..然后进程发生中断后,到退出中断。都应该保证说sp_irq=a.. ---------------- sp_irp的值永远不会修改的,它只是个暂存数据的地方,就象临时变量,每次发生中断,临时用一下而已

alone-wyr · 2016-08-10 14:10

先谢谢你的帮助。

你的回复里面有如下: 问题来了:sp_svc是什么值?在进程切换的时候就已经设定好了,sp_svc被设定为current thread的内核栈。

对于x86的来说,它可以在进入中断的时候,硬件电路可以实现tss来设定sp0。而对于ARM来说,它是在切换进程的时候, 提前设置好了sp_svc.. 这里解决了我的困惑,回头在看一下进程切换的时候设置sp_svc的代码。

另外,你提到的sp_irq是不会改变的,确实是这样的。 可以看到保存r0//lr/spsr stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, @ save spsr 并不会修改sp_irq的值...只是作为一个临时缓存数据。

非常感谢。

electrlife · 2016-11-30 20:18

alone-wyr 写道: 先谢谢你的帮助。

你的回复里面有如下: 问题来了:sp_svc是什么值?在进程切换的时候就已经设定好了,sp_svc被设定为current thread的内核栈。

对于x86的来说,它可以在进入中断的时候,硬件电路可以实现tss来设定sp0。而对于ARM来说,它是在切换进程的时候, 提前设置好了sp_svc.. 这里解决了我的困惑,回头在看一下进程切换的时候设置sp_svc的代码。

另外,你提到的sp_irq是不会改变的,确实是这样的。 可以看到保存r0//lr/spsr stmia sp, {r0, lr} @ save r0, lr mrs lr, spsr str lr, @ save spsr 并不会修改sp_irq的值...只是作为一个临时缓存数据。

非常感谢。

不太明白,这段代码应该是irq中断时执行的吧? 这里的代码好像是sp改变了啊!stmia应该会使用sp增加8字节吧?

另外sp_irq只是临时的保存lr, r0, spsr到其irq stack上,那当切入svc模式后,这些被保存在irq stack上的寄存器值是不是也应该再次保存到current thread stack上。