信号量作为一种同步机制,在每个成熟的现代操作系统的实现过程中,起着不可替代的作用,对于Linux也不例外,在Linux 2_4_x下,实现内核信号量机制的代码虽然不长,但由于涉及到多个进程间的相互干扰,并且在Linux发展过程中,不断进行优化,所以很难于理解,在 讲解Linux源代码的各类文章中,也大都对此语焉不详,本人通过认真阅读,对这部分代码有了一定的了解,这里介绍出来,希望对大家有所帮助。

信号量作为一种同步机制,在每个成熟的现代操作系统的实现过程中,起着不可替代的作用,对于Linux也不例外,在Linux 2_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 operation\n\t"
3 LOCK "decl %0\n\t" /* --sem->count */
4 "js 2f\n"
5 "1:\n"
6 ".section .text.lock,\"ax\"\n"
7 "2:\tcall __down_failed\n\t"
8 "jmp 1b\n"
9 ".previous"
10 :"=m" (sem->count)
11 :"c" (sem)
12 :"memory");
}

__down_fail()调用__down(),
(arch/i386/kernel/semaphore.c):
asm(
".text\n"
".align 4\n"
".globl __down_failed\n"
"__down_failed:\n\t"
13 "pushl 陎\n\t"
14 "pushl 韝\n\t"
15 "pushl 靫\n\t"
16 "call __down\n\t"
17 "popl 靫\n\t"
18 "popl 韝\n\t"
19 "popl 陎\n\t"
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);
25
26 spin_lock_irq(&semaphore_lock);
27 sem->sleepers ;
28 for (;;) {
29 int sleepers = sem->sleepers;
30
31 /*
32 * Add "everybody else" into it. They aren't
33 * playing, because we own the spinlock.

文章整理:西部数码--专业提供域名注册虚拟主机服务
http://www.west263.com
以上信息与文章正文是不可分割的一部分,如果您要转载本文章,请保留以上信息,谢谢!