首页
社区
课程
招聘
__down()
发表于: 1天前 505

__down()

1天前
505

down()和up(),用于进入和退出信号量临界区。

子函数:

down()->__down_failed()->__down()

up()->__up_wakeup()->__up()


难懂的是__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内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回