关闭和打开中断

关闭和打开中断

Question #

原子操作只适合于单体变量,如整数。操作系统的数据结构有的可能有几百字节大小,其中可能包含多种不同的基本数据类型。这显然用原子操作无法解决。下面,我们就要写代码实现关闭开启、中断了,x86 CPU 上关闭、开启中断有专门的指令,即 cli、sti 指令,它们主要是对 CPU 的 eflags 寄存器的 IF 位(第 9 位)进行清除和设置,CPU 正是通过此位来决定是否响应中断信号。

正确的关闭和打开中断的方式是怎样的?

Answer #

在关闭中断函数中先保存 eflags 寄存器,然后执行 cli 指令,在开启中断函数中直接恢复之前保存的 eflags 寄存器就行了,具体代码如下。

typedef u32_t cpuflg_t;
static inline void hal_save_flags_cli(cpuflg_t* flags)
{
     __asm__ __volatile__(
            "pushfl \t\n" //把eflags寄存器压入当前栈顶
            "cli    \t\n" //关闭中断
            "popl %0 \t\n"//把当前栈顶弹出到flags为地址的内存中
            : "=m"(*flags)
            :
            : "memory"
          );
}
static inline void hal_restore_flags_sti(cpuflg_t* flags)
{
    __asm__ __volatile__(
              "pushl %0 \t\n"//把flags为地址处的值寄存器压入当前栈顶
              "popfl \t\n"   //把当前栈顶弹出到eflags寄存器中
              :
              : "m"(*flags)
              : "memory"
              );
}

从上面的代码中不难发现,硬件工程师早就想到了如何解决在嵌套函数中关闭、开启中断的问题:pushfl 指令把 eflags 寄存器压入当前栈顶,popfl 把当前栈顶的数据弹出到 eflags 寄存器中。

hal_restore_flags_sti() 函数的执行,是否开启中断完全取决于上一次 eflags 寄存器中的值,并且 popfl 指令只会影响 eflags 寄存器中的 IF 位。这样,无论函数嵌套调用多少层都没有问题。

Viewpoint #

From #

08 | 锁:并发操作中,解决数据同步的四种方法