信号量
信号量是一个计数器,用来表示系统资源的数量,信号量用于多进程对共享数据对象的访问,来实现程序的同步互斥。为了正确实现信号量,信号量的加减操作必须是原子操作,因此,信号量也是在内核中实现的。
先看一下同步互斥和原子操作:
同步互斥
互斥
一个进程占用资源,其它进程就不能使用该资源,进程间的这种关系为进程互斥。
这些只能被一个进程同时使用的资源被称为临界资源。
进程中访问临界资源的一段需要互斥执行的代码称为临界区。
访问临界区的条件是临界资源没有被占用。
举个例子:假如厕所只有一个坑,一个人蹲上了,那么其他的人就不能再蹲了,这个坑就是临界资源。
同步
当进程A的执行依赖进程B的执行结果时,进程之间的执行必须存在一定的顺序关系,这种关系称为进程的同步。
原子操作
原子操作是指一次不存在任何中断和失败的操作,要么操作成功,要么操作没有执行,不存在部分执行的状态。
在此概念产生的那个年代,物理学的最小单位还是原子,因此这里原子是指不可在分割的整体。
信号量和 P、V原语
P、V操作
假设用S表示资源数的个数,P、V操作分别表示对S的减和加操作
- P()操作(Prolaag,荷兰语减少)
- S减n,表示可用资源数减少
- 如果S<0,进入等待,否则继续
- V()操作(Verhoog,荷兰语增加)
- S加n,表示可用的资源数增加
- 如果s>=0,则唤醒一个等待中的进程
信号量的伪代码实现
struct semaphore
{
int value;
pointer_PCB queue;
};
P原语
P(s)
{
s.value = s.value--;
if (s.value < 0)
{
//该进程状态置为等待状状态
//将该进程的PCB插入相应的等待队列s.queue末尾
}
}
V原语
V(s)
{
s.value = s.value++;
if (s.value < =0)
{
//唤醒相应等待队列s.queue中等待的一个进程
//改变其状态为就绪态
//并将其插入就绪队列
}
}
信号量相关函数
semget函数
- 功能
- 用来创建和访问一个信号量集
- 原型
int semget(key_t key, int nsems, int semflg);
- 参数
- key–信号集的名字
- nsems–信号集中信号的个数
- semflg–九个权限标志构成
返回值
- 成功返回一个非负数,表示信号集的表示id,失败返回-1。
shmctl函数
- 功能
- 用于控制信号量集
- 原型
int semctl(int semid, int semnum, int cmd, ...);
- 参数
- semid–由semget函数返回的信号标识id
- semnum–信号集中信号量的序号
- cmd–将要采取的动作
返回值
- 成功返回0,失败返回-1
semop函数
- 功能
- 用来创建和访问一个信号量集
- 原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
- 参数
- semid–信号量标识id
- sops–指向一个结构数值的指针
- nsops–信号量个数
- 返回值
- 成功返回0,失败返回-1
sembuf结构体
struct sembuf { short sem_num; short sem_op; short sem_flg; };
- sem_num–信号量的编号
- sem_op–信号量一次PV操作时加减的数值,一般只会用到两个值(-1和1)分别标识减1和加1
- sem_flag–两个取值为IPC_NOWAIT或SEM_UNDO
相关指令
ipcs -s
- 列出当前信号量信息
ipcrm -s semid
- 删除id为semid的信号量,一般由创建它的进程删除,不需要手动删除