概念描述
- 英文:
semaphore
简称SEM,主要用来进行进程间同步 - 本质:内核维护的一个正整数,可对其进行各种+/-操作
- 分类:systemV 信号量、POSIX 有名信号量、POSIX 无名信号量
- 用途:用来标示系统中可用资源的的个数,协调各个进程有序的访问资源,防止发生冲突
- P操作:程序在进入临界区之前要对资源进行申请
- V操作:程序离开临界区后要释放相应的资源
通信原理
- 类似于房卡,不是单个值,而是一组(实际上是数组)信号量元素构成
- 将信号量初始设置成一个绝对值
- 在信号量当前值的基础上加一个数量
- 在信号量当前值的基础上减去一个数量,降到0以下再去访问会引起阻塞
- 阻塞进程一直等待其他进程修改该信号量的值,直到恢复正常运行
- 信号量本身无意义,通常会与一块临界资源(共享内存)关联使用
编程接口
-
获取信号量ID
a. 头文件<sys/ipc.h> <sys/sem.h>
b.int semget(key_t key,int nsem, int semflg)
c. 函数参数:key:
用来表示信号量的键,通常使用值IPC_PRIVATE或者由ftok创建nsem
: 信号的数量,所有的信号量放在一个数组里semflg
:位掩码,用来设置信号量的权限或者检查一个已有信号量的权限
IPC_CREAT: 如果找不到指定的key相关联的信号量,创建一个信号量集合
IPC_EXCL: 若指定了IPC_CREAT且指定key相关联的信号量存在,报EXIST错误
d. 返回值
- 成功:返回操作信号量描述符
- 失败:返回-1
-
设置信号量
a. 头文件<sys/ipc.h> <sys/sem.h>
b.int semctl(int semid, int semnum, int cmd ..)
c. 函数参数:semid
: 信号量的描述符,标识符;用于操作信号量semnum
: 信号量的数量,所有的信号量放在一个数组内cmd
:
IPC_RMID :删除信号量及相关联的内核smid_ds数据结构
IPC_STAT: 获取semid_ds的副本
IPC_SET: 设置semid_ds的数据结构
GETVAL: 获取信号集中地 semnum个信号量的值
GETALL: 获取所有信号量的值
SETVAL: 设置信号集中的第semnum个信号量的值
d. 函数返回值
- 成功:根据cmd命令,返回不同的值
- 失败:-1
-
信号量P/V操作
a. 头文件<sys/ipc.h> <sys/sem.h>
b.int semop(int semid,struct sembuf *sops, size_t nsops);
c. 函数参数:semid
:信号的IPC标识符sops
: 指向数组的指针,数组中包含了需要执行的操作struct sembuf { unsigned short sem_num; // 标识要操作的信号集中的信号量 /* sem_op如下信息: a.若大于0 将sem_op的值加到信号量值上 b. 若等于0 ,则对信号量进行检查,确定其当前值是否为0,若为0则操作结束;若不为0,则一直阻塞,直到为0结束 c. 若小于0,则将信号量值减去sem_op,最后结果大于或等于0,操作立即结束;若最后结果小于0,则当前进程会阻塞 */ short sem_op; /*信号操作对标记,SEM_UNDO,IPC_NOWAIT*/ short sem_flag; }
nsops
:数组的大小
d. 返回值
- 成功:根据cmd命令,返回不同的值
- 失败:-1,并设置errno全局变量
使用流程
- 使用semget 创建或打开一个信号量集
- 使用semctl SETVAL 或 SETALL 操作初始化集合中的信号量(其中一个进程操作即可,内核中维护,对其它进程是全局可见的)
- 使用semop操作信号量的值,多个进程通过多信号量值的操作来表示一些临界资源的获取和释放
- 当所有进程不再需要信号量集时,使用semctl IPC_RMID 操作删除这个信号量集(其中一个进程操作即可)
编程案例
sem_create.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
union semnum {
int val;
struct semid_ds *buf;
unsigned short int *array;
struct seminfo *__buf;
};
int main ()
{
int sem_id;
key_t key;
if ((key =ftok(".",111)) == -1) {
printf("create key failed \n");
_exit(-1);
}
//使用semget 创建或打开一个信号量集
if ((sem_id = semget(key,3,IPC_CREAT | 0770)) == -1) {
printf("create shm_id failed \n");
_exit(-1);
}
printf("sem_id is :%d\n",sem_id);
//获取信号量中的值
int sem_value;
sem_value = semctl(sem_id , 0, GETVAL);
printf("sem value is %d\n",sem_value);
//使用semctl SETVAL 或 SETALL 操作初始化集合中的信号量。通过sem_union设置信号量的值
union semnum sem_union;
sem_union.val = 2;
semctl(sem_id, 0, SETVAL, sem_union);
sem_value = semctl(sem_id ,0, GETVAL);
printf("sem value after reset is %d\n",sem_value);
//使用semop操作信号量,对信号量进行pv操作,这里是-1
struct sembuf sops,sops2,sops3;
sops.sem_num = 0;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;
/*sops.sem_num = 1;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;
sops.sem_num = 2;
sops.sem_op = -1;
sops.sem_flg = SEM_UNDO;*/
if( -1 == (semop(sem_id,&sops, 1)) ) {
printf("semop set failed \n");
_exit(-1);
}
sem_value = semctl(sem_id, 0, GETVAL);
printf("sem value after semop is %d\n",sem_value);
sleep(30);
semctl(sem_id, 0 ,IPC_RMID);
return 0;
}
最终的输出如下:
sem_id is :524314
sem value is 0
sem value after reset is 2
sem value after semop is 1