这是我开这个栏目的第一篇文章,顺序也不是按照《Unix网络编程》(以下简称网编)的章节顺序往下写的,可能文章会提及一些前面章节的概念,遇到的话,我会做一些引导,读者也可以自己找找相关的内容或书籍查看。
信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。我们讨论的信号量一般都包括二值信号量和计数信号量,而提及System V信号量时指的是计数信号量集,但是提及Poix 信号量指的就是单个的计数信号量。
对于系统中每一个信号量集,内核维护了一个如下的信息结构,定义在 sys/sem.h 头文件中,
struct semid_ds {
struct ipc_perm sem_perm; /* operation permission struct */
struct sem *sem_base; /* ptr to array of semaphores in set */
ushort sem_nsems; /* number of sems in set */
time_t sem_otime; /* last operation time (semop)*/
time_t sem_ctime; /* time of creation or last IPC_SET */
};
其中,ipc_perm含有当前这个信号量的访问权限(参考网编3.3节);
sem结构是内核用于维护某个给定信号量的一组值的数据结构,需要注意的是sem_base是指向sem结构数组的指针,当前信号量集中的每一个信号量对应其中的一个数组元素,结构如下:
struct sem {
unsigned short semval; /* semaphore value */
pid_t sempid; /* pid of last operation */
unsigned short semncnt; /* # awaiting semval > cval */
unsigned short semzcnt; /* # awaiting semval == 0 */
};
我们假定信号量集中有两个信号量,该信号量集可以如下图表示:
注:更详细的信息大家可以参考《Unix网络编程》
System V 信号量涉及到的函数有:semget()、semctl()和semop(),下面我会一一介绍这几个函数的使用。
#include
int semget(key_t key, int nsems, int semflg);
//返回:若成功则返回一个称为信号量标识符的整数,semop和semctl函数
//都将使用它;失败返回-1
功能:创建一个信号量集或者打开一个已存在的信号量集;
参数:
key 是一个key_t类型的整数,我们通常使用ftok函数将一个已存在的路径和一个整数标识符转换成一个key_t值,称为IPC键(网编P27);
nsems:信号量集中信号量的个数,如果是打开一个信号量集,nsems可以设置成0,信号量集创建完成之后,我们不能改变其中的信号量个数;
semflg:设置改信号量集的访问权限(网编3.4、3.5节),该参数的读写权限位存入sem_perm.mode;
struct ipc_perm
{
uid_t uid; /* [XSI] Owner's user ID */
gid_t gid; /* [XSI] Owner's group ID */
uid_t cuid; /* [XSI] Creator's user ID */
gid_t cgid; /* [XSI] Creator's group ID */
mode_t mode; /* [XSI] Read/write permission */
unsigned short _seq; /* Reserved for internal use */
key_t _key; /* Reserved for internal use */
};
我们来创建一个信号量:
key = ftok(".",'s');
/**
* 创建一个信号量
*/
int sem_create(key_t key){
int semid;
semid = semget(key,1,IPC_CREAT | 0666); //1 信号量集中只有一个信号量,semflg还可以或上 IPC_EXCL 表示创建成功之后再次创建会失败
if (semid == -1){
ERR_EXIT("semget");
}
return semid;
}
先看下有没有信号量集:
运行程序:
如果加上 IPC_EXCL,再次编译运行程序:
我们看到,程序直接报错了。
我们再写一个打开信号量集的函数:
/**
* 打开一个信号量
*/
int sem_open(key_t key){
int sem_id;
sem_id = semget(key,0,0);
if(-1 == sem_id){
ERR_EXIT("semget");
}
return sem_id;
}
当实际创建一个新的信号量集时,相应的semid_ds结构的以下成员将被初始化:
1、sem_perm结构中的uid和cuid被设置成调用进程的有效用户id,gid和guid被设置成调用进程的有效组id;
2、semflg参数的读写权限位存入sem_perm.mode;
3、sem_otime被置为0,sem_ctime被置为当前时间;
4、sem_nsems被置为nests参数的值;
需要注意的是,与该集合中每个信号量关联的sem结构并不初始化,需要调用semctl函数进行初始化;
#include
int semctl(int semid, int semnum, int cmd, .../* union semun arg */);
//成功返回非负值,失败返回-1
功能:对一个信号量执行各种控制操作;
参数:
semid:待操作的信号量集,即由semget返回的信号量集标识符;
semnum: 标识信号量集中的某一个成员(0、1…知道nsems-1);
cmd:将对信号量执行的操作(如下);
第四个参数是可选的,取决于cmd;
union semun {
int val; /* value for SETVAL */
struct semid_ds *buf; /* buffer for IPC_STAT & IPC_SET */
unsigned short *array; /* array for GETALL & SETALL */
};
GETVAL:把semval的当前值作为函数的返回值返回;
SETVAL:把semval的值设置成arg.val;
GETALL:返回信号量集中每一个成员的semval的值,这些值通过arg.array指针返回;
SETALL:设置信号量集中每一个信号量的semval的值,这些值通过arg.array指针指定;
IPC_RMID:把由semid指定的信号量集从系统中删除;
IPC_SET:设置指定信号量集中的semid_ds结构中的以下三位成员:sem_perm.uid、sem_perm.gid和sem_perm.mode,这些值来自由arg.buf参数指向的结构中的相应成员;
IPC_STAT:通过arg.buf参数返回指定信号量集中的semid_ds结构;
现在,我们对信号量集能做的操作更多了,例如:设置一个信号量的值、获取一个信号量的值或者删除一个信号量集。
// 设置一个信号量的值
int sem_setval(int semid,int val){
union semun su;
su.val = val;
int ret;
ret = semctl(semid,0,SETVAL,su);
if(-1 == ret){
ERR_EXIT("sem_setval");
}
return 0;
}
// 获取一个信号量的值
int sem_getval(int semid){
int ret;
ret = semctl(semid,0,GETVAL,0);
if (-1 == ret){
ERR_EXIT("sem_getval");
}
printf("current val is:%d\n",ret);
return 0;
}
// 删除一个信号量集
int sem_d(int semid){
int ret;
ret = semctl(semid,0,IPC_RMID,0);
if(-1 == ret){
ERR_EXIT("sem_d");
}
return 0;
}