信号量
信号量:信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对于临界资源访问的同步。临界资源可以
理解为在某一时刻只能由一个进程或线程操作的资源,这里的资源可以是一段代码、一个变量或某种硬件资源。信号量的
值大于或等于0时表示可供并发进程使用的资源实体数,小于0时表示正在等待使用临界资源的进程数。
Linux内核也为每个信号集维护了一个semid_ds数据结构实例,该结构定义在头文件linux/sem.h中,各个字段含义为:
struct semid_ds { struct ipc_perm sem_perm; //对信号进行操作的许可权 __kernel_time_t sem_otime; //对信号进行操作的最后时间 __kernel_time_t em_ctime; //对信号进行修改的最后时间 struct sem *sembase; //指向第一个信号 struct sem_queue sem_pending; //等待处理的挂起操作 struct sem_queue **sem_pending_last; //最后一个正在挂起的操作 struct sem_undo *undo; //撤销的请求 ushort sem_nsems; //数组中的信号数 };
信号量使用key_t值作为它的名字。头文件<sys/types.h>把key_t这个数据类型定义为一个整数,它通常是一个至少32位
的整数。这些整数值通常由ftok函数赋予的。函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为
IPC键。该函数原形为:
#include <sys/ipc.h>
key_t ftok(const char *pathname,int proj_id);
创建或打开信号量前需要使用ftok函数得到key值,下面是ftok函数的包裹函数:
key_t Ftok(const char *pathname,int proj_id) { key_t key= ftok(pathname,proj_id); if(key== -1) { perror("ftok."); exit(1); } return key; }
信号量的创建或打开:
Linux下使用系统函数semget函数创建或打开信号量。该函数定义在头文件<sys/sem.h>中,该函数的原形为:
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
该函数执行成功返回一个信号量的标示符,失败返回-1。函数的第一个参数为ftok()函数得到的键值,第二个参数nsems
指明要创建的信号量包含的信号量个数,如果只是打开信号量,把nsems设置为0即可,第三个参数semflg为操作标志,可
以取如下值:
- IPC_CREAT:调用semget()时,它会将此值与系统中其它信号量的key值进行比较,如果存在相同的key,说明此信号量
已存在,此时返回该信号量的标识符,否则新建一个信号量并返回其标识符。
- IPC_EXCL:该宏必须和IPC_CREAT一起使用,否则没有意义。当semflg取IPC_CREAT|IPC_EXCL时,表示如果发现信号
量已经存在,则返回错误,错误码为EEXIST。
信号量的控制:
使用信号量时,往往需要对信号量进行一些控制操作,比如删除信号量、对内核维护的信号量的数据结构semid_ds进行
设置、获取信号量中信号值等。通过semctl控制函数可以完成这些操作,该函数定义在<sys/sem.h>中,如下所示:
int semctl(int semid,int semnum,int cmd, ...);
函数中,参数semid为信号量的标示符(即通过semget函数的到的值);参数semnum标示一个特定的信号;参数cmd指明控制
操作的类型;最后的“...”说明函数的参数是可选的,它依赖于第三个参数cmd,它通过共用体变量semun选择要操作的参数。
union semun { int val; //仅用于SETVAL操作类型,设置某个信号量的值等于val struct semid_ds *buf; //用于IPC_STAT和IPC_SET操作,存取semid_ds结构 unsigned short *array; //用于SETALL和GETALL操作 struct seminfo *__buf; //为控制IPC_INFO提供的缓存 };
参数cmd通过宏来指示操作类型,通常取以下几个宏:
- GETVAL:把semval的当前值作为函数返回值返回。既然信号量决不会时负数(semval被声明为一个unsigned short整数),
那么成功的返回值总是非负数。
- SETVAL:把semval值设置为val。如果操作成功,那么相应信号量在所有进程中的信号量调整值将被设置为0。
- IPC_RMID:把由semid指定的信号量集从系统中删除掉。
信号量的操作:
信号量的值与相应资源的使用情况有关,当它的值大于0时,表示当前可用资源的数量,当它的值小于0时,其绝对值表
示等待使用该资源的进程个数。信号量的值仅能由PV操作来改变。使用semget打开一个信号量集后,对其中一个或多个信号
量的操作就使用semop函数来执行。该函数原型为:
int semop(int semid,struct sembuf *sops,unsigned nsops);
函数的参数semid为信号量的标示符,参数sops指向进行操作的结构体数组首地址,参数nsops指出将要进行操作的信号量
的个数。semop函数调用成功返回0,否则返回-1。
semop的第二个参数sops指向的结构体数组中,每个sembuf结构体对应一个特定的信号操作。该结构体为:
struct sembuf { unsigned short sem_num; //信号量在信号集中的索引 short sem_op; //操作类型 short sem_flg; //操作标志 };
sem_op的取值和意义:
- sem_op>0:信号量加上sem_op的值,表示进程释放控制的资源。
- sem_op=0:如果没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量值为0;否则进程不会睡眠,直接返回
EAGAIN。
- sem_op<0:信号量加上sem_op的值。若没有设置IPC_NOWAIT,则调用进程阻塞,直到资源可用;否则进程直接返回
EAGAIN。
下面是对信号量进行操作的一个例子:
// myipc.h #pragma once #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/shm.h> #include <sys/msg.h> union semun { int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; key_t Ftok(const char *pathname,int proj_id) { key_t key= ftok(pathname,proj_id); if(key== -1) { perror("ftok."); exit(1); } return key; }
#include "myipc.h" int main() { key_t sem_key= Ftok("mysem",0xff); int sem_id= semget(sem_key,1,IPC_CREAT); if(sem_id== -1) { perror("semget."); exit(1); } else { printf("sem key = 0x%x,sem id = %d\n",sem_key,sem_id); } int ret; union semun init; init.val= 3; semctl(sem_id,0,SETVAL,init); struct sembuf info; info.sem_num= 0; info.sem_op= 2; info.sem_flg= 0; semop(sem_id,&info,1); ret= semctl(sem_id,0,GETVAL); printf("sem value = %d\n",ret); ret= semctl(sem_id,0,IPC_RMID); if(ret== -1) { printf("Remove sem error.\n"); } else printf("Remove sem ok.\n"); return 0; }