【 tulaoshi.com - Linux 】
信号量作为一种同步机制,在每个成熟的现代操作系统的实现过程中,起着不可替代的作用,对于Linux也不例外,在Linux2_4_x下,实现内核信号量机制的代码虽然不长,但由于涉及到多个进程间的相互干扰,并且在Linux发展过程中,不断进行优化,所以非常难于理解,在讲解Linux源代码的各类文章中,也大都对此语焉不详,本人通过认真阅读,对这部分代码有了一定的了解,这里介绍出来,希望对大家有所帮助。
信号量作为一种同步机制,在每个成熟的现代操作系统的实现过程中,起着不可替代的作用,对于Linux也不例外,在Linux2_4_x下,实现内核信号量机制的代码虽然不长,但由于涉及到多个进程间的相互干扰,并且在Linux发展过程中,不断进行优化,所以非常难于理解,在讲解Linux源代码的各类文章中,也大都对此语焉不详,本人通过认真阅读,对这部分代码有了一定的了解,这里介绍出来,希望对大家有所帮助。 首先看看信号量的概念: 1.信号量的类型定义: 每个信号量至少须记录两个信息:信号量的值和等待该信号量的进程队列。它的类型定义如下:(用类PASCAL语言表述) semaphore = record value: integer; queue: ^PCB; end; 其中PCB是进程控制块,是操作系统为每个进程建立的数据结构。s.value>=0时,s.queue为空; s.value<0时,s.value的绝对值为s.queue中等待进程的个数; 2.PV原语: 对一个信号量变量可以进行两种原语操作:p操作和v操作,定义如下: procedure p(var s:samephore); { s.value=s.value-1; if (s.value<0) asleep(s.queue); } procedure v(var s:samephore); { s.value=s.value+1; if (s.value<=0) wakeup(s.queue); } 其中用到两个标准过程: asleep(s.queue);执行此操作的进程的PCB进入s.queue尾部,进程变成等待状态wakeup(s.queue);将s.queue头进程唤醒插入就绪队列。s.value初值为1时,可以用来实现进程的互斥。p操作和v操作是不可中断的程序段,称为原语。如果将信号量看作共享变量,则pv操作为其临界区,多个进程不能同时执行,一般用硬件方法保证。一个信号量只能置一次初值,以后只能对之进行p操作或v操作。 再来看看在对应的Linux2.4.x具体实现中,信号量的数据结构: struct semaphore { atomic_t count; int sleepers; wait_queue_head_t wait; };可以看到相对于操作系统原理中的定义,数据结构多了一个sleepers成员,这个变量是为使主要分支代码执行速度更快,所作的优化。 其中,count相当于资源计数,为正数或0时表示可用资源数,-1则表示没有空闲资源且有等待进程,而其他的负数仅仅一个中间过程,当所有进程都稳定下来后,将最终变为-1。 sleepers相当于一个状态标志,它仅有0和1两个值,有时会出现2,但也只是中间状态,并没有实际意义。当sleepers为0时,表示没有进程在等待,或等待中的这些进程正在被唤醒过程中。当sleepers为1时,表示至少有一个进程在等待中 wait是等待队列。 在这里可以看到,等待进程的数量并不被关心。 下面我们来看函数,Linux2.4.x具体实现中,down()函数相当于p操作,up()函数相当于v操作。在down()函数中,count做原子减1操作,如果结果不小于0[第4行],则表示成功申请,从down()中返回,如果结果为负(大多数情况是-1,注意判断“结果不小于0”的原因,是因为结果有可能是其他负数),表示需要等待,则调用__down_fail()[第7行],(include/asm-i386/semaphore.h):static inline void down(struct semaphore * sem){1 __asm__ __volatile__(2 "# atomic down operationnt"3 LOCK "decl %0nt" /* --sem->count */4 "js 2fn"5 "1:n"6 ".section .text.lock,"ax"n"7 "2:tcall __down_failednt"8 "jmp 1bn"9 ".previous"10 :"=m" (sem->count)11 :"c" (sem)12 :"memory");}__down_fail()调用__down(),(arch/i386/kernel/semaphore.c):asm(".textn"".align 4n"".globl __down_failedn""__down_failed:nt"13 "pushl %eaxnt"14 "pushl %edxnt"15 "pushl %ecxnt"16 "call __downnt"17 "popl %ecxnt"18 "popl %edxnt"19 "popl %eaxnt"20 "ret");__down()用C代码实现。(arch/i386/kernel/semaphore.c):void __down(struct semaphore * sem){21 struct task_struct *tsk = current;22 DECLARE_WAITQUEUE(wait, tsk);23 tsk->state = TASK_UNINTERRUPTIBLE;24 add_wait_queue_exclusive(&sem->wait, &wait);2526 spin_lock_irq(&semaphore_lock);27 sem->sleepers++;28 for (;;) {29 int sleepers = sem->sleepers;3031 /*32 * Add "everybody else" into it. They aren't33 * playing, because we own the spinlock.34 */35 if (!atomic_add_negative(sleepers - 1, &sem->count)) {36 sem->sleepers = 0;37 break;38 }39 sem->slee