xv6具有用于创建内核使用的spinlock的spinlock.c文件。但我需要实现在用户级别使用的spinlock API。例如,我将实现sp_create()来在用户级别创建一个自旋锁。或者调用sp_acquire(int id)来获取锁,等等。要做到这一点,我应该创建系统调用,并将实际实现放在内核中。xv6具有spinlock功能,但只能在内核级别使用。
我曾想过创建系统调用,实际上是调用spinlock.c中的相应函数来创建锁、获取锁、释放锁等。但由于中断禁用的一些问题,它不起作用。
我正在下面复制我迄今为止写的代码:
//system call for lock_take():
int l_take(int lockid) {
struct proc *curproc = myproc();
//process will take lock
..
acquire(&LL.arraylockList[lockid].spnLock);
..
return 0;
}
我在这里遇到的问题是,它给了我关于panic的错误:sched锁我认为这是因为Acquired()代码中有pushcli()
void
acquire(struct spinlock *lk)
{
pushcli(); // disable interrupts to avoid deadlock.
if (holding(lk)) panic("acquire");
// The xchg is atomic.
while (xchg(&lk->locked, 1) != 0)
;
// Tell the C compiler and the processor to not move loads or stores
// past this point, to ensure that the critical section's memory
// references happen after the lock is acquired.
__sync_synchronize();
// Record info about lock acquisition for debugging.
lk->cpu = mycpu();
getcallerpcs(&lk, lk->pcs);
}
然后,我将代码复制到一个新函数acquire2()中,并在系统调用中使用它,其中pushcli()被注释为:
acquire2(struct spinlock *lk)
{
// pushcli(); // disable interrupts to avoid deadlock.
if (holding(lk)) panic("acquire");
// The xchg is atomic.
while (xchg(&lk->locked, 1) != 0) {
;
}
// Tell the C compiler and the processor to not move loads or stores
// past this point, to ensure that the critical section's memory
// references happen after the lock is acquired.
__sync_synchronize();
// Record info about lock acquisition for debugging.
lk->cpu = mycpu();
getcallerpcs(&lk, lk->pcs);
}
但是,错误消息会更改为:panic:mycpu()调用时启用了中断
事实证明,禁用中断是不允许的。因此,不应该使用pushcli()和popcli()。然后我需要弄清楚如何以原子方式运行mycpu()。它的实现是这样的:
// Must be called with interrupts disabled to avoid the caller being rescheduled
// between reading lapicid and running through the loop.
struct cpu *
mycpu(void)
{
int apicid, i;
if (readeflags() & FL_IF) panic("mycpu called with interrupts enabledn");
apicid = lapicid();
// APIC IDs are not guaranteed to be contiguous. Maybe we should have
// a reverse map, or reserve a register to store &cpus[i].
for (i = 0; i < ncpu; ++i) {
if (cpus[i].apicid == apicid) return &cpus[i];
}
panic("unknown apicidn");
}
for循环及其上面的行需要以原子方式执行。我该怎么做?
逆向工作(从问题末尾开始)。。。
我该怎么做?
如果必须这样做;你应该先找到一种不涉及循环的替代方法。
可能有很多可能的替代方案(假设80x86;以某种方式使用"通过.GS或FS找到的CPU本地结构,每个CPU使用不同的TSS并使用该TSS来查找CPU编号,在确保其得到支持并配置为适合后使用RDTSCP
指令,窃取/重新调整DR3等寄存器的用途,使用分页来创建"CPU特定区域",…)
for循环及其上面的行需要以原子方式执行。
为什么?假设该表是在引导期间生成的,并且在引导之后从未更改(并且不支持热插拔CPU);或者假设表只在CPU需要找到自己时以确保其正确的方式进行更改(认识到已脱机的CPU无法尝试找到自己);没有理由(我看得出来)循环需要是原子的。
事实证明,禁用中断是不允许的。
注意:这是不对的,应该是"原来中断必须被禁用"。
这是因为操作系统正在使用"禁用IRQ"来禁用/推迟任务切换。这不是一个好的或必要的要求(内核可以在其他地方有自己的标志/变量,以在不禁用IRQ的情况下禁用/推迟任务切换;以及两种不同类型的自旋锁,其中一种由可能由禁用两者的IRQ处理程序执行的代码使用,另一种从未由可能由只禁用/推迟执行任务且从不禁用IRQ执行的IRQ处理器执行的代码所使用)。当然,这需要对内核进行一些重大更改。
请注意,对于您发布的代码,获取自旋锁极有可能会导致所有IRQ被禁用,然后释放自旋锁会导致IRQ再次启用。这意味着(如果您让用户空间通过.asyscall使用内核的代码)用户空间可以获取任何自旋锁,然后永远占用100%的CPU时间(通过从不释放自旋锁)。换句话说,你打算做的是制造一个巨大的"拒绝服务"安全漏洞。
我考虑过创建系统调用,这些调用实际上是调用spinlock.c中的相应函数来创建锁、获取锁、释放锁等。
这是您所有问题的原因。
与其创建系统调用,不如为用户空间创建一些spinlock代码。您可以通过将内核的函数直接复制(使用"复制和粘贴")到您的用户空间代码(或者可能是库)中,并删除所有不相关的内容,最终得到以下内容:
my_acquire(int *lk)
{
// The xchg is atomic.
while (xchg(lk, 1) != 0) {
;
}
// Tell the C compiler and the processor to not move loads or stores
// past this point, to ensure that the critical section's memory
// references happen after the lock is acquired.
__sync_synchronize();
}
注意:要做到这一点,您还需要将xchg()
和__sync_synchronize()
的代码复制到用户空间;但这应该不是问题。
然而;我应该指出,内核中的代码并不好。通常,您希望使用"测试;然后原子测试和设置"方法,这样(在争用下)您就不会做任何原子操作(而只是在循环中进行测试);并且(对于80x86)您希望在循环中使用pause
指令来提高"循环退出速度",并提高内核中其他逻辑CPU的速度(对于超线程);(对于内核)您不想在测试时禁用IRQ,只想在"原子测试和设置"中禁用IRQ以避免无故损坏"IRQ延迟"。
在用户级上实现自旋锁
一般来说,这也可能是个坏主意。
问题是(如果这不是一场大规模的安全灾难)当你拿着锁的时候,任务切换可能会发生;导致所有其他任务(例如,潜在的许多CPU)在没有任何获取锁的希望的情况下浪费它们的整个时间片旋转(因为锁已经被未运行的任务获取,并且由于任务忙于白白浪费CPU时间旋转而不会运行)。
这个问题的解决方案(这不是一个巨大的安全灾难)是互斥/信号量。具体来说,解决方案是能够告诉内核的调度器"在它能够获得所需的互斥/信号量之前,不要给这个任务CPU时间"。当然,这可以这样实现,即"愉快的情况"(当根本没有争用时)完全在用户空间中处理,并且只有在实际需要时才涉及内核(以及系统调用的开销等)。