乍一看下边的Linux内核代码,貌似L3389有bug,于是我就绕有兴趣地阅读了一下local_irq_save/local_irq_restore的源代码。

/* linux-4.14.12/mm/slab.c#3389 */

  static __always_inline void *
slab_alloc(struct kmem_cache *cachep, gfp_t flags, unsigned long caller)
{
unsigned long save_flags;
void *objp;
....
local_irq_save(save_flags);
objp = __do_cache_alloc(cachep, flags);
local_irq_restore(save_flags);
....
return objp;
}

在L3380和L3389中, 如果local_irq_save()是一个函数,必然存在着bug, 因为需要把save_flags的变量地址传给local_irq_save()才对。

      unsigned long save_flags;
....
local_irq_save(save_flags);

L3389是不是该是这样才对啊?

      local_irq_save(&save_flags);

但是,local_irq_save()和local_irq_restore()不是函数,而是宏,这样就没有bug了。

1. local_irq_save()和local_irq_restore()的实现

/* linux-4.14.12/include/linux/irqflags.h#139 */

  #ifdef CONFIG_TRACE_IRQFLAGS
...
#define local_irq_save(flags) \
do { \
raw_local_irq_save(flags); \
trace_hardirqs_off(); \
} while () #define local_irq_restore(flags) \
do { \
if (raw_irqs_disabled_flags(flags)) { \
raw_local_irq_restore(flags); \
trace_hardirqs_off(); \
} else { \
trace_hardirqs_on(); \
raw_local_irq_restore(flags); \
} \
} while ()
...
#else /* !CONFIG_TRACE_IRQFLAGS */
...
#define local_irq_save(flags) \
do { \
raw_local_irq_save(flags); \
} while ()
#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)
...
#endif /* CONFIG_TRACE_IRQFLAGS */

为简单起见,我们只关注!CONFIG_TRACE_IRQFLAGS分支就好了,

  #define local_irq_save(flags)                              \
do { \
raw_local_irq_save(flags); \
} while ()
#define local_irq_restore(flags) do { raw_local_irq_restore(flags); } while (0)

于是, 我们可以认为, locale_irq_save()/local_irq_restore()等同于:

#define local_irq_save(flags)    raw_local_irq_save(flags)
#define local_irq_restore(flags) raw_local_irq_restore(flags)

2. raw_local_irq_save()和raw_local_irq_restore()的实现

/* linux-4.14.12/include/linux/irqflags.h#78 */

  #define raw_local_irq_save(flags)                   \
do { \
typecheck(unsigned long, flags); \
flags = arch_local_irq_save(); \
} while ()
#define raw_local_irq_restore(flags) \
do { \
typecheck(unsigned long, flags); \
arch_local_irq_restore(flags); \
} while ()

关于宏typecheck()不做解释,因为很直观,就是保证flags的类型必须是unsigned long。于是,raw_local_irq_save()和raw_local_irq_restore()等同于:

#define raw_local_irq_save(flags)       flags = arch_local_irq_save()
#define raw_local_irq_restore(flags) arch_local_irq_restore(flags)

下面以x86为例说明arch_local_irq_save()和arch_local_irq_restore()这两个函数的实现。

3. arch_local_irq_save()和arch_local_irq_restore()这两个函数在x86上的实现

/* linux-4.14.12/arch/x86/include/asm/irqflags.h#70 */

  static inline notrace unsigned long arch_local_save_flags(void)
{
return native_save_fl();
} static inline notrace void arch_local_irq_restore(unsigned long flags)
{
native_restore_fl(flags);
}
...
static inline notrace unsigned long arch_local_irq_save(void)
{
unsigned long flags = arch_local_save_flags();
arch_local_irq_disable();
return flags;
}

函数arch_local_irq_save()在调用arch_local_save_flags()还做了一件事,那就是调用arch_local_irq_disable()把中断禁止掉。 接下来,我们首先看看native_save_fl()和native_restore_fl()的具体实现。

3.1 native_save_fl()的实现

/* linux-4.14.12/arch/x86/include/asm/irqflags.h#16 */

  static inline unsigned long native_save_fl(void)
{
unsigned long flags; /*
21 * "=rm" is safe here, because "pop" adjusts the stack before
22 * it evaluates its effective address -- this is part of the
23 * documented behavior of the "pop" instruction.
24 */
asm volatile("# __raw_save_flags\n\t"
"pushf ; pop %0"
: "=rm" (flags)
: /* no input */
: "memory"); return flags;
}

这是一段内嵌的汇编代码,后面写一个简单的demo再解释。

3.2 native_restore_fl()的实现

/* linux-4.14.12/arch/x86/include/asm/irqflags.h#34 */

  static inline void native_restore_fl(unsigned long flags)
{
asm volatile("push %0 ; popf"
: /* no output */
:"g" (flags)
:"memory", "cc");
}

同样,这也是内嵌的汇编代码,后面写一个简单的demo再解释。

3.3 反汇编理解native_save_fl()和native_restore_fl()

  • foo.c
 static inline unsigned long native_save_fl(void)
{
unsigned long flags; /*
* "=rm" is safe here, because "pop" adjusts the stack before
* it evaluates its effective address -- this is part of the
* documented behavior of the "pop" instruction.
*/
asm volatile("# __raw_save_flags\n\t"
"pushf ; pop %0"
: "=rm" (flags)
: /* no input */
: "memory"); return flags;
} static inline void native_restore_fl(unsigned long flags)
{
asm volatile("push %0 ; popf"
: /* no output */
:"g" (flags)
:"memory", "cc");
} int main(int argc, char *argv[])
{
unsigned long flags = native_save_fl();
native_restore_fl(flags);
return ;
}
  • 用gcc编译并反汇编
veli@idorax:/tmp$ gcc -g -Wall -o foo foo.c
veli@idorax:/tmp$
veli@idorax:/tmp$ gdb foo
GNU gdb (Ubuntu 7.11.-0ubuntu1~16.5) 7.11.
...<snip>...................................
(gdb) set disassembly-flavor intel
(gdb)
(gdb) disas /m main
Dump of assembler code for function main:
{
0x00000000004004f5 <+>: push rbp
0x00000000004004f6 <+>: mov rbp,rsp
0x00000000004004f9 <+>: sub rsp,0x20
0x00000000004004fd <+>: mov DWORD PTR [rbp-0x14],edi
0x0000000000400500 <+>: mov QWORD PTR [rbp-0x20],rsi unsigned long flags = native_save_fl();
0x0000000000400504 <+>: call 0x4004d6 <native_save_fl>
0x0000000000400509 <+>: mov QWORD PTR [rbp-0x8],rax native_restore_fl(flags);
0x000000000040050d <+>: mov rax,QWORD PTR [rbp-0x8]
0x0000000000400511 <+>: mov rdi,rax
0x0000000000400514 <+>: call 0x4004e6 <native_restore_fl> return ;
0x0000000000400519 <+>: mov eax,0x0 }
0x000000000040051e <+>: leave
0x000000000040051f <+>: ret End of assembler dump.
(gdb) #
(gdb) disas /m native_save_fl
Dump of assembler code for function native_save_fl:
{
0x00000000004004d6 <+>: push rbp
0x00000000004004d7 <+>: mov rbp,rsp unsigned long flags; /*
6 * "=rm" is safe here, because "pop" adjusts the stack before
7 * it evaluates its effective address -- this is part of the
8 * documented behavior of the "pop" instruction.
9 */
asm volatile("# __raw_save_flags\n\t"
0x00000000004004da <+>: pushf
0x00000000004004db <+>: pop rax
0x00000000004004dc <+>: mov QWORD PTR [rbp-0x8],rax "pushf ; pop %0"
: "=rm" (flags)
: /* no input */
: "memory"); return flags;
0x00000000004004e0 <+>: mov rax,QWORD PTR [rbp-0x8] }
0x00000000004004e4 <+>: pop rbp
0x00000000004004e5 <+>: ret End of assembler dump.
(gdb) #
(gdb) disas /m native_restore_fl
Dump of assembler code for function native_restore_fl:
{
0x00000000004004e6 <+>: push rbp
0x00000000004004e7 <+>: mov rbp,rsp
0x00000000004004ea <+>: mov QWORD PTR [rbp-0x8],rdi asm volatile("push %0 ; popf"
0x00000000004004ee <+>: push QWORD PTR [rbp-0x8]
0x00000000004004f1 <+>: popf : /* no output */
:"g" (flags)
:"memory", "cc");
}
0x00000000004004f2 <+>: nop
0x00000000004004f3 <+>: pop rbp
0x00000000004004f4 <+>: ret End of assembler dump.
(gdb) q
veli@idorax:/tmp$

根据上面的反汇编代码不难看出,native_save_fl()和native_restore_fl()的实现异常简单。

  • native_save_fl()
; static inline unsigned long native_save_fl(void)

0x00000000004004da <+>:        pushf                          ; 把标志寄存器(FLAGS)压入栈(Stack)中
0x00000000004004db <+>: pop rax ; 通过出栈操作把标志寄存器的值存入rax中
0x00000000004004dc <+>: mov QWORD PTR [rbp-0x8],rax ; 把rax存入局部变量flags中
0x00000000004004e0 <+>: mov rax,QWORD PTR [rbp-0x8] ; 根据ABI, 返回值总是存于rax中,这里等同于return flags
  • native_restore_fl()
; static inline void native_restore_fl(unsigned long flags)

0x00000000004004ea <+>:        mov    QWORD PTR [rbp-0x8],rdi ; 根据ABI, 函数的第一个参数通过寄存器rdi传递
; 于是,等同于将第一个参数flags存入一个局部变量中
0x00000000004004ee <+>: push QWORD PTR [rbp-0x8] ; 等同于将第一个参数flags压入栈中
0x00000000004004f1 <+>: popf ; 通过初栈操作把flags的值回复到标志寄存器FLAGS中

注意:操作标志寄存器FLAGS(16位: flags, 32位: eflags, 64位: rflags),必须通过pushf和popf这两个指令,而不能使用push和pop指令。

因此,我们可以得出如下结论,(在x86平台上)

  • local_irq_save()就是把标志寄存器保存到一个局部变量flags中,然后禁止中断;
  • local_irq_restore()则是通过局部变量flags的值恢复标志寄存器,中断自动打开。

4. arch_local_irq_disable()和arch_local_irq_enable()在x86上的实现

/* linux-4.14.12/arch/x86/include/asm/irqflags.h#80 */

  static inline notrace void arch_local_irq_disable(void)
{
native_irq_disable();
} static inline notrace void arch_local_irq_enable(void)
{
native_irq_enable();
} /* linux-4.14.12/arch/x86/include/asm/irqflags.h#42 */
static inline void native_irq_disable(void)
{
asm volatile("cli": : :"memory");
} static inline void native_irq_enable(void)
{
asm volatile("sti": : :"memory");
}

从上面的代码可以看出,在x86中,

  • arch_local_irq_disable()的实质是执行汇编指令cli
  • arch_local_irq_enable的实质则是执行汇编指令sti

到此为止,我们已经搞清楚了如下4个宏的作用。

  1. local_irq_disable() : 禁止本地中断传递。 在x86上,本质上是调用汇编指令cli;
  2. local_irq_enable() : 激活本地中断传递。 在x86上,本质上是调用汇编指令sti;
  3. local_irq_save() : 保存本地中断传递的当前状态,然后禁止本地中断传递。在x86上,本质上是调用pushf+pop先保存标志寄存器到一个变量flags中,然后调用汇编指令cli;
  4. local_irq_restore() : 恢复本地中断传递到给定的状态。在x86上,本质上是调用push+popf重置标志寄存器。

更多有关中断控制的细节,请阅读源代码和《Linux Kernel Development》一书的第7章:中断和中断处理。 常用的中断控制方法,如下图所示。

If all you have is a hammer, everything looks like a nail. | 如果你拥有的东西就只有一把锤子,那么一切事物在你眼里都看起来是钉子。 (P.S. 保持Open的心态很重要啊)

理解Linux内核之中断控制的更多相关文章

  1. 深入理解Linux内核-中断和异常

    Linux内核代码查看 http://androidxref.com/ 中断:被定义位一个事件,它能改变处理器执行指令的顺序.它对应硬件(CPU.其他硬件设备)电路产生的电信号. 同步中断:指令执行时 ...

  2. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  3. 再思linux内核在中断路径内不能睡眠/调度的原因(2010)【转】

    转自:http://blog.csdn.net/maray/article/details/5770889 Linux内核中断路径中不能睡眠,为什么? 这里就行了很深入的讨论,值得一看:http:// ...

  4. 《深入理解Linux内核》 读书笔记

    深入理解Linux内核 读书笔记 一.概论 操作系统基本概念 多用户系统 允许多个用户登录系统,不同用户之间的有私有的空间 用户和组 每个用于属于一个组,组的权限和其他人的权限,和拥有者的权限不一样. ...

  5. Linux内核实现中断和中断处理(二)

    第一部分移步传送门召唤!!:http://www.cnblogs.com/lenomirei/p/5562086.html 上回说了Linux内核实现中断会把中断分为两部分进行处理,上回讲了上部分,这 ...

  6. 【读书笔记::深入理解linux内核】内存寻址【转】

    转自:http://www.cnblogs.com/likeyiyy/p/3837272.html 我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0 ...

  7. 【读书笔记::深入理解linux内核】内存寻址

    我对linux高端内存的错误理解都是从这篇文章得来的,这篇文章里讲的 物理地址 = 逻辑地址 – 0xC0000000:这是内核地址空间的地址转换关系. 这句话瞬间让我惊呆了,根据我的CPU的知识,开 ...

  8. Linux内存管理(深入理解Linux内核)

    Linux的内存管理,实际上是借助80x86的硬件分段和分页电路,将逻辑地址转化为物理地址的. 物理内存中,有一部分是一直(Permanently)映射给内核使用的,这部分主要用于保存内核的代码,以及 ...

  9. 深入理解Linux内核 学习笔记(1)

    1.用户和用户组 每个用户是一个或多个用户组的一名成员,组由唯一的用户组标识符(user group ID)标识.每个文件的相关权限也恰好与一个组相对应. root为超级用户, 2.模块 为了达到微内 ...

随机推荐

  1. 发展科技到底有什么用,转NASA专家给一位修女的一封信

    问题补充:我们难道不应该把这些资金用于更深入的医疗保障和减少贫穷吗? 我们为何要仰望星空,花大量的金钱和精力探索那不可预知的宇宙呢?NASA科学家写给非洲修女的一封信回答得特别好,也特别震撼人心.—— ...

  2. .net core An assembly specified in the application dependencied mainfest<****.json>was not found解决办法

    最近在开发项目中,遇到了一个问题.在本机开发中部署到本机iis上或者本机控制台都没有问题,运行正常.当发布部署到服务器(windowsServer)中的时候一直运行不起来,用控制台也运行不起来,直接报 ...

  3. MySQL事务一致性理解

    一致性是指数据处于一种语义上的有意义且正确的状态.一致性是对数据可见性的约束,保证在一个事务中的多次操作的数据中间状态对其他事务不可见的.因为这些中间状态,是一个过渡状态,与事务的开始状态和事务的结束 ...

  4. nginx安装和遇到的问题

    nginx安装步骤和遇到的问题 tar -xvf nginx-.tar.gz cd nginx- ./configrue make make install 在configure中可能遇到的问题: ( ...

  5. 第三章 CopyOnWriteArrayList源码解析

    注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析. http://www.cnblogs.com/java-zhao/p/5102342.html ...

  6. day 112天,爬虫(拉钩网,斗音,GitHub)第二天

    提前准备工作.安装准备工作(day3用)  1. 安装scrapy  https://www.cnblogs.com/wupeiqi/articles/6229292.html a. 下载twiste ...

  7. 如何在Qt资源文件中包含和释放exe等各种类型文件?

    操作系统:Windows 10 X64 企业版 Qt: 5.8.0 QtCreater: 4.2.1 刚刚开始学习Qt,不断遇到困难和挑战,前几天在各个QQ群里询问如何在Qt的资源文件中包含和释放ex ...

  8. jzoj2941

    我們可以暴力枚舉每一個人分幾個糖果,再暴力統計答案即可 每次遞歸下去可以從1-n號人,決定選多少個糖果再遞歸 #include<bits/stdc++.h> using namespace ...

  9. ssh密钥认证排错

    sshd配置文件没问题: 目录权限设置也没问题: 但是 ssh -vvv 提示: debug3: no such identity: /Users/user/.ssh/id_rsa,/Users/us ...

  10. jvm(2)类的初始化(一)

    [深入Java虚拟机]之三:类初始化 类初始化是类加载过程的最后一个阶段,到初始化阶段,才真正开始执行类中的Java程序代码. 1,下面说的初始化主要是类变量的初始化,实例变量的初始化触发条件不同(一 ...