为什么中断使能开关需要两个寄存器控制? #
在 GIC Distributor 中,提供了两个 GICD_ISENABLER 和 GICD_ICENABLER,用于控制 GICD 是否可以将某个中断转发至 CPU Interface,寄存器描述如下:
GICD_ISENABLER
Reads
0 Forwarding of the corresponding interrupt is disabled.
1 Forwarding of the corresponding interrupt is enabled.
Writes
0 Has no effect.
1 Enables the forwarding of the corresponding interrupt.
GICD_ICENABLER
Reads
0 Forwarding of the corresponding interrupt is disabled.
1 Forwarding of the corresponding interrupt is enabled.
Writes
0 Has no effect.
1 Enables the forwarding of the corresponding interrupt.
最初以为,此功能只需要一个寄存器控制,姑且将此寄存器叫做 ENABLER。ENABLER 寄存器次与一片普通内存类似,某比特置 1, 代表对应中断使能,某比特置 0 代表禁用对应中断。 若只有一个线程在操作 ENABLER,此方案没有问题。但考虑下列多线程情况,线程 A 想要使能 0 号中断:
uint32_t value_a = *ENABLER;
value_a |= 1;
*ENABLER = value_a;
线程 B 想要使能 1 号中断:
uint32_t value_b = *ENABLER;
value_b |= (1 << 1);
*ENABLER = value_b;
线程 C 想要禁用 2 号中断:
uint32_t value_b = *ENABLER;
value_b &= ~(1 << 2);
*ENABLER = value_b;
从软件逻辑上,三个线程操作的对象是三个不同的中断,三个操作本应该相互独立,互不影响。但是,由于所有中断的控制都在 ENABLER 一个寄存器,若无锁保护,对 ENABLER 的并发访问必然会产生 race condition,ENABLER 的最终值未知。
为避免此种情况下软件加锁,硬件有两种不同设计,一种是 W1C (write 1 clear)或 W1E (write 1 enable),第二种就是将 enable 和 disable 操作的寄存器拆为两组。
借助 W1C,线程 A 和线程 B 就不存在 race condition 了,因为他们写 1 位置不同(写 0 的 bit 被忽略)。而借助“将 enable 和 disable 操作的寄存器拆为两组”,线程 C 和线程 A/B 的中断开关操作变为写两个不同的寄存器(GICD_ISENABLER/GICD_ICENABLER),自然也就不存在 race condition。