- 由于 linux-0.11 使用的是单核 CPU,所以我们可以利用关中断法来对信号量临界区进行保护,信号量的代码实现如下
L18 信号量的代码实现
应用层信号量代码实现
由于 linux-0.11 使用的是单核 CPU,所以我们可以利用关中断法来对信号量临界区进行保护,信号量的代码实现如下图:
- 首先我们应该要有一个能够表示信号量的东西,这里是用
semtable[i]
来表示信号量,semtable
是一个内核中的信号量表,其中每一个”数据“都包含名称 name 、资源个数 value 和 PCB(即这里的 queue),通过sys_sem_open
来从信号量表中找到对应于 name 的信号量,比如sd=sem_open("empty")
就利用系统调用sem_open
从内核中找到了名为 empty 的信号量; - 在找到了信号量以后,就要通过
P(semaphore s)
和V(semaphore s)
对其进行操作,例如这里的sys_sem_wait(int sd)
就对应于P(semaphore s)
,首先要cli()
关中断,以对 sd 信号量进行保护,然后进行P(semaphore s)
对应的操作,操作结束后,再sti()
关中断,就在代码上实现了P(semaphore s)
,V(semaphore s)
同理。
- 首先我们应该要有一个能够表示信号量的东西,这里是用
操作系统内部的信号量应用
信号量不仅可以用于上层应用,使上层应用的进程同步,也可以用于操作系统内部,来保证操作系统内部可以以正确的顺序运行。
以操作系统内核读磁盘块为例,在进程启动磁盘读以后就要睡眠,等待磁盘读完后由磁盘中断将其唤醒,
sleep_on
就是让进程睡眠的函数,同样用关中断法来保护“信号量”,然后调用sleep_on
函数。sleep_on
函数是将当前进程加入阻塞队列的队首,并将当前进程的状态改为阻塞态(不可中断的睡眠状态),然后通过schedule()
调度别的进程。下面的图就展示了如何将当前进程加入阻塞队列的队首。
sleep_on
函数用于让进程睡眠,而wake_up
函数则用于唤醒进程,如下图所示的wake_up
函数,通过(**p).state=0
将进程的状态改为就绪态,并通过*p=NULL
将处于就绪态的进程从阻塞队列中移出,这样就唤醒了进程。由于进程在陷入睡眠时,是从
schedule
函数出去的,所以当进程被唤醒时,就要从中断出去的地方继续向下执行,于是if(tmp) tmp->state=0;
就将阻塞队列中的下一个睡眠进程唤醒,形成链式反应,最终将阻塞队列中的所有进程都唤醒了,所有被唤醒的进程都处于就绪态,这时就会根据优先级来决定是将哪个进程变为运行态。所以为什么
lock_buffer(buffer_head *bh)
用while(bh->b_lock)
就说得通了,因为在选择了一个进程使其成为运行态以后,bh->b_lock=1
,相当于除了处于运行态的进程,其他被唤醒处于就绪态的进程要再一次陷入睡眠,等待下一次被唤醒。