信号灯集详细使用说明

信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间的同步的机制
信号灯种类:
posix有名信号灯。
posix基于内核的信号灯(无名信号灯)
System V信号灯(IPC对象)
System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。而posix信号灯指的是单个计数信号灯
可以通过命令
ipcs -a查看所有的IPC对象使用情况
ipcs -m 查看共享内存
ipcs -q 查看消息队列
ipcs -s 查看信号灯集
ipcrm -m shmid 删除shmid的共享内存
ipcrm -q msgid 删除消息队列
ipcrm -s semid 删除信号灯集
主要函数ftok,semget,semop,semctl
key_t ftok(const char *pathname, int proj_id);
参数:
pathname 指定的文件夹或者文件
proj_id 是子序号。
返回值:成功返回key值,失败返回:-1
ftok()函数会获取第一个参数(文件或目录)的属性信息,并把ftok()的第二个参数的后8位,st_dev的后两位,st_ino的后四位,构成了一个键值。信号灯集详细使用说明
所以由于ftok()只取参数proj_id的后8位(0-255),所以调用ftok两次传入不同的值,生成的key值可能相同,我不是我们想要的,所以我们一般都传一个char类型的字符(如'a')。
查询文件inode号的方法是: ls -i
那么问题来了,两个进程调用ftok()函数传入的参数相同(如ftok("/home/file",'a')),生成的key值是否一定相同?答案当然不是了。
当把指定的文件或者文件夹删除重新创建后,inode号由操作系统根据当时文档系统的使用情况分配,因此和原来不同,所以得到的inode号也不同。
所以我们要确保key_t值不变,确保ftok传入的文件或文件夹不被删除,如根目录('/'),或者不用ftok函数,指定一个固定的key_t值。
另外在有的操作系统上,有多个文件系统,会出现分布在不同的文件系统上的两个文件或文件夹有相同的inode号,此时用ftok对两个具有相同的inode号不同的文件或文件夹但进行操作,并且proj_id参数不变,得到的key_t值就会相同。但是这种情况相当少见罢了。
因为在开发中涉及多种系统平台,在系统移植时发现ftok()函数在不同平台下存在一定的差异性。当然,根本原因不在于ftok()本身,而应该是操作系统对于文件系统管理的差异性。
还有一个问题就是如果两个进程想通过相同的键值来映射同一片共享内存,但是传入的文件或文件夹相同proj_id也相同,但是传入的文件或文件夹不存在,那么这整个进程会得到相同的键值吗?
答案是会得到相同的键值,ipcs -m后会发现的确两个进程都映射到一片共享内存上去了,但是得到的键值都是0xffffffff。虽然成功映射,但是这么做很不安权,别的进程也可以通过一个不存在的文件来映射到这片内存上去。
int semget(key_t key, int nsems, int semflg);
功能:创建/打开信号量集合
参数:
key:键值,可以通过ftok()来获得,或者指定一个key
nsems:指定的信号量集合中的信号量个数,比如指定2就创建两个
semflg,: IPC_CREAT、IPC_EXCL,创建时需要指定权限
返回值:返回该集合的标识符sem_id,失败-1
如:sem_id=semget(ftok("/",'a'),2,IPC_CREAT|IPC_EXCL|0666);
如果之前创建过,那么就可以直接接收它的sem_id。sem_id=semget(ftok("/",'a'),0,0);由于之前创建过了,所以后面两个参数可以直接填0,当然这么写也不会错sem_id=semget(ftok("/",'a'),2,0666);
sem_id的值从0开始,每次加32768,而不是1。
int semctl(int semid, int semnum, int cmd, ...);
功能:信号量集合的控制
参数:
semid:指定要操作的集合
semnum: 要操作的信号量的编号,编号从0开始
cmd:常用的SETVAL、GETVAL、IPC_RMID
返回值:和cmd有关,失败-1。
SETVAL:设置信号灯的值,给信号灯初始化,需要用到第四个参数:
第四个参数类型如下,需要自己定义:
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */
}semun_t;
SETVAL这个参数就是把val的值赋给信号灯。如下:
semun_t my_semun0,my_semun1;
my_semun0.val=0;
my_semun1.val=0;
semctl(sem_id,0,SETVAL,my_semun0);//上面我们创建了两个信号灯,因为编号从0开始的,所以
semctl(sem_id,1,SETVAL,my_semun0);//这两个信号灯的编号分别是0和1,给他们都是初始化成0
GETVAL:获取信号灯的值, 返回值是获得信号灯值。
比如:value = semctl(semid, 0, GETVAL);
IPC_RMID:从系统中删除信号灯集合
比如:semctl(semid, 0, IPC_RMIDL);
int semop ( int semid, struct sembuf *opsptr, unsigned nops);
功能:就是对信号量集合中的信号量进行PV操作
参数:
semid:这个参数就不多说了
opsptr:这个参数传入的是结构体的地址
struct sembuf{
unsigned short sem_num; /* semaphore number */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
成员分析:
sem_num:表示要操作的信号量的编号
sem_op:表示进行P或者V操作, 比如:sem_op =1(+1)
sem_op = -1(-1)
sem_op = 0, 那么semop函数会等到该信号量的值变为0为止。
sem_flg:
0(最常用的),表示semop函数的操作是阻塞的,直到成功为止。
IPC_NOWAIT,表示semop函数的操作是非阻塞的,如果操作没有成功,立刻返回。
SEM_UNDO(不常用),设置只对当前进程有效,不会保存到系统的信号量集合中。
nops:调用一次semop要操作的信号量的个数。第二个参数来指定他们的首地址,由第三个参数来指定数量
返回值:成功0, 失败-1
如:我每执行以下程序,第一个信号灯就会减一,第二个信号灯就会加1,直到第一个信号减到0阻塞
信号灯集详细使用说明
信号灯集详细使用说明
运行结果:
信号灯集详细使用说明
....................
信号灯集详细使用说明
每次加减的数可以修改,如下,信号灯0每次-5,信号灯1每次
信号灯集详细使用说明
运行结果:
信号灯集详细使用说明
如果我修改成如下之后,就从mybuf这个首地址执行两个这个结构体
信号灯集详细使用说明
运行结果:
信号灯集详细使用说明

信号灯集详细使用说明
运行结果:
信号灯集详细使用说明
注意要执行的结构体数目空间必须是连续的,比如使用结构体数组,因为我们只给函数提供了一个首地址。