|
平台:x86 32位
内核:2.6.24
在 linux 中,P 函数被称为 down ;V 函数被称为 up 。
在进入临界区时,用 down 函数对使用计数器减 1,如果成功调用,则该线程就拥有了信号量,从而被赋予访问由该信号量保护的临界区的权力;当信号量计数器为 0 时,其他进程就不能再进入临界区。
down() 函数定义在 include/asm-x86/Semaphore_32.h 中:
static inline void down(struct semaphore * sem)
{
might_sleep();
__asm__ __volatile__(
"# atomic down operation\n\t"
LOCK_PREFIX "decl %0\n\t" /* --sem->count */
"jns 2f\n"
"\tlea %0,%%eax\n\t"
"call __down_failed\n"
"2:"
:"+m" (sem->count)
:
:"memory","ax");
}
decl %0 : %0 表示 sem->count ,这里信号量计数器减 1。
jns 2f : 成功获取信号量。
lea %0, %%eax : 将信号量结构体指针放到 eax 里。这里需要注意的是,结构体的指针(地址)和结构体第一个元素(sem->count)的地址是一样的。
call __down_failed :无法获取信号量时调用 __down_failed 函数处理这种情况,__down_failed 函数的参数用 eax 来传递。
如果用 down() 获取不到信号量时,进程进入睡眠,该进程被放到与该信号量关联的等待队列上。同时,该进程被置为 TASK_UNINTERRUPTIBLE 状态,也就是说处于“非中断”状态,这时候它无法接收信号,这就建立了不可杀进程(ps 命令输出中的 "D state"),这种情况往往让用户感到不爽。
在退出临界区时,必须调用 up() 以释放信号量。该函数的定义为:
static inline void up(struct semaphore * sem)
{
__asm__ __volatile__(
"# atomic up operation\n\t"
LOCK_PREFIX "incl %0\n\t" /* ++sem->count */
"jg 1f\n\t"
"lea %0,%%eax\n\t"
"call __up_wakeup\n"
"1:"
:"+m" (sem->count)
:
:"memory","ax");
}
如果 sem->count 为负,那么表明有进程在等待,在加 1 后为 0 ,然后使用函数 __up_wakeup() 函数唤醒因等待信号量而睡眠的某个进程,此后这个进程便可进入临界区,而所有其他仍在等待的进程继续睡眠。
正如上面所说,使用 down() 可能会建立不可杀进程,这会令用户不爽。所以内核还提供了 down_interruptible() 函数,它的工作方式和 down() 相同,它在调用成功时返回 0 值,如果操作被中断则返回非零值。和 down() 不同的是,如果进程无法获得信号量,进程在进入睡眠时是被置为 TASK_INTERRUPTIBLE 状态,这样它可以被信号唤醒。对该函数的正确使用,需要始终检查返回值,并作出相应的响应。
down_trylock() 函数试图获取信号量,如果失败,则进程不会进入睡眠等待信号量,而是继续正常执行。它的定义为:
/*
* Non-blockingly attempt to down() a semaphore.
* Returns zero if we acquired it
*/
static inline int down_trylock(struct semaphore * sem)
{
int result;
__asm__ __volatile__(
"# atomic interruptible down operation\n\t"
"xorl %0,%0\n\t"
LOCK_PREFIX "decl %1\n\t" /* --sem->count */
"jns 2f\n\t"
"lea %1,%%eax\n\t"
"call __down_failed_trylock\n\t"
"2:\n"
:"=&a" (result), "+m" (sem->count)
:
:"memory");
return result;
}
从上面的注释也可以看到,如果获取信号量,该函数返回 0 值,否则返回非零值。
|
|