信号量
信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。它们常常被用做一个锁机制,在某个进程正在对特定资源进行操作时,信号量可以防止另一个进程去访问它。生产者和消费者的模型是信号量的典型使用。
1. 信号量数据结构
信号量数据结构是信号量程序设计中经常使用的数据结构,由于在之后的函数中经常用到,这里将结构的原型列出来,便于读者查找。
union semun /*信号量操作的联合结构*/
{
int val; /*整型变量*/
struct semid_ds *buf; /*semid_ds结构指针*/
unsigned short *array; /*数组类型*/
struct seminfo *_buf; /*信号量内部结构*/
}
2. 新建信号量函数semget()
semget()函数用于创建一个新的信号量集合,或者访问现有的集合。
其原型如下
参数key是ftok生成的键值;
参数nsems可以指定在新的集合中应该创建的信号量的数目;
参数semflg是打开信号量的方式。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
semflg是打开信号量的方式:
□ IPC_CREAT:如果内核中不存在这样的信号量集合,则把它创建出来。
□ IPC_EXCL:当与IPC_CREAT—起使用时,如果信号量集合早已存在,则操作将失败。
如果单独使用IPC_CREAT,semget()或者返回新创建的信号量集合的信号量集合标识符;或者返回早已存在的具有同一个关键字值的集合的标识符。
如果同时使用IPC_EXCL和IPC_CREAT,那么将有两种可能的结果:如果集合不存在,则创建一个新的集合;如果集合早已存在,则调用失败,并返回-1。
IPC_EXCL 本身是没有什么用处的,但当与IPC_CREAT组合使用时,它可以用于防止为了访问而打开现有的信号量集合。
利用semget()函数包装建立信号量的代码如下:
CreateSem()函数按照用户的键值生成一个信号量,把信号量的初始值设为用户输入的value。
3. 信号量操作函数semop()
信号量的P、V操作是通过向己经建立好的信号量(使用semget()函数),发送命令来完成的。向信号量发送命令的函数是semop(),这个函数的原型如下:
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
semop()函数中第2个参数(sops)是一个指针,指向将要在信号量集合上执行操作的一个数组,而第3个参数(nsops)则是该数组中操作的个数。sops参数指向的是类型为sembuf结构的一个数组。sembuf结构是在linux/sem.h中定义的,如下所示:
struct sembuf
{
ushort sem_num; //信号量的编号
short sem_op; //信号量的操作
short sem_flg; //信号量的操作标志
}
□ sem_num:用户要处理的信号量的编号。
□ sem_op:将要执行的操作(正、负、或者零)。
□ sem_flg:信号量操作的标志。如果sem_op为负,则从信号量中减掉一个值。如果 sem_op为正,则从信号量中加上值。如果sem_op为0,则将进程设置为睡眠状态, 直到信号量的值为0为止。
例如“struct sembuf sem={0, +1,NOWAIT};”表示对信号量0,进行加1的操作。 用函数semop()可以构建基本的P、V操作,代码如下所示。Sem_P构建{0, +1,NOWAIT}的sembuf结构来进行增加1个信号量值的操作;Sem_V构建{0, -1, NOWAIT}的sembuf结构来进行减少1个信号量的操作,所对应的信号量由函数传入(semid)。
4. 控制信号量参数semctl()函数
与文件操作的ioctl()函数类似,信号量的其他操作是通过函数semctl()来完成的。函数semctl()的原型如下:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl (int semid, int semnum, int cmd, ......};
函数semctI()用于在信号量集合上执行控制操作。这个调用类似于函数msgctl(),msgctl()函数是用于消息队列上的操作。
semctI()函数的第1个参数是关键字的值(在我们的例子中它是调用semget()函数所返回的值)。
第2个参数(semun)是将要执行操作的信号量的编号,它是信号量集合的一个索引值,对于集合中的第1个信号量(有可能只有这一个信号量)来说,它的索引值将是一个为0的值。
cmd参数代表将要在集合上执行的命令。其取值如下所述:
□ IPC_STAT:获取某个集合的semid_ds结构,并把它存储在semun联合体的buf参数所指定的地址中。
□ IPC_SET:设置某个集合的semid_ds结构的ipc_perm成员的值。该命令所取的值是从semun联合体的buf参数中取到的。
□ IPC_RMID:从内核删除该集合。
□ GETALL:用于获取集合中所有信号量的值。整数值存放在无符号短整数的一个数组中,该数组由联合体的array成员所指定。
□ GETNCNT:返回当前正在等待资源的进程的数目。
□ GETPID:返回最后一次执行semop调用的进程的PID。
□ GETVAL:返回集合中某个信号量的值。
□ GETZCNT:返回正在等待资源利用率达到百分之百的进程的数目。
□ SETALL:把集合中所有信号量的值,设置为联合体的array成员所包含的对应值。
□ SETVAL:把集合中单个信号量的值设置为联合体的val成员的值。
参数arg代表类型semun的一个实例。这个特殊的联合体是在Linux/Sem.h中定义的, 如下所示:
□ val:当执行SETVAL命令时将用到这个成员,它用于指定要把信号量设置成什么值。
□ buf:在命令IPC_STAT/IPC_SET中使用。它代表内核中所使用的内部信号量数据结构的一个复制。
□ array:用在GETALL/SETALL命令中的一个指针。它应当指向整数值的一个数组。在设置或获取集合中所有信号量的值的过程中,将会用到该数组。
□ 剩下的参数_buf和_pad将在内核中的信号量代码的内部使用,对于应用程序开发人员来说,它们用处很少,或者说没有用处。这两个参数是Linux操作系统所特有的,在其他的UNIX实现中没有。
利用semctl()函数设置和获得信号量的值构建通用的函数:
SetvalueSem()函数设置信号量的值,它是通过SETVAL命令实现的,所设置的值通过联合变量sem的val域实现。GetvalueSem()函数用于获得信号量的值,semctl()函数的命令GETVAL会使其返回给定信号量的当前值。当然,销毁信号量同样可以使用semctl()函数实现。
命令IPC_RMID将给定的信号量销毁。
5. 一个信号量操作的例子
在之前的信号量函数的基础上,进行了单进程的信号量程序模拟。下面的代码先建立一个信号量,然后对这个信号量进行P、V操作,并将信号量的值打印出来,最后销毁信号量。