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

转载 2012年03月22日 16:28:47

中断栈与内核栈的话题更多地属于内核的范畴,所以在《深入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栈。草图如下:




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

Linux内核中用户空间栈和内核栈的区别

转自http://blog.csdn.net/bailyzheng/article/details/8015618   1.进程的堆栈      内核在创建进程的时候,在创建task_str...
  • weiqing1981127
  • weiqing1981127
  • 2012年11月30日 09:29
  • 1274

在linux kernel中打印函数调用的堆栈的方法

在linux内核调试中,经常用到的打印函数调用堆栈的方法非常简单,只需在需要查看堆栈的函数中加入: dump_stack();或 __backtrace();即可。   dump_s...
  • xishuang_gongzi
  • xishuang_gongzi
  • 2016年05月05日 21:02
  • 3720

Linux 中断详解

原文地址 http://www.yesky.com/20010813/192117.shtml 方法之三:以数据结构为基点,触类旁通   结构化程序设计思想认为:程序 =数据结构 +算法。...
  • tiangwan2011
  • tiangwan2011
  • 2012年08月21日 15:59
  • 17817

Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈

转载请注明出处: https://yakir-yang.github.io/ 栈是什么?栈有什么作用?首先,栈 (stack) 是一种串列形式的 数据结构。这种数据结构的特点是 后入先出 (LIF...
  • yangkuanqaz85988
  • yangkuanqaz85988
  • 2016年09月01日 21:52
  • 6154

Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈

栈是什么?栈有什么作用? 首先,栈 (stack) 是一种串列形式的 数据结构。这种数据结构的特点是 后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:...
  • fivedoumi
  • fivedoumi
  • 2016年11月08日 11:15
  • 690

Linux内核堆栈实现分析 save_stack_trace

1 内核线程 内核为每个线程分配8K的栈空间, 在每个堆栈的顶部放着struct thread_info 结构体,用来保存线程相关信息. 其中有几个重要变量: Preempt_count : 此变量分...
  • bin_linux96
  • bin_linux96
  • 2014年12月13日 10:42
  • 3354

中断发生时用户堆栈和内核堆栈的切换

如果一个中断产生时任务正在用户代码中执行,那么该中断会引起CPU特权级从3到0的变化,此时CPU就会运行用户态堆栈到内核态堆栈的切换操作。CPU会从当前任务的任务状态段TSS中取得新堆栈的段选择符和偏...
  • dandelionj
  • dandelionj
  • 2014年07月18日 16:57
  • 1768

linux内核堆栈设置过程

内核在没有开启MMU之前,内核堆栈的设置在arch/arm/boot/
  • dropping_1979
  • dropping_1979
  • 2014年05月07日 18:30
  • 1294

linux内核中打印栈回溯信息 - dump_stack()函数分析

当内核出现比较严重的错误时,例如发生Oops错误或者内核认为系统运行状态异常,内核就会打印出当前进程的栈回溯信息,其中包含当前执行代码的位置以及相邻的指令、产生错误的原因、关键寄存器的值以及函数调用关...
  • jasonchen_gbd
  • jasonchen_gbd
  • 2015年05月08日 21:19
  • 5091

Linux 内核堆栈打印函数

linux kernel 里面打印堆栈的函数   调用dump_stack()就会打印当前cpu的堆栈的调用函数了。参考内核源代码 arch/x86_64/kernel/traps.c /* ...
  • transformer_han
  • transformer_han
  • 2013年11月11日 23:09
  • 4005
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux内核中的中断栈与内核栈的补充说明
举报原因:
原因补充:

(最多只允许输入30个字)