Linux中断补充

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux中断补充相关的知识,希望对你有一定的参考价值。

参考技术A 在系统结构中,CPU工作的模式有两种,一种是中断,由各种设备发起;一种是轮询,由CPU主动发起。
中断IRQ:
中断允许让设备(如键盘,串口卡,并口等设备)表明它们需要CPU。一旦CPU接收了中断请求,CPU就会暂时停止执行正在运行的程序,并且调用一个称为中断处理器或中断服务程序(interrupt service routine)的特定程序。CPU处理完中断后,就会恢复执行之前被中断的程序。
中断分类:
硬中断+软中断
硬中断:
①非屏蔽中断:不能被屏蔽,硬件发生的错误:内存错误,风扇故障,温度传感器故障等。
②可屏蔽中断:可被CPU忽略或延迟处理。当缓存控制器的外部针脚被触发的时候就会产生这种类型的中断,而中断屏蔽寄存器就会将这样的中断屏蔽掉。我们可以将一个比特位设置为0,来禁用在此针脚触发的中断。
软中断:
是软件实现的中断,也就是程序运行时其他程序对它的中断;而硬中断是硬件实现的中断,是程序运行时设备对它的中断。

CPU之间的中断处理(IPI)
处理器间中断允许一个CPU向系统其他的CPU发送中断信号,处理器间中断(IPI)不是通过IRQ线传输的,而是作为信号直接放在连接所有CPU本地APIC的总线上。
CALL_FUNCTION_VECTOR (向量0xfb)

发往所有的CPU,但不包括发送者,强制这些CPU运行发送者传递过来的函数,相应的中断处理程序叫做call_function_interrupt(),例如,地址存放在群居变量call_data中来传递的函数,可能强制其他所有的CPU都停止,也可能强制它们设置内存类型范围寄存器的内容。通常,这种中断发往所有的CPU,但通过smp_call_function()执行调用函数的CPU除外。

RESCHEDULE_VECTOR (向量0xfc)

当一个CPU接收这种类型的中断时,相应的处理程序限定自己来应答中断,当从中断返回时,所有的重新调度都自动运行。

INVALIDATE_TLB_VECTOR (向量0xfd)

发往所有的CPU,但不包括发送者,强制它们的转换后援缓冲器TLB变为无效。相应的处理程序刷新处理器的某些TLB表项。

Linux内核中的中断栈与内核栈的补充说明

转自:http://blog.chinaunix.net/uid-12461657-id-3487463.html

中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入Linux设备驱动程序内核机制》第5章“中断处理”当中,基本上没怎么涉及到上述内容,只是在5.4节有些许的文字讨论中断栈在中断嵌套情形下可能的溢出问题。

本贴在这个基础上对内核栈与中断栈的话题做些补充,讨论基于x86 32位系统,因为64位系统下Linux内核关于栈的支持原理上是相同的,不过也有些特性属于64位特有的,比如IST(Interrupt Stack Table),如果可能将来会在processor版块发个帖子专门讨论。


1. x86下内核栈与中断栈是否共享的问题

我们知道Linux系统下每个用户进程都有个task_struct对象来表示,同时在处理器层面还对应一个TSS(Task State Segment),当中断发生时,用户进程或者处于用户态(ring 3)或者处于内核态(ring 0),如果是在用户态,那么会发生栈的切换问题,也就是会切换到内核态的栈,如果是在内核态,那么就没有栈切换的问题。但是x86处理器在ring 0上只有一个ESP,这意味着中断发生后,只能使用一个栈,这个栈就是内核栈(kernel stack)。处理器的硬件逻辑会将被中断进程的下条指令(CS,EIP)以及EFLAG压入栈,当然如果发生用户态栈向内核态栈的切换,处理器还会把用户态的(SS, ESP)也压入栈,此时使用的就是内核栈。这个行为属于处理器的硬件逻辑范畴,不是系统软件的行为。

至于x86下内核栈与中断栈是否共享的问题,其实是个内核设计的问题,换言之,中断栈可与内核栈共享,也可重新分配一个独立的中断栈。2.4的内核版本似乎采用中断栈与内核栈共享的设计,因为这种设计的好处是代码相对简单,如前所述,直接使用ESP0就可以了,但是负面因素是中断栈如果发生嵌套,可能破坏内核栈的一些数据,因为毕竟共享,所以栈空间有时候难免会捉襟见肘。所以在2.5内核版本开发中,来自IBM的一位大侠曾提交过一个补丁(详见http://lwn.net/Articles/21846/),试图在中断发生时,从内核栈switch到一个独立的中断栈中,后来也不知道被内核社区采纳了没有,总之我现在在3.2的内核源码中没有看到那位仁兄的补丁代码了,当然也可能是那个补丁已经长成现在的代码样子了。

现在的Linux内核中采用的是内核栈与中断栈分离的设计,下面我们从源码层面来看一看这种分离是如何完成的。

内核栈与中断栈分离的核心代码发生在do_IRQ() --> handle_irq() --> execute_on_irq_stack()
最后一个函数字面上的意思大约是在中断栈中执行中断处理例程,也就是说中断的处理函数会在独立于被中断进程的上下文中执行。execute_on_irq_stack的函数实现为:

<arch/x86/kernel/irq_32.c>


  1. static inline int
  2.  
  3. execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)
  4.  
  5. {
  6.         union irq_ctx *curctx, *irqctx; 
  7.         u32 *isp, arg1, arg2;
  8.  
  9.         curctx = (union irq_ctx *) current_thread_info(); 
  10.         irqctx = __this_cpu_read(hardirq_ctx);
  11.         /* 
  12.          * this is where we switch to the IRQ stack. However, if we are
  13.          * already using the IRQ stack (because we interrupted a hardirq 
  14.          * handler) we can‘t do that and just have to keep using the 
  15.          * current stack (which is the irq stack already after all)
  16.          */
  17.  
  18.         if (unlikely(curctx == irqctx)) 
  19.                 return 0;
  20.  
  21.         /* build the stack frame on the IRQ stack */
  22.         isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
  23.         irqctx->tinfo.task = curctx->tinfo.task;
  24.         irqctx->tinfo.previous_esp = current_stack_pointer;
  25.  
  26.         /* 
  27.          * Copy the softirq bits in preempt_count so that the 
  28.          * softirq checks work in the hardirq context.
  29.          */
  30.  
  31.         irqctx->tinfo.preempt_count = 
  32.                 (irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) |
  33.                 (curctx->tinfo.preempt_count & SOFTIRQ_MASK);
  34.  
  35.         if (unlikely(overflow))
  36.                 call_on_stack(print_stack_overflow, isp);
  37.  
  38.         asm volatile("xchgl %%ebx,%%esp \n" 
  39.                      "call *%%edi \n"
  40.                      "movl %%ebx,%%esp \n" 
  41.                      : "=a" (arg1), "=d" (arg2), "=b" (isp) 
  42.                      : "0" (irq), "1" (desc), "2" (isp), 
  43.                        "D" (desc->handle_irq)
  44.                      : "memory", "cc", "ecx");
  45.  
  46.         return 1;
  47. }
代码中的curctx=(union irq_ctx *) current_thread_info()用来获得当前被中断进程的上下文,irqctx = __this_cpu_read(hardirq_ctx)用来获得hardirq的上下文,其实就是获得独立的中断栈起始地址。中断栈的大小与layout与内核栈是完全一样的。接下来isp指向中断栈栈顶,最后的堆栈切换发生在那段汇编代码中:当前进程的内核栈ESP指针保存在EBX中,而中断栈的isp则赋值给了ESP,这样接下来的代码就将使用中断栈了。call语句负责调用desc->handle_irq()函数,这里会进行中断处理,设备驱动程序注册的中断处理函数会被调用到。当中断处理例程结束返回时,ESP将重新指向被中断进程的内核栈。(此处我们应该注意到内核栈中还保留着中断发生时处理器硬件逻辑所压入的CS, EIP等寄存器,所以在内核栈中做中断返回是完全正确的)。

2. 中断栈的分配

独立的中断栈所在内存空间的分配发生在arch/x86/kernel/irq_32.c的irq_ctx_init函数中(如果是多处理器系统,那么每个处理器都会有一个独立的中断栈),函数使用__alloc_pages在低端内存区分配2个物理页面(2的THREAD_ORDER次方),也就是8KB大小的空间。有趣的是,这个函数还会为softirq分配一个同样大小的独立堆栈,如此说来,softirq将不会在hardirq的中断栈上执行,而是在自己的上下文中执行。

总结一下,系统中每个进程都会拥有属于自己的内核栈,而系统中每个CPU都将为中断处理准备了两个独立的中断栈,分别是hardirq栈和softirq栈。草图如下:


技术分享

最后,关于设备驱动程序的中断处理例程中调用可能引起阻塞函数的问题,可以简单归结为在中断处理上下文中能否进行调度的问题。现实中,绝对不应该这样做,因为这会引起很多问题。但是从理论实现的角度,如果调度器愿意,它找到被中断进程的上下文并不存在技术上的障碍,这意味着在中断处理函数中如果发生进程切换,被中断进程被再次调度是可能的,如果调度器愿意这么做的话。

(原文首发:http://www.embexperts.com/forum.php/forum.php?mod=viewthread&tid=499&extra=page%3D1,略有改动)

以上是关于Linux中断补充的主要内容,如果未能解决你的问题,请参考以下文章

linux 内核网络发送技术栈

09 Linux进程的概念

非常好!!!Linux源代码阅读——中断

Linux设备树(四 中断)

Linux x86_64内核中断初始化

linux内核中断体系结构