写点什么

【精通内核】Linux 内核中断控制原理源码解析

  • 2022 年 9 月 10 日
    上海
  • 本文字数:2337 字

    阅读完需:约 8 分钟

前言

📫作者简介小明java问道之路,专注于研究计算机底层/Java/Liunx 内核,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计📫 

🏆 InfoQ 签约博主、CSDN 专家博主/Java 领域优质创作者/CSDN 内容合伙人、阿里云专家/签约博主、华为云专家、51CTO 专家/TOP 红人 🏆

🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~


本文导读

CPU 对于任务切换是通过时钟中断来控制的,只要我们将中断屏蔽,就可以保证在当前 CPU 中的所有操作都不会被中断,从而保证了原子性。在单核 CPU 上, 通过操作 EFLAGS 寄存器相当于保存 EFLAGS 表示中断

一、Linux 内核中断控制

如果是在单核 CPU 上,情况又当如何在中断程序中也要获取自旋锁,那么可能造成和应用程序自旋锁相互争用,从而造成死锁。然而,能否将本地的中断禁止,以完成在单核 CPU 上的原子性操作,当我们完成操作后再打开中断。

同时,如果进程在内核中工作,当有高优先级的任务需要被紧急处理时,那么能否在内核态中对其进行抢占呢?

本节将详细描述 Linux 内核中断控制与内核抢占原理。下面看看实现原理。

1、中断原理

从前述理论得知,CPU 对于任务切换是通过时钟中断来控制的,只要我们将中断屏蔽,就可以保证在当前 CPU 中的所有操作都不会被中断,从而保证了原子性。

可能有读者会想,既然有了关中断,为什么还需要信号量、互斥量、自旋锁?

这很简单,在 SMP 即对称多处理器架构中,多个 CPU 共享内存。由于不可以将全部 CPU 的中断都关闭,因此为了支撑 SMP 架构,除了本地中断被关闭之外,还可以通过自旋锁来保证任务操作原子性。

上前述内容在讲解理论时,已经介绍了关中断和开中断的汇编指令 CLI、STI。现在我们来看看所有与中断相关的操作。

2、Linux 内核源码解析

首先将 EFLAGS 寄存器入栈,然后弹出放入所传入的 x 变量中,相当于保存 EFLAGS;将传入的 x 变量入栈,然后弹出放入 EFLAGS 寄存器中。

相当于恢复 EFLAGS 到中,禁止本地 CPU 中断请求,打开本地 CPU 中断请求

判断是否已经禁止了中断请求。可以看到,这里取 EFLAGS 的第九位,看看是否为 0,保存本地 EFLAGS 且禁止中断。这里与 local save flags 不同之处在于,最后的 cli,相当于保存了 EFLAGS,且禁止本地 CPU 中断,local_save flags 只保存了 EFL AGS 并没有禁止中断

// 将 EFLAGS 寄存器入栈,然后弹出放入所传入的x变量中// 相当于保存EFLAGS#define local_save_flags(x)	do {    typecheck(unsigned longx);    _asm__volatile_("pushfl;popl %O":"=g	(x): /* no input */); } while (0)
// 将传入的x变量入栈,然后弹出放入EFLAGS寄存器中。相当于恢复EFLAGS到中#define local irq_restore(x) do { typecheck(unsigned long,x); _asm__volatile__("push %O;popfl": /* no output */ : "g" (x): "memory", "cc"); } while (0)
//禁止本地CPU中断请求#define local irq_disable() _asm__volatile("cli":::"memory")
// 打开本地CPU 中断请求#define local irq_enable() _asm__volatile_("sti":::"memory")
// 判断是否已经禁止了中断请求。可以看到,这里取 EFLAGS的第九位,看看是否为0#define irqs_disabled()({ unsigned long flags;
// //保存本地EFLAGS且禁止中断。这里与local save flags不同之处在于,最后的cli,相当于保存了 EFLAGS local_save_flags(flags); !(flags & (1<<9));})
// 且禁止本地CPU中断,local_save flags只保存了EFL AGS并没有禁止中断#define local_irq_save(x) __asm__volatile ("pushfl; popl %0;cli": "=g" (x): /* no input */ : "memory")
复制代码

二、EFLAGS 寄存器

与中断相关的操作也仅仅只是上面这些了,是不是是很简单,但是我们还是忽略了一个东西,那就是 EFLAGS 寄存器,这到底是是个什么东西呢?

EFLAGS 寄存器包含了一组状态标志、控制标志、系统标志的寄存器。处理器在初始化时将其初始

化为 00000002H。1、3、5、15 和 22~31 保留未使用。 当然,这里不能依赖这些保留未使用的状态位。

1、EFLAGS 寄存器图解

EFLAGS 寄存器的详细描述如下图所示

S 代表状态标志;C 代表控制标志;X 代表系统标志;

其中,状态标志有 6 个;控制标志有 1 个;系统标志有 10 个。

一些标志位可以通过特殊的汇编指令来修改,但是这些可被修改的汇编指令都不能直接对这个寄存器进行操作,只能间接设置。例如,可以通过 LAHF、SAHF、PUSHFD、POPF、POPFD 这些指令来从 EAX 寄存器或者栈中加载或者存标志位

上面的带 L 就是 LOAD,带 S 的就是 STORE。

当通过寄存器或者栈保存好之前的状态位后,就可以通过位操作指令 BT(位测试)、BTS(位测试并设置值)、BTR(位测试并复位值)、BTC(位测试并取反)来检测或者设置状态值。

当挂起一个任务时,CPU 将会自动保存 EFLAGS 寄存器的内容到任务状态段即 TSS 中,下一次在调度任务时再恢复。当然不止有任务切换时会这样做,当我们任务在进行中断或者异常处理时也会保存 EFLAGS 寄存器的值。

2、EFLAGS 寄存器状态标志位

状态标志位,即 0、2、4、6、7、11 标明了 ADD、SUB、MUL、DIV 等算术逻辑运算指令的操作结果。

3、EFLAGS 寄存器控制标志位

控制标志位,只有一个 DF 标志。

这个方向标志(位于 EFLAGS 寄存器的第 10 位)控制串指令(MOVS、 CMPS、SCAS、LODS 和 STOS)。设置 DF 标志使得串指令自动递减(从高地址向低地址方向处理字符串),清除该标志则使得串指令自动递增。STD 和 CLD 指令分别用于设置和清除 DF 标志。

4、EFLAGS 寄存器系统标志位

EFLAGS 寄存器中的系统标志位部分标志位用于控制操作系统或执行操作,它们不允许被应用程序所修改。

总结

CPU 对于任务切换是通过时钟中断来控制的,只要我们将中断屏蔽,就可以保证在当前 CPU 中的所有操作都不会被中断,从而保证了原子性。在单核 CPU 上, 通过操作 EFLAGS 寄存器相当于保存 EFLAGS 表示中断。

发布于: 2022 年 09 月 10 日阅读数: 5
用户头像

InfoQ签约作者/技术专家/博客专家 2020.03.20 加入

🏆InfoQ签约作者、CSDN专家博主/Java领域优质创作者、阿里云专家/签约博主、华为云专家、51CTO专家/TOP红人 📫就职某大型金融互联网公司高级工程师 👍专注于研究Liunx内核、Java、源码、架构、设计模式、算法

评论

发布
暂无评论
【精通内核】Linux内核中断控制原理源码解析_cpu_小明Java问道之路_InfoQ写作社区