一、信号量原理
进程间同步和互斥的核心机制 <sys/sem.h>
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有资源可用,P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信号量的值大于 1,则称之为计数信号量。
本质是一个受保护的整数变量,只能通过原子操作(不可中断的操作)修改值。
临界资源:同一时刻,只允许被一个进程或线程访问的资源。
临界区:访问临界资源的代码段。
原子操作:对信号量的增加或减少,避免多进程并发修改不一致
阻塞机制:当信号量的值为0时,试图获取资源的进程会被阻塞,直到有其他进程释放资源
两种类型:
二值信号量 | 计数信号量 |
0 1 | 0 ~ n |
核心操作:P V原语 Proberen Verhogen
p操作:将信号量值减一,如果>=0,进程可继续执行,如果<0,进程阻塞,放入信号量的等待队列。
V操作:将信号量值加一,如果>0,进程执行,如果<=0,从等待队列中唤醒一个进程,继续执行。
P操作获取资源 V操作释放资源
使用信号量的步骤:
a.创建并初始化
b.进行PV操作
c.销毁信号量
二、参数介绍
1.semget(): 创建或获取信号量集的系统调用
成功返回信号量ID,失败返回-1。
int semget(key_t key, int nsems, int semflg)
key: 键值,用于表示信号量集的唯一性。两个进程使用相同key,可以使用同一信号量。
nsems: 要创建的信号量的数量,内核维护的是一个信号量集。
如果要创建新信号量集,该值需要大于0,如果获取已存在的信号量,可设为0。
semflg: 控制函数行为
IPC_CREAT: 创建信号量集,如果存在返回0。
IPC_EXCL: 确保全新创建。
权限标志:0600。
2.semctl(): 控制信号量集的操作
int semctl(int semid, int semnum, int op, ...)
semid: 信号量集标识符。
semnum: 信号量集的索引,表示要操作哪一个信号。
如果对整个信号量集操作,此参数设为0。
op: 要执行的控制命令,决定函数功能。
SETVAL: 设置semnum指定的信号量的初始值,需配合第四个参数使用。
GETVAL: 获取semnum指定的信号量的当前值。
IPC_RMID: 删除整个信号量集。
IPC_STAT: 获取信号量集的状态信息(创建时间)。
IPC_SET: 设置信号量集的属性(权限)。
第四个参数一般需要传递一个nuion semnum类型的参数。
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
3.semop():执行信号量操作,实现对信号量的原子性增减(PV操作)
int semop(int semid, struct sembuf *sops, unsigned nsops);
semid:信号量的标识符。
sops:指向结构体的指针,定义信号量的操作。
struct sembuf
{
unsigned short sem_num 信号量在集中索引
short sem_op 操作类型(+1 -1)
short sem_flg 操作标志,一般用SEM_UNDO(进程退出时自动回复信号量值)
}
三、代码练习
1.例题:进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符‘a’表示开始使用打印
机,输出第二个字符‘a’表示结束使用,b 进程操作与 a 进程相同。(由于打印机同一时刻
只能被一个进程使用,所以输出结果不应该出现 abab),如图所示:
sem.h:
sem.c:
执行结果:



1.c:
2.c:
执行结果: