免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 5169 | 回复: 11
打印 上一主题 下一主题

x86_32上内核栈与中断栈切换的实现方法 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-02-14 10:06 |只看该作者 |倒序浏览
原文链接:http://www.embexperts.com/forum. ... &extra=#pid4523

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

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

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

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

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

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

评分

参与人数 1可用积分 +6 收起 理由
瀚海书香 + 6 支持原创,多谢分享!

查看全部评分

论坛徽章:
0
2 [报告]
发表于 2012-02-14 10:07 |只看该作者
内核栈与中断栈分离的核心代码发生在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等寄存器,所以在内核栈中做中断返回是完全正确的)。

论坛徽章:
0
3 [报告]
发表于 2012-02-14 10:08 |只看该作者
本帖最后由 MagicBoy2010 于 2012-02-14 10:08 编辑

2. 中断栈的分配

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

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


论坛徽章:
12
寅虎
日期:2013-12-04 20:37:4915-16赛季CBA联赛之广东
日期:2017-08-22 19:23:1215-16赛季CBA联赛之上海
日期:2016-06-18 23:05:05操作系统版块每日发帖之星
日期:2016-06-06 06:20:00操作系统版块每日发帖之星
日期:2016-06-05 06:20:00操作系统版块每日发帖之星
日期:2016-06-03 06:20:002015年辞旧岁徽章
日期:2015-03-03 16:54:152015年亚洲杯之巴勒斯坦
日期:2015-02-10 21:38:08卯兔
日期:2014-10-31 20:42:23申猴
日期:2014-06-11 17:15:10处女座
日期:2014-05-22 09:00:1815-16赛季CBA联赛之广夏
日期:2017-09-25 23:37:46
4 [报告]
发表于 2012-02-15 18:32 |只看该作者

实在是太感谢楼主了! 本来那个帖子还在研究中,结果这里就开了一个专门的新帖。

先占座,再细看。

论坛徽章:
0
5 [报告]
发表于 2012-02-15 23:20 |只看该作者
赞,言简意赅,3.6.34的内核也是这样:)

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
6 [报告]
发表于 2012-02-16 08:19 |只看该作者
回复 1# MagicBoy2010
多谢分享!

   

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
7 [报告]
发表于 2012-02-16 08:43 |只看该作者
回复 5# alex-huang
赞,言简意赅,3.6.34的内核也是这样

2.6.34吧。。。

   

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
8 [报告]
发表于 2012-02-16 09:07 |只看该作者
回复 1# MagicBoy2010
所以在2.5内核版本开发中,来自IBM的一位大侠曾提交过一个补丁,试图在中断发生时,从内核栈switch到一个独立的中断栈中,后来也不知道被内核社区采纳了没有

是指这个吗?

4KB stack + irq stack for x86From:         Benjamin LaHaise <bcrl@redhat.com>
To:         Linux Kernel <linux-kernel@vger.kernel.org>
Subject: [RFC] 4KB stack + irq stack for x86
Date:         Tue, 4 Jun 2002 22:55:39 -0400
Cc:         Linus Torvalds <torvalds@transmeta.com>

Hey folks,

Below is a patch against 2.5.20 that implements 4KB stacks for tasks,
plus a seperate 4KB irq stack for use by interrupts.  There are a couple
of reasons for doing this: 4KB stacks put less pressure on the VM
subsystem, reduces the overall memory usage for systems with large
numbers of tasks, and increases the reliability of the system when
under heavy irq load by provide a fixed stack size for interrupt
handlers that other kernel code will not eat into.

The interrupt stacks are stackable, so we could use multiple
4KB irq stacks.  The thread_info structure is included in each
interrupt stack, and has the current pointer copied into it upon
entry.

Things can be made a bit more efficient by moving thread_info from the
bottom of the stack to the top.  This would simplify the irq entry code
a bit as the same pointer can be used for the thread_info code and top
of stack.  The 2.4 version of the patch does this.  In addition, moving
thread_info to the top of the stack results in better cache line
sharing: thread_info is in the same cacheline as the first data pushed
onto the irq stack.  Ditto for regular tasks.  I'll do this if there is
interest in merging it.

I had been playing with 2.5KB stacks (4KB page minus 1.5K for task_struct),
and it is possible given a few fixes for massive (>1KB) stack allocation
in the pci layer and a few others.  So far 4KB hasn't overflowed on any
of the tasks I normally run (checked using a stack overflow checker that
follows).

Comments?

   

论坛徽章:
0
9 [报告]
发表于 2012-02-16 12:46 |只看该作者
回复 8# 瀚海书香

关于那个补丁详细的信息我现在已经不太记得了,只记得是在2.5内核开发期间,IBM的一位开发者,其目的就是在中断发生时,将内核栈给switch到中断栈,实现原理大体上和现在的代码我想应该没有本质的区别。


   

论坛徽章:
0
10 [报告]
发表于 2012-05-07 23:11 |只看该作者
sorry,敲错了,是2.6.34
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP