内核同步原语
一. 情景
首先,初始化一个信号量,调用 sema_init(sem, 1)。 这相当于初始化一个
互斥量。之后有三个进程申请这个信号量保护的互斥资源,p[0..2]。而且,p0
首先申请到,所以p1和p2会休眠,之后,p0释放这个信号量。
p[0..2]的申请过程,使用a[0..2]来分别代表。
/**
* 内核版本 2.6.10
* 用到的函数和结构体的定义都是在
* include/asm-i386/semaphore.h
**/
/**
* 定义
**/
struct semaphore {
atomic_t count;
int sleepers;
wait_queue_head_t wait;
};
static inline void sema_init (struct semaphore *sem, int val)
{
/*
* *sem = (struct semaphore)__SEMAPHORE_INITIALIZER((*sem),val);
*
* i'd rather use the more flexible initialization above, but sadly
* GCC 2.7.2.3 emits a bogus warning. EGCS doesn't. Oh well.
*/
atomic_set(&sem->count, val);
sem->sleepers = 0;
init_waitqueue_head(&sem->wait);
}
这里,我们可以看出,初始化过程中,是将 count 设置成了1,sleepers设置成0
并且初始化了等待队列的头wait。
现在,p0申请信号量sem,调用down()函数。
static inline void down(struct semaphore * sem)
{
might_sleep();
__asm__ __volatile__(
"# atomic down operation\n\t"
LOCK "decl %0\n\t" /* --sem->count */ /*@a1: 锁住总线,原子的将count减 1,这时候,count是0!*/
"js 2f\n" /*@a1: 只有count减为负数的时候,才会跳转。所以直接执行LOCK_SECTION_START("")
,申请成功。
@a2: 由于count在为0的时候减1变成了-1,出现了负数,执行跳转!
@a3: 在p3到达这里的时候,sem->count的值变成-2,调用__down_failed
*/
"1:\n"
LOCK_SECTION_START("")
"2:\tlea %0,%%eax\n\t" /*@a2: 将count的值,存入eax,调用__down_failed,p2需要休眠*/
"call __down_failed\n\t"
"jmp 1b\n"
LOCK_SECTION_END
:"=m" (sem->count)
:
:"memory","ax");
}
由于以下代码 arch/i386/kernel/semaphore.c
asm(
".section .sched.text\n"
".align 4\n"
".globl __down_failed\n"
"__down_failed:\n\t"
#if defined(CONFIG_FRAME_POINTER)
"pushl %ebp\n\t"
"movl %esp,%ebp\n\t"
#endif
"pushl %edx\n\t"
"pushl %ecx\n\t"
"call __down\n\t"
"popl %ecx\n\t"
"popl %edx\n\t"
#if defined(CONFIG_FRAME_POINTER)
"movl %ebp,%esp\n\t"
"popl %ebp\n\t"
#endif
"ret"
);
这就将down_failed函数,在i386环境下,定义为了以上的这段代码,其实调用的是__down函数。
fastcall void __sched __down(struct semaphore * sem)
{
struct task_struct *tsk = current; /*创建task struct,创建和p2相关的wait_queue*/
DECLARE_WAITQUEUE(wait, tsk);
unsigned long flags;
tsk->state = TASK_UNINTERRUPTIBLE; /*将当前进程的状态改为TASK_UNINTERRUPTIBLE*/
spin_lock_irqsave(&sem->wait.lock, flags); /*使用信号量等待队列的自旋锁构建临界区*/
add_wait_queue_exclusive_locked(&sem->wait, &wait); /*将当前进程加入到信号量的等待队列*/
sem->sleepers++; /*a2:这时候,将sem的sleeper加一,变成1*/
for (;;) {
int sleepers = sem->sleepers; /*a3:当p3到达这里的时候,sleepers临时变成2,count变成-2*/
/*
* Add "everybody else" into it. They aren't
* playing, because we own the spinlock in
* the wait_queue_head.
*/
if (!atomic_add_negative(sleepers - 1, &sem->count)) {
/*a2:原子的将sleeper-1加到count上,这时候,count是-1,sleeper-1是0,
所以,结果是-1,返回true,整个表达式返回假
a3:由于count是-2,sleeper减一是1,所以结果还是负数,整个表达式
返回假。
在这里,我们可以总结出,这个算法的核心思想:
开始:
count是-1,sleepers是1,这样结果是-1,
迭代:
count是-2,这时候sleepers是2,这样结果还是-1,
count变成-1,sleepers变成1。
所以,无论执行多少次,count都是临时会变成-2,然后
sleepers也是临时变成2,之后原子的相加,结果是count
变回-1,sleepers变回1.
*/
sem->sleepers = 0;
break;
}
sem->sleepers = 1; /* us - see -1 above */ /*当count为-1的时候,就会执行到这里*/
spin_unlock_irqrestore(&sem->wait.lock, flags); /*结束临界区*/
schedule(); /*开始调度*/
spin_lock_irqsave(&sem->wait.lock, flags);
tsk->state = TASK_UNINTERRUPTIBLE;
}
remove_wait_queue_locked(&sem->wait, &wait);
wake_up_locked(&sem->wait);
spin_unlock_irqrestore(&sem->wait.lock, flags);
tsk->state = TASK_RUNNING;
}
当,p1释放信号量的时候,会唤醒sem->wait中的进程。这时候,count的值会变成0,然后,例如p2被唤醒,
这时候,sleepers是1,所以 atomic_add_negative(sleepers - 1, &sem->count) 之后,count仍然是0,sleepers是0!
下一次,p3被唤醒的时候,count是1, atomic_add_negative(sleepers - 1, &sem->count) 之后,count变成了0,
sleepers变成了0!
所以这里,我们可以看出,只要是有进程等待在wait队列上,有进程释放信号量的时候,count就会在第一次
的时候从-1变成0,sleepers有1变成0,之后一直保持!
如果在这期间,还有进程申请信号量,那么count就会再次变成-1,而sleepers也会再次变成1。重复上述过程。
直到最后一个进程释放信号量的时候,count变成了1,sleepers变成了0!回到了最初状态!
阅读(754) | 评论(0) | 转发(0) |