Chinaunix首页 | 论坛 | 博客
  • 博客访问: 34222
  • 博文数量: 8
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 45
  • 用 户 组: 普通用户
  • 注册时间: 2014-10-26 02:10
个人简介

滴水穿石。

文章分类

全部博文(8)

文章存档

2015年(8)

我的朋友

分类: 嵌入式

2015-01-18 05:42:00

转载了几篇与Linux中断有关的博文,整理一下。本文以ARM平台为例。

ARM-Linux的IRQ中断处理流程


ARM平台有 IRQ 和 FIQ 两种中断,但后者被Linux弃用,Linux只使用了IRQ。
ARM-Linux的IRQ中断处理流程可大致分为如下几个阶段:
  • A. 执行一小段汇编代码:保护现场、设置抢占计数

      1. 从IRQ模式进入SVC模式(ARM有7种模式,但Linux主要使用SVC和USR两种模式)
      2. 将中断现场,即在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构,包括中断前的SP、LR、CPSR和各工作寄存器的值,保存到SVC模式的栈中。(可以看出ARM平台没有单独的中断栈,中断上下文中使用的是被中断进程的内核栈)
      3. 将被中断进程的 preempt_count 中的 "抢占(preempt)计数" 加 1。
      4. 获取中断的IRQ号,并以IRQ号和第2步中生成的栈帧结构指针为参数,调用C函数 asm_do_IRQ()。这一步会一直循环执行,直到不再有中断标志。

  • B. 循环执行C函数 asm_do_IRQ()处理中断服务程序和软中断,直到不再有中断标志
  1. 栈帧替换 struct pt_regs *old_regs = set_irq_regs(regs);
  2. 调用 irq_enter() 进入中断上下。其内部会将被中断进程的 preempt_count 中的 "硬中断(hardirq)计数" 加 1。
  3. 调用generic_handle_irq (irq)。它内部会调用所有通过request_irq()绑定到irq号中断上的ISR。
  4. 调用 irq_exit()。它先将被中断进程的 preempt_count 中的 "硬中断(hardirq)计数" 减 1; 如果满足软中断的执行条件,再执行软中断,软中断的执行条件为 ( ! in_interrupt() ),它代表preempt_count中的硬中断、不可屏蔽中断和软中断的计数都是0,即所有的硬中断、不可屏蔽中断都已处理完且尚没有软中断在执行; 最后再通过preempt_enable_no_resched()将preempt_count 中的 "抢占(preempt)计数"减 1 (疑问:irq_enter()中并没有增加抢占计数,为什么irq_exit()时要减?)。
  5. 栈帧替换  set_irq_regs(old_regs);
  • C. 中断汇编代码: 执行内核抢占、恢复抢占计数、恢复现场(返回进程上下文)
  1. 检查被中断进程是否需要调度(TIF_NEED_RESCHED标志),如果需要则调用svc_preempt(最终调用的是__schedule())进行内核抢占。如果中断前进程处于用户模式,则无内核抢占这一步。
  2. 将被中断进程的 preempt_count 中的 "抢占(preempt)计数" 恢复到中断之前(A.3步之前)的状态。(疑问:既然这里会恢复抢占计数,那在B.4步骤中对抢占计数的减 1 是否并不需要甚至会导致错误?)
  3. 将A.2步骤中保存的栈帧结构送进CPU寄存器,恢复现场,并跳转回进程上下文(如果中断前进程处于用户模式,则返回用户模式,这时会进行用户空间的抢占;如果中断前处于内核模式,则还返回内核模式)


A、C 阶段汇编代码分析


当 ARM 处理器发生异常(中断是一种异常)时,会跳转到异常向量表(起始地址为0xFFFF_0000 或 0x0000_0000)。在中断机制的初始化过程中,把在arch/arm/kernel/entry-armv.S 中的异常向量表及其处理程序的stub重定位到了 0xFFFF_0000处,这样才使得 ARM 处理器能够正常处理异常,而且事实上执行的就是 entry-armv.S 中的异常处理程序。entry_armv.S包含了所有的异常处理程序,而且定义了一些宏操作,比较复杂,这里我们只关注与中断处理相关的部分。为便于理解,把其中的某些宏展开,再去掉和中断处理无关的部分,整理后的代码如下:

(本段代码和主要注释来自:http://blog.chinaunix.net/uid-27717694-id-3664425.html。汇编代码看起来比较费劲,因此我又添加了很多有助理解的注释,带有"//<<<"前缀的注释内容是我自己添加的)
arch/arm/kernel/entry-armv.S

点击(此处)折叠或打开

  1. //异常向量表
  2. __vectors_start:
  3.  ARM( swi SYS_ERROR0 )
  4.  THUMB( svc #0 )
  5.  THUMB( nop )
  6.     W(b) vector_und + stubs_offset
  7.     W(ldr) pc, .LCvswi + stubs_offset
  8.     W(b) vector_pabt + stubs_offset
  9.     W(b) vector_dabt + stubs_offset
  10.     W(b) vector_addrexcptn + stubs_offset
  11.     W(b) vector_irq + stubs_offset //中断入口:vector_irq
  12.     W(b) vector_fiq + stubs_offset

  13.     .globl __vectors_end
  14. __vectors_end:

  15. vector_irq:
  16. //调整 LR_irq
  17. sub lr, lr, #4    //<<< lr-4就是中断前的PC,断点的返回地址
  18.   
  19. //保存 R0, LR_irq(中断之前的 PC, 断点), SPSR_irq(中断之前的 CPSR) 到 irq模式的栈中
  20.  stmia sp, {r0, lr} @ save r0, lr    //<<< 因为马上要用r0和lr,所以先把他们的原值(中断前的R0和PC)保存到栈中
  21.  mrs lr, spsr    //<<< spsr就是中断前的cpsr,因为马上要修改spsr,所以先将spsr的原值(中断前的CPSR)保存到栈中
  22.  str lr, [sp, #8] @ save spsr
  23.  
  24. //SPSR 设置为 SVC模式
  25.  mrs r0, cpsr
  26.  eor r0, r0, #(\\mode ^ SVC_MODE)
  27.  msr spsr_cxsf, r0    //<<< 讲spsr的模式位设成svc。注意这里设置的是spsr,后面的跳转指令会将spsr写入cpsr,那时才真正进入svc模式
  28. //根据中断前的模式跳转到相应的处理程序
  29. //lr是中断刚开始时的 SPSR,即被中断代码的 CPSR,其低 4位表示中断之前的模式
  30.  and lr, lr, #0x0f
  31.  mov r0, sp
  32.  ldr lr, [pc, lr, lsl #2]

  33. //<<< 到这里,跳转到SVC模式之前,IRQ模式的栈中依次保存了中断前的R0、中断前的PC和中断前的CPSR。R0保存IRQ模式的栈指针,LR中保存合适的irq处理地址(irq_usr或irq_svc)为跳转做准备
  34.  

  35. //跳转到相应模式的处理程序,模式变为 SVC(SPSR 拷贝到 CPSR )
  36.  movs pc, lr    //<<< movs指令的目的寄存器如果是PC,还会同时将spsr写入到cpsr中,由于前面已经将spsr的模式位设成了svc,这样跳转之后就进入了svc模式
  37.  
  38. //跳转表,必须紧跟 ldr lr,[pc,lr,lsl #2]movs pc,lr 两条指令(ARM 流水线机制)
  39.   .long __irq_usr @ 0 (USR)    
  40.  .long __irq_invalid @ 1 (FIQ)
  41.  .long __irq_invalid @ 2 (IRQ)
  42.  .long __irq_svc @ 3 (SVC)
  43.  .long __irq_invalid @ 4
  44.  .long __irq_invalid @ 5
  45.  .long __irq_invalid @ 6 (ABT)
  46.  .long __irq_invalid @ 7
  47.  .long __irq_invalid @ 8
  48.  .long __irq_invalid @ 9
  49.  .long __irq_invalid @ a
  50.  .long __irq_invalid @ b (UND)
  51.  .long __irq_invalid @ c
  52.  .long __irq_invalid @ d
  53.  .long __irq_invalid @ e
  54.  .long __irq_invalid @ f (SYS)


  55. //<<< *****************  真正的中断处理开始  *********************************
  56. //<<< *********************************************************************
  57. //USR模式中断入口
  58. __irq_usr:    //<<< 中断前处于用户模式,进这里
  59.      
  60. //在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构 //<<
  61.  sub sp, sp, #S_FRAME_SIZE    //<<<  让sp指向栈帧结构的地址
  62.  stmib sp, {r1 - r12}    //<<< 把r1 - r12先保存到栈帧中(进入中断后它们的值没变过,所以可以直接保存),这样下面就可以使用它们了。
  63.  ldmia r0, {r1 - r3}    //<<< 由于在跳转到这里之前,R0已保存了IRQ模式的栈指针,所以这里通过R0可以将位于IRQ栈中的 中断前的R0、返回地址、中断前的CPSR 分别保存到r1-r3中。
  64.  add r0, sp, #S_PC @ here for interlock avoidance    //<<< r0指向栈帧结构中的 S_PC (即R15)的位置
  65.  mov r4, #-1 @ \"\" \"\" \"\" \"\"    //<<< r4中存放空数
  66.  str r1, [sp] @ save the \"real\" r0 copied    //<<< r1中保存了中断前的R0,sp则指向栈帧结构,因此这一行是将中断前的R0写入栈帧结构中
  67.  stmia r0, {r2 - r4}        //<<< r2中的PC(中断前的PC)、r3中的CPSR(中断前的CPSR)和r4中的空数, 分别保存到栈帧结构的 R15:CPSR:ORIG_R0 三个字段中(r0现在指向的是栈帧结构中的 S_PC(即R15))
  68.  stmdb r0, {sp, lr}^    //<<< stm和ldm加后缀"^"表示无论现在CPU处于什么模式,寄存器列表中都代表USR模式下的寄存器(另外,如果ldm指令的目标寄存器列表中包含R15,即PC,则该条指令执行时还会讲SPSR写回到CPSR中)。因此这条指令可以将USR模式的sp(R13)和lr(R14)作为现场数据保存到栈帧结构中,这样做的原因是被中断的现场本来就是处于USR模式。指令stmdb中的db表示先将r0中的基地址减1再存取数据

  69. //<<< 到这里,栈帧结构已保存完整

  70. //Clear FP to mark the first stack frame
  71.  zero_fp
  72.  
  73. //把被中断任务的 preempt_count 增加 1
  74.  get_thread_info tsk
  75. #ifdef CONFIG_PREEMPT
  76.  ldr r8, [tsk, #TI_PREEMPT] @ get preempt count     //<<< 中断前的抢占计数保存到r8中
  77.  add r7, r8, #1 @ increment it    //<<< 抢占计数加一后保存到r7中,即r7保存了进入中断后的抢占计数
  78.  str r7, [tsk, #TI_PREEMPT]
  79. #endif
  80.  
  81. //循环调用 asm-do_IRQ()
  82. 1: get_irqnr_and_base r0, r6, r5, lr
  83.  movne r1, sp
  84.  adrne lr, 1b   
  85.  bne asm_do_IRQ    //<<< 注意这里调用asm_do_IRQ函数时用的是"b"指令而不是"bl"指令,但上一行中已将lr指向了标号1:,因此asm_do_IRQ函数返回时回直接跳到1:,这样就形成了一个循环
  86.  
  87. #ifdef CONFIG_PREEMPT
  88.  ldr r0, [tsk, #TI_PREEMPT]     // 读取现在的抢占计数到r0中
  89.  str r8, [tsk, #TI_PREEMPT]        //<<< 前面已将中断前的抢占计数保存到了r8中,因此这里把抢占计数写回
  90.  teq r0, r7    //<<< r7之前保存了进入中断后的抢占计数,这时对比一下r0和r7是否还一样,看中断处理过程中有没有抢占计数的bug.
  91.  strne r0, [r0, -r0]
  92. #endif
  93. //返回到 user 模式
  94.  mov why, #0
  95.  b ret_to_user    //<<< 返回到usr模式的函数中会进行用户抢占
  96.  
  97. //<<< ****************************** irq_usr与irq_svc的分割线 **********************************
  98. //<<< ****************************************************************************************

  99. //SVC模式中断入口
  100. __irq_svc:    //<<< 中断前处于内核模式,进这里
  101. //在内核栈中产生 include/asm-arm/ptrace.h中 pt_regs 定义的栈帧结构
  102.  sub sp, sp, #S_FRAME_SIZE    //<<< 还是由sp指向栈帧结构
  103.  tst sp, #4  
  104.  bicne sp, sp, #4    //<<< 这里似乎是为了保证在内核栈中让栈帧结构位于8字节对齐的位置,#4是(1<<2),判断第2位是否为0可以知道sp是否8字节对齐。从用户模式进入中断时,内核栈指针正好位于内核栈顶(每次由用户空间进入内核空间时,内核栈指针都位于栈顶位置,因为每次从内核空间返回用户空间时,从内核栈获取的数据又都"还给"内核栈了)
  105.  stmib sp, {r1 - r12}   //<<< 保存r1-r12到栈帧结构中
  106.  
  107.  ldmia r0, {r1 - r3}    //<<< 由于在跳转到这里之前,R0已保存了IRQ模式的栈指针,所以这里通过R0可以将位于IRQ栈中的 中断前的R0、返回地址、中断前的CPSR 分别保存到r1-r3中。
  108.  add r5, sp, #S_SP @ here for interlock avoidance     //<<< r5指向栈帧结构中的S_SP(R13)位置
  109.  mov r4, #-1 @ \"\" \"\" \"\" \"\"    //<<< r4中存放空数
  110.  add r0, sp, #S_FRAME_SIZE @ \"\" \"\" \"\" \"\"    //<<< r0指向栈帧结构的顶部,这也正好是中断之前的内核栈指针指向的位置
  111.  addne r0, r0, #4    //<<< 如果执行之前的"bicne sp, sp, #4"指令时,sp的第2位被强制清零了(额外减了4),这里为了让r0指回原来sp的位置,就要让r0额外加4。ne是这两条指令被执行的条件。
  112.  str r1, [sp] @ save the \"real\" r0 copied from the exception stack    //<<< r1中保存了中断前的R0,sp则指向栈帧结构,因此这一行是将中断前的R0写入栈帧结构中
  113.  mov r1, lr        //<<<  进入中断后SVC模式的lr还未被改动过(前面改动的都是IRQ模式的lr),所以直接讲lr保存到r1中。
  114.  stmia r5, {r0 - r4}     //<< 现在,r0到r4分别是: 中断前的栈指针SP、中断前的LR、中断前的PC、中断前的CPSR、空数,  而r5正好指向栈帧结构的S_SP(R13)字段,因此这条指令用中断前的信息填充了栈帧结构的S_SP(R13)到ORIG_R0之间的字段

  115. //<<< 到这里,栈帧结构已保存完整
  116.  
  117. //把被中断任务的 preempt_count 增加 1
  118. #ifdef CONFIG_PREEMPT
  119.  get_thread_info tsk
  120.  ldr r8, [tsk, #TI_PREEMPT] @ get preempt count     //<<< 中断前的抢占计数保存到r8中
  121. add r7, r8, #1 @ increment it    //<<< 抢占计数加一后保存到r7中,即r7保存了进入中断后的抢占计数
  122.  str r7, [tsk, #TI_PREEMPT]
  123. #endif
  124.  
  125. //循环调用 asm-do_IRQ()
  126. 1: get_irqnr_and_base r0, r6, r5, lr
  127.  movne r1, sp
  128.  adrne lr, 1b
  129.  bne asm_do_IRQ
  130.  
  131. //如果需要调度,调用 svc_preempt进行内核抢占
  132. #ifdef CONFIG_PREEMPT
  133.  ldr r0, [tsk, #TI_FLAGS] @ get flags
  134.  tst r0, #_TIF_NEED_RESCHED    //<<< 通过TIF_NEED_RESCHED标志判断是否需要内核抢占
  135.  blne svc_preempt
  136. preempt_return:
  137.  ldr r0, [tsk, #TI_PREEMPT] @ read preempt value     // 读取现在的抢占计数到r0中
  138.  str r8, [tsk, #TI_PREEMPT] @ restore preempt count     //<<< 前面已将中断前的抢占计数保存到了r8中,因此这里把抢占计数写回
  139.  teq r0, r7    //<<< r7之前保存了进入中断后的抢占计数,这时对比一下r0和r7是否还一样,看中断处理过程中有没有抢占计数的bug.
  140.  strne r0, [r0, -r0] @ bug()
  141. #endif
  142. //返回到内核空间
  143.  ldr r0, [sp, #S_PSR] @ irqs are already disabled    
  144.  msr spsr_cxsf, r0
  145.  ldmia sp, {r0 - pc}^ @ load r0 - pc, cpsr    //<<< 注意sp始终指向栈帧结构。这里将栈帧中保存的终端现场恢复到CPU寄存器中(恢复现场),包括pc也重新装入了,因此这就是中断的返回点。

  146. //<<
  147. //<<< 这里还有个疑问: 如果发生了内核抢占,那在最后恢复现场的时候,应该重新装入到CPU寄存器中的就不再是被中断进程的栈帧现场了,而是实施抢占的进程的栈帧(不然中断返回后运行的就还是原来被中断的进程而不是实施抢占的进程)。那这个栈帧的替换是在哪里进行的?怎么实现?

  148. //可以看到,中断的入口点是 vector_irq。此时处理器处于 irq 模式,vector_irq 首先把 R0,LR(中断发生之前的 PC)和 SPSR(中断发生之前的 CPSR)保存到 irq 模式的堆栈中,然后根据进入中断之前的处理器模式跳转到不同的入口,跳转后处理器的模式变成了 svc。 如果中断前处于 usr模式,跳转到__irq_usr;如果中断前处于 svc 模式,则跳转到__irq_svc。__irq_usr 和__irq_svc 运行流程大致相同:都是先在 svc 模式栈(也就是 linux 的内核栈)中生成/include/asm-arm/ptrace.h 中 struct pt_regs 定义的堆帧结构,把被中断任务的preempt_count增加1,然后通过宏 get_irqnr_and_base 读取中断通道号和中断状态寄存器,调用 asm_do_IRQ()函数。只要读取的中断状态寄存器不是 0(表示有中断发生) ,就一直循环调用 asm_do_IRQ()。然后,对于 usr模式,调用 ret_to_usr返回用户空间;对于 svc 模式,如果当前进程需要调度,内核又允许抢占,则调用 svc_preempt 进行任务切换,然后从堆栈弹出寄存器,返回到内核模式。至此,汇编语言阶段结束。

B阶段的C代码分析

关于中断中的C代码部分,下面两篇文章已经解释得很详细了,我就不再重复:
http://blog.chinaunix.net/uid-27717694-id-3664425.html
http://blog.chinaunix.net/uid-24708340-id-3332362.html

C代码中我原来也有个问题,因为我发现中断上下文中有使用 current 宏的语句(还有汇编代码中使用tsk宏的语句),甚至连必须用到的操作中断计数、抢占计数的语句都是依赖 current_thread_info 宏的,而一个进程的 struct thread_info 是放在进程的内核栈中,因此current和current_thread_info宏都依赖进程的内核栈,它们的使用条件是:当前使用的栈是该进程的内核栈。
而在中断上下问中,使用的栈可以配置为被中断进程的内核栈,也可以是独立的中断栈(每个CPU有自己的中断栈)。如果是后者,那current和current_thread_info宏是不是就都不能用了?那还如何操作中断计数和抢占计数?
后来,搜了搜关于独立中断栈的内容,发现了下面这篇文章:
http://blog.chinaunix.net/uid-20543672-id-3164600.html
文章的开头有一段:
{{{{{
 从《深入Linux内核构架》中可以知道:内核在IA-32平台上,早期(2.6.36及之前)内核如 果配置了4K内核栈(CONFIG_4KSTACKS)(默认是8K),对于常规的内核工作以及IRQ处理例程共用这个栈来说似乎有点不够用,所有引入了 两个栈:硬件IRQ栈和软件IRQ栈。在这种情况下,当内核进入中断之后,检测自己所在的栈是内核栈还是中断栈。如果是中断栈(中断嵌套情况)就去执行中 断例程;如果是内核栈就切换到中断栈,同时复制当前内核栈中的部分thread_info数据到中断栈
   但是2.6.36之后的内核就不再有4K内核栈的配置,对于IA-32统一使用8K内核栈,并总是使用两个独立的8K中断栈。这样的改变应该是由于计算机性能的提高、内存的扩大(4G内存已经很平常,16G、32G内存也已不新鲜)以及软件的复杂度提高(对栈的需求增加)。
}}}}}
高亮显示的那句话可能就是我的答案。但尚未在内核源码中找到这句话的证据。

问题

OK,这里把剩余问题总结一下:
  1.  B.4步骤中 irq_exit() 的最后为什么要减少 抢占计数 ?
  2. (参见上面汇编代码中我最后的注释)如果发生了内核抢占,那在最后恢复现场的时候,应该重新装入到CPU寄存器中的就不再是被中断进程的栈帧现场了,而是实施抢占的进程的栈帧(不然中断返回后运行的就还是原来被中断的进程而不是实施抢占的进程)。那这个栈帧的替换是在哪里进行的?怎么实现?







附: 1. current_thread_info()->preempt_count、抢占计数和中断计数的相关代码

include/linux/hardirq.h:

点击(此处)折叠或打开

  1. #define PREEMPT_BITS    8    // [0:7] 8位的抢占计数
  2. #define SOFTIRQ_BITS    8    // [8:15] 8位的软中断计数
  3. #define NMI_BITS    1        //     1位的NMI位(不可屏蔽中断)

  4. #define MAX_HARDIRQ_BITS 10    // 最多10位的硬中断计数

  5. #ifndef HARDIRQ_BITS
  6. # define HARDIRQ_BITS    MAX_HARDIRQ_BITS    // 默认 [16:25] 共10位的中断计数
  7. #endif

  8. #if HARDIRQ_BITS > MAX_HARDIRQ_BITS
  9. #error HARDIRQ_BITS too
  10. #endif

  11. #define PREEMPT_SHIFT    0
  12. #define SOFTIRQ_SHIFT    (PREEMPT_SHIFT + PREEMPT_BITS)
  13. #define HARDIRQ_SHIFT    (SOFTIRQ_SHIFT + SOFTIRQ_BITS)
  14. #define NMI_SHIFT    (HARDIRQ_SHIFT + HARDIRQ_BITS)

  15. #define __IRQ_MASK(x)    ((1UL << (x))-1)

  16. #define PREEMPT_MASK    (__IRQ_MASK(PREEMPT_BITS) << PREEMPT_SHIFT)
  17. #define SOFTIRQ_MASK    (__IRQ_MASK(SOFTIRQ_BITS) << SOFTIRQ_SHIFT)
  18. #define HARDIRQ_MASK    (__IRQ_MASK(HARDIRQ_BITS) << HARDIRQ_SHIFT)
  19. #define NMI_MASK    (__IRQ_MASK(NMI_BITS) << NMI_SHIFT)

  20. #define PREEMPT_OFFSET    (1UL << PREEMPT_SHIFT)
  21. #define SOFTIRQ_OFFSET    (1UL << SOFTIRQ_SHIFT)
  22. #define HARDIRQ_OFFSET    (1UL << HARDIRQ_SHIFT)
  23. #define NMI_OFFSET    (1UL << NMI_SHIFT)

  24. #define SOFTIRQ_DISABLE_OFFSET    (2 * SOFTIRQ_OFFSET)

  25. #ifndef PREEMPT_ACTIVE    /*ACTIVE_PREEMPT的作用:防止已经处于非运行态的进程还没有加入睡眠队列的时候就被抢占然后剔除出运行队列。这样就永远也回不来了,虽然这种情况很少见,一般都是先将进程放到睡眠队列再设置状态。*/
  26. #define PREEMPT_ACTIVE_BITS    1
  27. #define PREEMPT_ACTIVE_SHIFT    (NMI_SHIFT + NMI_BITS)
  28. #define PREEMPT_ACTIVE    (__IRQ_MASK(PREEMPT_ACTIVE_BITS) << PREEMPT_ACTIVE_SHIFT)
  29. #endif

  30. #if PREEMPT_ACTIVE < (1 << (NMI_SHIFT + NMI_BITS))
  31. #error PREEMPT_ACTIVE is too
  32. #endif

  33. #define hardirq_count()    (preempt_count() & HARDIRQ_MASK)
  34. #define softirq_count()    (preempt_count() & SOFTIRQ_MASK)
  35. #define irq_count()    (preempt_count() & (HARDIRQ_MASK | SOFTIRQ_MASK \
  36.                  | NMI_MASK))

  37. /*
  38.  * Are we doing bottom half or hardware interrupt processing?
  39.  * Are we in a softirq context? Interrupt context?
  40.  * in_softirq - Are we currently processing softirq or have bh disabled?
  41.  * in_serving_softirq - Are we currently processing softirq?
  42.  */
  43. #define in_irq()        (hardirq_count())
  44. #define in_softirq()        (softirq_count())
  45. #define in_interrupt()        (irq_count())
  46. #define in_serving_softirq()    (softirq_count() & SOFTIRQ_OFFSET)

  47. /*
  48.  * Are we in NMI context?
  49.  */
  50. #define in_nmi()    (preempt_count() & NMI_MASK)

  51. #if defined(CONFIG_PREEMPT)
  52. # define PREEMPT_CHECK_OFFSET 1
  53. #else
  54. # define PREEMPT_CHECK_OFFSET 0
  55. #endif

  56. /*
  57.  * Are we running in atomic context? WARNING: this macro cannot
  58.  * always detect atomic context; in particular, it cannot know about
  59.  * held spinlocks in non-preemptible kernels. Thus it should not be
  60.  * used in the general case to determine whether sleeping is possible.
  61.  * Do not use in_atomic() in driver code.
  62.  */
  63. #define in_atomic()    ((preempt_count() & ~PREEMPT_ACTIVE) != 0)

  64. /*
  65.  * Check whether we were atomic before we did preempt_disable():
  66.  * (used by the scheduler, *after* releasing the kernel lock)
  67.  */
  68. #define in_atomic_preempt_off() \
  69.         ((preempt_count() & ~PREEMPT_ACTIVE) != PREEMPT_CHECK_OFFSET)

  70. #ifdef CONFIG_PREEMPT
  71. # define preemptible()    (preempt_count() == 0 && !irqs_disabled())
  72. # define IRQ_EXIT_OFFSET (HARDIRQ_OFFSET-1)
  73. #else
  74. # define preemptible()    0
  75. # define IRQ_EXIT_OFFSET HARDIRQ_OFFSET
  76. #endif
{{{{{
上面代码中出现了PREEMPT_ACTIVE标志,它的作用如下:(参考 http://utensil.iteye.com/blog/409785
PREEMPT_ACTIVE标志的本意是说明正在抢占,设置了之后preempt_counter就不再为0,从而执行抢占相关工作的代码不会被抢占。

它可被非常tricky地这样使用:

preempt_schedule()是内核抢占时进程调度的入口,其中调用了schedule()。它在调用schedule()前设置 PREEMPT_ACTIVE标志,调用后清除这个标志。而schedule()会检查这个标志,对于不是TASK_RUNNING(state != 0)的进程,如果设置了PREEMPT_ACTIVE标志,就不会调用deactivate_task(),而deactivate_task()的工作 是把进程从runqueue移除。

你可能会疑惑,为什么要预防已经不在RUNNING状态的进程从runqueue中移除?设想一下,一个进程刚把自己标志为 TASK_INTERRUPTIBL,就被preempt了,它还没来得及把自己放进wait_queue中...这个时候当然要让它回头接着运行,直到 把自己放进wait_queue然后自愿进程切换,那时才可以把它从runqueue中移除。

}}}}}
关于PREEMPT_ACTIVE更详细的参考:http://www.cnblogs.com/openix/archive/2013/03/09/2952041.html

附: 2. 内核抢占的使能与禁止相关代码

include/linux/preempt.h

点击(此处)折叠或打开

  1. #if defined(CONFIG_DEBUG_PREEMPT) || defined(CONFIG_PREEMPT_TRACER)
  2.   extern void add_preempt_count(int val);
  3.   extern void sub_preempt_count(int val);
  4. #else
  5. # define add_preempt_count(val)    do { preempt_count() += (val); } while (0)
  6. # define sub_preempt_count(val)    do { preempt_count() -= (val); } while (0)
  7. #endif

  8. #define inc_preempt_count() add_preempt_count(1)    // current_thread_info()->preempt_count ++ ;
  9. #define dec_preempt_count() sub_preempt_count(1)    // current_thread_info()->preempt_count -- ;

  10. #define preempt_count()    (current_thread_info()->preempt_count)

  11. #ifdef CONFIG_PREEMPT

  12. asmlinkage void preempt_schedule(void);

  13. #define preempt_disable() \     // 禁止内核抢占
  14. do { \
  15.     inc_preempt_count(); \     // current_thread_info()->preempt_count ++ ;
  16.     barrier(); \
  17. } while (0)

  18. #define preempt_enable_no_resched() \     // 使能内核抢占(并非真正使能,而只是让抢占计数减一),但不马上重新调度
  19. do { \
  20.     barrier(); \
  21.     dec_preempt_count(); \    // current_thread_info()->preempt_count -- ;
  22. } while (0)

  23. #define preempt_check_resched() \ // 检测current_thread_info()->flags 是否设置了TIF_NEED_RESCHED标志(需要重新调度),如果设置了则抢占调度
  24. do { \
  25.     if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \   
  26.         preempt_schedule(); \
  27. } while (0)

  28. #define preempt_enable() \     // 使能内核抢占,并马上重新调度
  29. do { \
  30.     preempt_enable_no_resched(); \
  31.     barrier(); \
  32.     preempt_check_resched(); \
  33. } while (0)

  34. /* For debugging and tracer internals */ /* 内部调试用的接口 */    
  35. #define add_preempt_count_notrace(val)            \
  36.     do { preempt_count() += (val); } while (0)
  37. #define sub_preempt_count_notrace(val)            \
  38.     do { preempt_count() -= (val); } while (0)
  39. #define inc_preempt_count_notrace() add_preempt_count_notrace(1)
  40. #define dec_preempt_count_notrace() sub_preempt_count_notrace(1)

  41. #define preempt_disable_notrace() \
  42. do { \
  43.     inc_preempt_count_notrace(); \
  44.     barrier(); \
  45. } while (0)

  46. #define preempt_enable_no_resched_notrace() \
阅读(1914) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~