生产者-消费者实例
当生产者发现 counter == BUFFER_SIZE
生产者等待(阻塞)
当消费者发现 counter == 0
消费者等待(阻塞)
存在问题:
只用counter
做判断,在图中情况下会导致P2不会被唤醒。
应该需要另一量(信号量)考虑记录有多少进程阻塞等信息,并进行判断。
信号量
当 sem < 0
表示 sem
有多个进程在等待
当 sem > 0
表示有空闲
B,有2个资源可以使用
信号量的定义
queue
代表阻塞队列
V()
的模型:
V(semaphore s)
{
s.value++;
if(s.value <= 0)
{
wakeup(s.queue);
}
}
用信号量解决生产者-消费者问题
empty
表示空闲缓冲区,当缓冲区空时,生产者等待。full
表示缓冲区使用的数量,与empty
相反。
Producer()
中,作为生产者,P(empty)
,V(full)
,test 有没有空闲缓冲区。
Consumer()
中,作为消费者,P(full)
,V(empty)
,test 缓冲区是否为空
生产者什么时候会停?
当缓冲区为满时,空闲缓冲区为空时。
消费者什么时候会停?
当缓冲区为空时,缓冲区全部空闲时。
mutex
互斥信号量,初值为1
....
P(mutex) //申请锁 mutex --
{
......
}
V(mutex)//释放锁 mutex ++;
....
由于mutex
初值为1
,当进程A申请锁时(P(mutex)
),mutex --
,此时为0
。
若此时进程B也申请锁,由于此时进程A已上锁,P(mutex)
将会使mutex
由0
改为 -1
,此时进程将被sleep()
。
当进程A执行完毕,释放锁(V(mutex)
),会使mutex
由-1
改为0
,将唤醒进程B,进程B取得锁。
进程B执行完毕,释放锁(V(mutex)
),将会使mutex
恢复至1
.
信号量临界区保护
**什么是信号量?**通过对这个量的访问和修改,让大家有序推进。
为什么要保护信号量?
既然要对这个量访问和修改,那么一定会出现 竞争条件。
竞争条件:和调度有关的共享数据语义错误。
临界区:
读写信号量的代码一定是临界区,加以保护。
保护原则:
临界区保护的方法
轮换法:
标记法:
非对称标记:
标记与轮换的综合:Peterson算法
flag[i]
可以理解为:当前要执行i
进程
turn = j
可以理解为: 接下来执行j
进程
多个进程:面包店算法
硬件法:关中断
调度由中断产生,控制中断可以组织调度,cli()
关中断,sli()
开中断。
多CPU(多核)无效的原因:cli()
和sli()
会使当前CPU忽略INTR寄存器上的中断标记,但无法控制其他CPU。
硬件法:原子指令法
信号量代码实现
(1)信号量数据结构以及应该在内核态,用户态应定义系统调用:sem_open()
,调用sem_open()
进入内核态调用sys_sem_open()
,sys_sem_open()
中创建信号量。
(2)此生产者要在文件中写入5个数:写之前判断是否有空闲缓冲区:sem_wait()
,sys_sem_wait
中实现P(empty)
的功能:
....
cli();//保护信号量
if(semtable[sd].value -- < 0)
sleep_on(current);
(semtable[sd].queue).push(current); //加入队列,具体实现略
schedule();
sti();
....
Linux 0.11 中:
- 获取空闲缓存
- 启动读入(上锁)
sleep_on 的实现:
在 四:进程运行轨迹的跟踪与统计中提到了sleep_on
的实现,这个队列的实现及其巧妙。
wake_up
会将等待队列全部唤醒,然后根据优先级获取资源。
再回头看lock_buffer()
:因为全部唤醒,优先级最高的会上锁,其他的再次睡眠。