-
-
__down()
-
发表于: 1天前 505
-
down()和up(),用于进入和退出信号量临界区。
子函数:
down()->__down_failed()->__down()
难懂的是__down()函数:
void __down(struct semaphore * sem)
{
struct task_struct *tsk = current;
DECLARE_WAITQUEUE(wait, tsk);
tsk->state = TASK_UNINTERRUPTIBLE;
add_wait_queue_exclusive(&sem->wait, &wait);
spin_lock_irq(&semaphore_lock);
sem->sleepers++;
for (;;) {
int sleepers = sem->sleepers;
/*
* Add "everybody else" into it. They aren't
* playing, because we own the spinlock.
*/
if (!atomic_add_negative(sleepers - 1, &sem->count)) {
sem->sleepers = 0;
break;
}
sem->sleepers = 1; /* us - see -1 above */
spin_unlock_irq(&semaphore_lock);
schedule();
tsk->state = TASK_UNINTERRUPTIBLE;
spin_lock_irq(&semaphore_lock);
}
spin_unlock_irq(&semaphore_lock);
remove_wait_queue(&sem->wait, &wait);
tsk->state = TASK_RUNNING;
wake_up(&sem->wait);
}代码寥寥十多行,但是分析过程,像是在参悟经文,最终想出下图,辅助理解:

sem->wait:
等待队列,队列中全是等待进入临界区的进程,既包括TASK_RUNNING状态的,也包括暂时不能挤进临界区,从而进入TASK_UNINTERRUPTIBLE睡眠状态的。
sem->count(down()和up(),分别对它执行+1和-1操作):
≥0时:表示剩余的空闲"门票"数;
<0时:剩以-1后,表示等待进入临界区,且状态为TASK_RUNNING的进程数。
sem->sleepers(仅在__down()函数中使用):
sleepers含义很飘渺,先说它的目的:让暂时没能挤进临界区而睡眠的进程(不会占用临界区中的共享资源),暂时归还"门票"。
所以从"sleepers"字面含义,或者从整体逻辑的使用意图来看,它像是表示暂时没能挤进临界区而睡眠的进程总数。但是从函数的实际实现来看,它并不是在睡眠前+1,睡眠后-1,而是睡眠前固定设置为1,从而相当于一个布尔值,表示最后一个尝试进入临界区的等待进程的执行结果,0表示挤进成功,1表示挤进失败,成了sleepers中的一员。不过,它又不完全被当作布尔值使用,有时也可能变成2,比如图中标号②对应的执行路径:进程A挤进临界区失败,执行schedule()进入睡眠,正好调度正在spin_lock_irq()处自旋的进程B执行。这是因为,代码在这里复用了sleepers变量,也可以单独定义一个变量,表示sem->count的调整值,根据sleepers值计算,替换if语句中的(sleepers-1)。
明确了sleepers的含义,也就能明白,每次有等待进程进入睡眠,"门票"都是在下一个等待进程执行时,即时归还给sem->count了,而不是累加后统一归还。
if()中,为什么对sleepers减1?
两点反直觉:
1. 仅看if语句,难道有一个睡眠进程,不用暂时归还"门票"?
2. 再往前看sem->sleepers++,既然先加,又减回来,为什么不直接别加减?
先看一下,图中标注的所有能执行到if语句的场景:
① 之前没有等待进程,或者上个等待进程,成功进入临界区,当前进程进入自旋锁保护区;
② 上个等待进程,进入睡眠,当前进程进入自旋锁保护区;
③ 睡眠的等待进程被唤醒,回到for(;;)入口(睡眠过程中,可能有其它等待进程运行过,所以sleepers值不一定还是自己睡眠前设置的1)。
可以看出,sleepers-1,是对场景③的考虑,当前进程是从睡眠中醒过来的,那从逻辑上讲,sleepers当然要减1,只不过sleepers可能又被别的等待进程修改过,所以此时sleepers-1的值,可能是-1(睡眠进程取回"门票")或0。而场景①和场景②,当前进程一定是首次进入自旋锁保护区,那也一定没有执行过"sem->sleepers=1 -> 睡眠"这条路径,作为睡眠进程归还过"门票",所以通过+1,抵消为场景③执行的-1操作,从而不能影响sleepers值。
[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
赞赏
- __down() 500
- schedule() 957
- Linux-2.4.0内核加载、启动过程 10508
- KernelSnitch(侧信道泄漏内核地址部分) 2757
- CVE-2014-0038内核漏洞exp分析 4458