》小明去BAT公司面试面试官:我叫神马小明:小明面试官:时钟中断是什么?小明:手表坏了,所以我不跑了面试官:???没关系。面试官接着问:时钟中断处理程序出现pagefault怎么办?小明嘀咕:为什么表坏了就没液了?这是什么表啊。。。液压手表?加多少油...面试官:小明同学,滚...”以上是一句玩笑话,但这是一道面试必答题,考查大家对中断和异常处理的理解程度。如果对中断和异常没有深入的了解,是很难胜任Linux相关的研发工作的。如果你能搞清楚这道题的来龙去脉,你面试BAT或者其他大公司就没有问题了。无独有偶,今天本大叔的VIP跑团里,有人问了这样一个问题,中断处理函数ISR能不能出现pagefault中断?当页面错误发生时会发生什么?【如果你也想加入VIP跑团与笨叔叔交流,请点击“阅读原文”订阅旗舰文章】01什么是分页?首先我们搞清楚两件事:一是中断,二是缺页中断。他们都是中断吗?答案显然是否定的。中断和页面错误是兄弟,但不是同一个人。在大多数架构中,pagefault实际上就是pagefault,英文叫做“PageFault”。我不知道从哪本教科书中“页面错误”被称为页面错误。实际上,它会被翻译成页面错误。比较合适,不然同学们天真地认为缺页中断和普通的外设中断没什么区别。在ARM32处理器中,中断和异常是明确划分的。中断包括IRQ和FIQ中断。我们常说的常见外设中断就是IRQ中断。异常分为数据中止、未定义中止和预取异常。从异常向量表可以看出,当中断和异常发生时,它们的处理入口是不同的。当外设中断发生时,其处理步骤如下:在vector_IRQ汇编函数中运行到IRQ向量表,将当前的lr和spsr保存到中断栈中,在SVC模式下跳转到SVC模式,判断中断的发生interrupt在内核态或用户态,将上下文发生时的中断保存到进程的内核栈(SVC模式),跳转到do_IRQ函数执行中断处理函数,判断是否需要抢占中断并返回上面笨叔叔总结的IRQ中断。前半部分处理了一些要经历的事情(我们在这里讨论的时候忽略了下半部分的中断)。这次小伙伴问的是第6步出现pagefault的问题。即在do_IRQ函数中,访问的内存不存在,或者页面的属性不正确,导致pagefault异常,那怎么办呢?经过笨叔叔的以上分析,我们就明白了这个问题的本质。【关于中断处理的全过程,建议你看《奔跑吧Linux内核》的第5.1章】02No.假设在中断过程的第6步发生pagefault,你访问了不该访问的内存,会导致一个马蜂窝,接下来会发生什么?(ARM处理器中的pagefault异常大概有两种,一种是进程地址空间的pagefaultexceptiondo_page_fault,另一种是vmalloc的pagefaultexceptiondo_translation_fault。后者比较简单,vmallocpage故障中断只是把init_task进程的页表复制到当前进程,我们暂时考虑更复杂的前者)。首先,异常处理的汇编代码部分和中断类似,都是经过异常模式的栈,然后跳转到当前进程的内核栈。但是异常模式有区别,就是中断处理过程,允许休眠和调度。所以当外围中断ISR处理过程中出现异常时,会出现如下情况。如图所示,在被中断的当前进程的内核栈中,会出现一个一个的栈帧,最上面的是中断发生的上下文点的栈帧,后面是do_IRQ的栈帧,以及然后是用于外围ISR处理函数的特定硬件堆栈帧。接下来是发生异常时保存的栈帧。既然在异常处理函数中允许开启中断和休眠,那么“层层嵌套”,套路太深,需要很长时间才能回到原来的中断上下文?如图,中断A发生在①处,然后保存本场景的上下文。然后在②处出现了异常,这个场景的上下文也被保存了下来。在异常处理过程③中,中断B再次发生,中断B的上下文此时发生。假设调度发生在中断B返回时,猴年能否回到③?假设调度发生在异常处理期间,同样如此。猴年能回到③吗?如果出现上述情况,中断A什么时候可以返回?因为中断控制器还在等待程远给他一个吻,这个吻叫做“EOI”,英文叫做“endofinterrupt”。或许是时间太久了,我已经等不及这个吻了。..如果上述场景发生在时钟中断中,会有什么后果?因此,为了防止在异常处理上深陷陷阱,我们来看看Linux内核做了哪些限制?在do_page_fault()函数中判断当前上下文是否在中断上下文中,由in_atomic()函数判断。如果它在中断上下文中,它会转到do_kernel_fault,并且通常会打印一个oops错误。那么in_atomic()是如何判断当前是否在中断上下文呢?这里的主要目的是判断thread_info中的preempt_count计数。【这里的preempt_count是什么意思?傻大叔这里就不解释了,建议大家看看《奔跑吧Linux内核》]第3章图3.2是不是在中断处理过程中设置了preempt_count计数?IRQ处理:irq_handle->gic_handle_irq()->handle_domain_irq()->irq_enter->_irq_enter这次终于知道IRQ中断处理会设置preempt_count计数的HARDIRQ_OFFSET域。【这部分不清楚的可以阅读《奔跑吧Linux内核》第5.1.5章】奔跑吧Linux社区坚持原创文章,原汁原味,绝不转载!喜欢的话记得打赏转发,谢谢!订阅Linux社区旗舰视频与笨叔叔一起跑、交流、跑!
