关于软中断原理,以及中断栈的概念等

中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入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. execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)

  3. {
  4.         union irq_ctx *curctx, *irqctx; 
  5.         u32 *isp, arg1, arg2;

  6.         curctx = (union irq_ctx *) current_thread_info(); 
  7.         irqctx = __this_cpu_read(hardirq_ctx);
  8.         /* 
  9.          * this is where we switch to the IRQ stack. However, if we are
  10.          * already using the IRQ stack (because we interrupted a hardirq 
  11.          * handler) we can't do that and just have to keep using the 
  12.          * current stack (which is the irq stack already after all)
  13.          */

  14.         if (unlikely(curctx == irqctx)) 
  15.                 return 0;

  16.         /* build the stack frame on the IRQ stack */
  17.         isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
  18.         irqctx->tinfo.task = curctx->tinfo.task;
  19.         irqctx->tinfo.previous_esp = current_stack_pointer;

  20.         /* 
  21.          * Copy the softirq bits in preempt_count so that the 
  22.          * softirq checks work in the hardirq context.
  23.          */

  24.         irqctx->tinfo.preempt_count = 
  25.                 (irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) |
  26.                 (curctx->tinfo.preempt_count & SOFTIRQ_MASK);

  27.         if (unlikely(overflow))
  28.                 call_on_stack(print_stack_overflow, isp);

  29.         asm volatile("xchgl %%ebx,%%esp \n" 
  30.                      "call *%%edi \n"
  31.                      "movl %%ebx,%%esp \n" 
  32.                      : "=a" (arg1), "=d" (arg2), "=b" (isp) 
  33.                      : "0" (irq), "1" (desc), "2" (isp), 
  34.                        "D" (desc->handle_irq)
  35.                      : "memory", "cc", "ecx");

  36.         return 1;
  37. }
代码中的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栈。草图如下:




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

总结:①硬中断是由外部事件引起的因此具有随机性和突发性;软中断是执行中断指令产生的,无面外部施加中断请求信号,因此中断的发生不是随机的而是由程序安排好的。
②硬中断的中断响应周期,CPU需要发中断回合信号(NMI不需要),软中断的中断响应周期,CPU不需发中断回合信号。
③硬中断的中断号是由中断控制器提供的(NMI硬中断中断号系统指定为02H);软中断的中断号由指令直接给出,无需使用中断控制器。
④硬中断是可屏蔽的(NMI硬中断不可屏蔽),软中断不可屏蔽。

软中断是一种推后执行的机制,定时器,网卡的数据的处理是很典型的软中断,这个和中断向 量表里的中断是完全不一样的,以网络数据的处理为例,当网卡接到一个数据包后,其中断处理程序只是把数据复制到缓冲区,然后就告诉网卡,你可以再传数据给 我了,也就是中断返回,但在此之前,网卡的中断处理程序要置一个标志位,告诉操作系统有事要做,这个事就是软中断,但软中断只是很多中断返回时要做的事情 之一,操作系统每次中断返回时会检查着个标志位,看是否有事要做,如果有,就会去处理,象前面提到的网卡,这时候操作系统就回调用软中断的处理函数,网卡 的软中断程序就是做分析数据包啊,这个数据应该传给谁啊等这些工作.没有,就返回了,除了必须的部分


编写两个中断服务函数的区别
1.软中断发生的时间是由程序控制的,而硬中断发生的时间是随机的 
2.软中断是由程序调用发生的,而硬中断是由外设引发的 (硬中断收到中断信号后会跳转到公共代码段执行do_IRQ函数并保存寄存器中的用户信息,进入硬件中断栈即handle_irq,执行中断处理函数。而软中断是通过raise_softirq函数标志一个软中断可以执行,软中断向量表就是一个数组,然后在do_softirq函数中执行切换到软件中断栈,然后在调用__do_softirq()函数执行软中断处理函数)
3.硬件中断处理程序要确保它能快速地完成它的任务,这样程序执行时才不会等侍较长时间 

相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页