这里所介绍的信号量是一种计数信号量集,它是一个计数器,用于多进程对共享数据对象的访问。共享资源通常分为两类:一类是互斥共享资源,即任一时刻只允许一个进程访问该资源;另一类是同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。
当有进程要求使用共享资源时,需要执行以下操作:
当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。
每个信号量集都有一个与其相对应的结构,该结构定义如下:
/* Data structure describing a set of semaphores. */ struct semid_ds { struct ipc_perm sem_perm; /* operation permission struct */ struct sem *sem_base; /* ptr to array of semaphores in set */ unsigned short sem_nsems; /* # of semaphores in set */ time_t sem_otime; /* last-semop() time */ time_t sem_ctime; /* last-change time */ }; /* Data structure describing each of semaphores. */ struct sem { unsigned short semval; /* semaphore value, always >= 0 */ pid_t sempid; /* pid for last successful semop(), SETVAL, SETALL */ unsigned short semncnt; /* # processes awaiting semval > curval */ unsigned short semzcnt; /* # processes awaiting semval == 0 */ };信号量集的结构图如下所示:
信号量的创建函数定义如下:
/* Create a set of semaphores */ /* 函数功能:创建一个新的信号量或打开一个现有的信号量; * 返回值:若成功则返回信号量ID,若出错则返回-1; * 函数原型: */ #include <sys/sem.h> int semget(key_t key, int nsems, int flag); /* * 说明: * key是信号量集的键; * nsems表示信号量的个数; * flag用于表示调用函数的操作类型,也可用于设置信号量集的访问权限; * flag访问权限值:IPC_CREAT,IPC_EXCL以及IPC指定的权限位,若IPC_CREAT | IPC_EXCL,当信号量集返回出错,errno设为EEXIST; */当成功创建了一个新信号量集,则相应的 semid_ds 结构成员会被初始化为以下值,但是必须注意,信号量集中的每个信号量并没有被初始化,必须调用函数 semctl 对其进行初始化:
/* 函数功能:对信号量进行操作; * 返回值:若成功则返回0,若出错则返回-1; * 函数原型: */ #include <sys/sem.h> int semop(int semid, struct sembuf semoparray[], size_t nops); /* * 说明: * semid是信号量的引用ID; * nops是semoparray数组元素的个数; * semoparray是一个指针,指向信号量操作数组; */semoparray 是一个指向数组的指针,数组每个元素表示一个操作,由于此函数是一个原子操作,一旦执行就将执行数组中的所有操作。其中 sembuf 结构定义如下:
struct sembuf { unsigned short sem_num; /* number # in set (0,1,..., nsems-1) */ short sem_op; /* operation (negative, 0, or positive) */ short sem_fig; /* IPC_NOWAIT, SEM_UNDO */ }每个信号量成员操作 sem_op 不同的取值,会执行不同的操作,下面是不同取值对应的操作:
/* 函数功能:对信号量进行控制; * 函数原型: */ #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, .../* union semun arg */); /* * 该函数的返回依赖与参数cmd; * semid是信号量集的ID; * semnum是指定某个特定的信号量; * cmd是希望该函数执行的操作; */其中联合体 semun 参数结构如下:
union semun { int val; /* for SETVAL */ struct semid_ds *buf; /* for IPC_STAT and IPC_SET */ unsigned short *array; /* for GETALL and SETALL */ };
测试程序:
#include "apue.h" #include <sys/sem.h> #include <fcntl.h> #include <sys/ipc.h> //#define PATH_NAME "./Sem" key_t MakeKey(const char *pathname); union semun { int val; struct semid_ds *buf; unsigned short *array; }; //信号量值 int main(int argc, char *argv[]) { int semid, nsems, i; key_t key; struct semid_ds seminfo; unsigned short *ptr; union semun arg; if(argc != 2) err_quit("usage: a.out <pathname>"); key = MakeKey(argv[1]); if((semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0) { arg.val = 2; semctl(semid, 0, SETVAL, arg); } else if(errno == EEXIST) { semid = semget(key, 1, 0666); arg.val = 4;/* 设置信号量值*/ semctl(semid, 0, SETVAL, arg); } else err_quit("semget error"); arg.buf = &seminfo; /* 对信号量集取ipc_perm结构,并保存在array.buf所指的缓冲区中 */ if(semctl(semid, 0, IPC_STAT, arg) < 0) err_quit("semctl error"); nsems = arg.buf->sem_nsems;/* 获取信号量的个数 */ ptr = (unsigned short *)calloc(nsems, sizeof(unsigned short)); arg.array = ptr; /* 获取信号量集中信号量的值,并保存在arg.array所指向的数组中,即保存在ptr所指的地址 */ semctl(semid, 0, GETALL, arg); for(i = 0; i < nsems; i++) printf("semval[%d] = %d\n", i, ptr[i]); exit(0); } key_t MakeKey(const char *pathname) { int fd; if((fd = open(pathname, O_CREAT, 0666)) < 0) err_quit("open error"); close(fd); return ftok(pathname, 0); }
./sem Sem semval[0] = 4
参考资料:
《UNIX高级环境编程》