目录
前言
一、信号量的使用
二、信号量的实现
1.有正有负信号量的实现
2.只有正数的信号量的实现
在上一篇文章中,我们介绍了临界区的概念,并用临界区实现了对信号量的保护,使信号量能够拥有正确的语义,通过信号量的数值表达的语义进行操作就可以实现对进程的阻塞和唤醒。操作系统给上层用户提供了信号量定义接口和P、V操作接口后,用户就可以调用这些接口完成进程同步。
根据信号量的含义:
1.信号量是一个需要被多个进程共享的整数;
2.根据信号量的值让相应的值睡眠或唤醒,操作的对象是进程PCB;
3.在操作信号量时需要进行临界区保护,用Lamport面包店算法或开关中断操作等。
应当将信号量的实现放在操作系统内核中,并且将信号量的P、V操作定义为系统调用,这是因为将一个变量放在操作系统内核中所有进程,并且在系统中对PCB进行操作、实现开关中断也比较方便。
一般信号量实现为系统内核中的数据对象,而P、V操作实现为系统提供给上层用户程序的两个系统调用。
POSIX标准定义了四个基本系统调用:
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
这个系统调用用来打开或创建一个信号量变量,name是信号量的名字,oflag选择创建一个新的信号量或者打开一个现有的信号量,mode是权限位,value是信号量的初值。
int sem_unlink(count char* name);
根据名字从系统中删除信号量。
int sem_wait(sem_t* sem);
int sem_post(sem_t* sem);
对应信号量的V操作和P操作。
现在编写一个应用程序来模拟经典的生产者——消费者问题:
1.创建一个生产者进程和五个消费者进程;
2.通过操作同一个文件来建立一个共享数据缓存区;
3.生产者依次向缓存区文件写入0,1,2,···,499五百个整数;
4.每个消费者进程从缓存区中读取100个数,每读出一个数就打印到标准输出上;
5.缓存区最多保存10个数。
main()
{
if(!fork())//生产者进程
{
empty = sem_open("empty", 10);//只输入了名字和初值
full = sem_open("full", 0);
mutex = sem_open("mutex", 1);
for(i = 0; i < 500; i++)
{
sem_wait(empty);
sem_wait(mutex);
读写文件的操作;
sem_post(mutex);
sem_post(full);
}
}
if(!fork())//消费者进程
{
empty = sem_open("empty", 10);
......//类似内容
}
}
核心问题是P、V这两个系统调用的实现,这两个系统调用的核心是对系统内核中的一个整型变量进行操作,并根据变量的数值决定是否对进程进行唤醒或阻塞。
sys_sem_wait(sem_t *sem)
{
cli();//进入区代码,可以是Lamport面包店算法或者硬件原子指令法等;
sem->value--;
if((sem->value) < 0)
{
current->state = SLEEP;//将当前进程阻塞;
将当前进程追加到sem->queue队列的尾部;
schedule;//切换到别的进程
}
sti();//退出区代码
}
sys_sem_post(sem_t *sem)
{
cli();//进入区代码
sem->value++;
if((sem->value) >= 0)
{
从sem->queue队列首部取出一个进程;
current->state = READY;
将该进程加入到就绪队列中;
}
sti();
}
上述的有正有负的信号量,正值表示的是当前拥有的资源数量,当信号量增加1,那么就会唤醒一个阻塞队列中的进程,那么应当是哪个进程被唤醒了?
如果按照顺序,首先唤醒队列头部的进程,就可能出现高优先级的进程一直无法被唤醒,反而处于队列前面的低优先级进程先被唤醒。
为了避免这一情况的出现,当sem_post决定唤醒队列中的进程时,先将阻塞在信号量上的所有进程都唤醒,由CPU调度算法schedule()来决定由哪个进程获得信号量。所有被唤醒的进程都需要检测自己是否获得了信号量,没有获得信号量的继续阻塞状态。
sys_sem_wait(sem_t *sem)
{
cli();//进入区代码,可以是Lamport面包店算法或者硬件原子指令法等;
while((sem->value) = 0)
{
将当前进程追加到sem->queue队列的尾部;
schedule;//切换到别的进程
}
sem->value--;
sti();//退出区代码
}
sys_sem_post(sem_t *sem)
{
cli();//进入区代码
sem->value++;
让sem->queue队列中的所有进程都加入就绪队列;
sti();
}