我正在用 C++ 编写操作系统,并为键盘设置了旋转锁。
_Use_decl_annotations_
bool
HandleKeyboard(
_In_ const BYTE Index,
_In_ const PINTERRUPT_STACK Stack,
_In_ const BYTE HasError,
_In_ const PINTERRUPT_CPU_STATE Cpu
)
{
(void)Index;
(void)Stack;
(void)HasError;
(void)Cpu;
if (const auto key = Pic::PicManager::keyboard.GetKeyPress(); key.hasValue)
{
__cli();
{
auto& [capitalize, keyLock, currentKey, wakeUp] = Pic::PicManager::keyboard;
Sync::Guard guard(keyLock);
currentKey = key.inner;
wakeUp = true;
}
__sti();
}
return true;
}
它禁用中断,获取锁并将密钥放入全局变量中,然后设置一个标志以便主循环可以处理它:
while (true)
{
auto& [capitalize, keyLock, currentKey, wakeUp] = Pic::PicManager::keyboard;
Sync::Guard guard(keyLock);
if (!wakeUp || currentKey == 0)
{
continue;
}
// do something with the key
currentKey = 0;
wakeUp = false;
}
有时我可以写 1-3 个键,然后就卡住了。 Bochs 说键盘缓冲区已满,所以我猜它以某种方式卡在中断处理程序中。
自旋锁的实现:
__spinlock_lock:
lock bts word [rcx], 0
jc .wait
ret
.wait:
test word [rcx], 0
jnz .wait
jmp __spinlock_lock
__spinlock_unlock:
lock btr word [rcx], 0
ret
RCX 中的参数是一个不稳定的词。
Sync::Spin::Lock
只调用这些函数,Sync::Guard
在构造函数中调用Lock
,在析构函数中调用Unlock
。我尝试了多种自旋锁的实现(带有标志的基本 while 循环,<atomic>
),但效果是一样的。如果我从任何一个可以工作的地方取下锁。
我不明白它是如何卡住的,因为 while 循环在每次迭代时都会解锁自旋锁,因此中断自旋锁有机会被获取。
如果主循环已经获取了锁,并且中断在临界区期间到达同一个核心,则处理程序将旋转,但锁永远不会变得可用,因为主循环无法完成临界区,直到处理程序返回。所以是的,当然会陷入僵局。
自旋锁对于在不同核心上并发运行的线程之间的互斥很有用。您不能将它们用于同一核心上的主线程和中断处理程序之间的互斥,因为它们不会同时运行;主线程被挂起,直到中断处理程序返回。
这里的一种方法是使用处理程序的无锁原子标志来指示数据可用。当主线程观察到此标志被设置时,它可以在从缓冲区检索数据时禁用中断。
或者,使用完全无锁的队列算法 - 最好是现成的算法,因为它们很难正确使用。