信号量的结构可以简单朴素地看成一个结构体:
struct semaphore {
int count;
queue blockQueue;
}
- 初始化
- 在某一进程/线程发送semSignal
- 在某一进程/线程发送semWait
这些操作可以假设为原子的,操作系统提供了保证。
而信号量的值只可为0或1的信号量种类,称之为二元信号量。 而仅可以在当前进程/线程执行semSignal,且在当前进程/线程执行semWait的二元信号量,成为互斥量(mutex)。
不同于自旋锁或特殊的机器指令对某一进程/线程进行忙等,信号量不会占用CPU时间片,而是适时地将进程/线程状态置为阻塞态,将时间片让出给其他进程/线程使用,效率上优先于忙等。当然,相较而言,进程间切换也会有一定的消耗。
- semSignal将会使一个semaphore增加1。
- semWait将会使一个semaphore减少1。
当执行了semWait后,sem >= 0时,当前进程将可以继续执行。 当执行了semWait后,sem < 0时,当前进程将被阻塞,同时进入阻塞队列。 当执行了semSignal后,sem += 1,从阻塞队列中取出一个进程设为就绪态,进入就绪队列,等待内核调度其得到CPU时间片。 (如何从阻塞队列中取出进程,以什么样的顺序取出进程,涉及到策略,例如使用了简单的FIFO可称为强信号量,而没有规定以什么顺序取出的称为弱信号量)
有一些进程,每一个都需要进入临界区域,可以使用信号量解决。
semaphore s = 1;
function aFunction() {
while (true) {
semWait(s);
// 临界区操作
semSignal(s);
}
}
信号量初始化为1,首先执行semWait的进程将信号量s执行减1操作,此时s == 0,非负,当前进程得以进入临界区;下一个进程再次调用semWait时,s变为-1,该进程被阻塞,进入阻塞队列;当第一个进程执行完临界区操作后,调用semSignal,s执行+1操作,从阻塞队列中取出一个进程变为就绪态,等待调用。
信号量的操作是原子性的是一切理论的基础,没有原子性,信号量也不能提供安全。
简单说,信号量就是一个提供了原子性,且内部有一个阻塞队列的结构体,通过对其执行semSignal和semWait原子性地改变内部的count,同时在适当情况下将调用了semWait的进程移进或移出其阻塞队列。