Linux进程间通信之信号量
信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。在UNIX下有三种分别如下:
在这里只和大家分享下有关System V信号量。
System V通过定义计数信号量集来对信号量的操作,计数信号量集是一个或多个信号量构成一个集合,其中每个都是计数信号量。对于系统中的每个信号量集,内核维护一个如下的信息结构,它定义在<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 */
uishort sem_nsems; /* #of semaphores in set */
time_t sem_otime; /* time of last semop() */
time_t sem_ctime; /* time of creation or last IPC_SET */
};
成员struct sem结构如下:
struct sem{
ushort_t semval; /* semaphore value , nonnegative */
short sempid; /* PID of last successful semop(), SETVAL, SETALL */
ushort_t semncnt; /* awaiting semval > current value */
ushort_t semzcnt; /* awaiting semval = 0 */
};
注意在struct semid_ds结构中的sem_base含有指向某个sem结构数组的指针:当前信号量集中的每个信号对应其中一个数组元素。我们可以把内核中的某个选定信号量图解成指向一个sem结构数组的一个semid_ds结构。图解如下:
有了以上的理论,那么接下来我们来探讨下如何对这样的信号量进行操作,linux操作系统为我们提供了操作system v信号量的API函数,以下就开始讲解这些API函数。
#include <sys/sem.h>
int semget(key_t key, int nsems, int oflag);
该函数成功返回时,其返回值是一个称为信号量标识符的整数,semop和semctl使用它。出错则返回-1。
nsems参数指定集合中的信号量数。如果我们创建一个新的信号量集,而只是访问一个已存在的集合,那就可以把该参数指定为0。一旦创建完一个信号量集,我们就不能改变其中的信号量数。
oflag值是一些权限值的组合,如果是创建一个信号量集,那么得在此oflag的基础上或上O_CREAT或者是O_CREAT|O_EXCL。
此函数不可以对创建的信号量集中的信号量进行初始化,对信号量的初始化是通过另外的一个函数semctl进行的。
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …./* union semun arg*/ );
semid标识其操作待控制的信号量集。
semnum标识该信号量集内的某个成员(0, 1等待,直到nsems -1)。semnum值仅仅用于GETVAL、SETVAL、GETNCNT、GETZCNT和GETPID命令。
第四个参数是可选的,取决于第三个参数cmd。union semnu结构如下:
union semnu{
int val; /* used for SETVAL only */
struct semid_ds *buf; /* used for IPC_SET and IPC_STAT */
ushort *array; /* used for GETALL and SETALL */
};
这个联合体并没有出现在任何头文件中,因而必须由应用程序声明。
第三个参数cmd可以取以下值:
GETVAL 把semval的当前值作为函数返回值返回。
SETVAL 把semval值设置为arg.val。如果操作成功,那么相应信号量在所有进程是的信号
量调整值(semadj)将被置为0。
IPC_RMID 把由semid指定的信号量集从系统中删除掉。
IPC_SET 设置所指定信号量集的semid_ds结构中的某些成员。
IPC_SET R 返回所指定信号量集当前的semid_ds结构。
#include <sys/sem.h>
int semop(int semid, struct sembuf *opsptr, size_t nops);
semid标识其操作的信号量集。
其中opsptr指向一个如下结构的数组:
Struct sembuf{
Short sem_num; /* semaphore number: 0, 1, …., nsems-1 */
Short sem_op; /* semaphore operation: <0, 0, >0 */
Short sem_flag; /* semaphore flags : 0, IPC_NOWAITE, SEM_UNDO */
};
nops参数指出由opsptr指向的sembuf结构数组中元素的数目。该数组中的生个元素给目标信号量集内某个特定的信号量指定一个操作。这个特定的信号量由sem)num指定,0代表第一个元素,1代表第二个元素,依次类推,直到nsems-1,其中nsems是目标信号量集内成员信号量的数目。
semop对信号的操作是由sem_op的值确定的,以下是对sem_op取值的分析:
以下是利用信号量来进行一个PV操作,实现代码如下:
//初始化信号量 int init_sem(int semid, int semval) { SYS_NUM semnu; semnu.sem_val = semval; if((semctl(semid, 0, SETVAL, semnu)) < 0) { perror("init_sem semctl"); return -1; } return 0; } //对信号量进行p操作 int sem_p(int semid) { SYS_SEM sembu; sembu.sem_num = 0; sembu.sem_op = -1; sembu.sem_flg = SEM_UNDO; if((semop(semid, &sembu, 1)) < 0) { perror("sem_p semop"); return -1; } return 0; } //对信号量进行v操作 int sem_v(int semid) { SYS_SEM sembu; sembu.sem_num = 0; sembu.sem_op = 1; sembu.sem_flg = SEM_UNDO; if((semop(semid, &sembu, 1)) < 0) { perror("sem_v semop"); return -1; } return 0; } //删除信号量集 int del_sem(int semid) { if((semctl(semid, 0, IPC_RMID)) < 0) { perror("del_sem semctl"); return -1; } return 0; }
总结:
信号量往往是用来同步的,保护共享内存。