信 号 量
信号量的原理是一种数据操作锁的概念,它本身不具备数据交换的功能,而是通过控制其他的通信资源(文件,外部设备等)来实现进程间通信。信号量本身不具备数据传输的功能,其只是一种外部资源的标识。本小节将深入介绍信号量的操作。
一、信号量的概念
信号量本身不具备数据传输的功能,它只是一种外部资源的标识,通过该标识可以判断外部资源是否可用,信号量在此过程中负责数据操作的互斥、同步等功能。
当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值,以判断相应的资源是否可用。当信号量的值大于0时,表明有资源可以请求。等于0时,说明现在无可用资源,所以进程会进入睡眠状态直至有可用资源时。
当进程不再使用一个信号量控制的共享资源时,此信号量的值增1,对信号量的值进行增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建以及初始化时,不能保证操作均为原子。
同其他的IPC对象一样,内核对每一个信号量集都会设置一个shmid_ds结构(详细介绍见14.3.4),同时用一个无名结构来标识一个信号量。简要定义如下:
struct {
unsigned short semval;
pid_t sempid;
unsigned short semncent;
unsigned short semzcnt;
...
...
}
二、信号量的创建
同共享内存一样,系统中同样需要为信号量集定制一系列专有的操作函数(semget,semctl等)。系统命令ipcs可查看当前的系统IPC的状态,在命令后使用-s参数。使用函数semget可以创建或者获得一个信号量集ID,函数原型如下:
#include <sys/shm.h>
int semget( key_t key, int nsems, int flag);
函数中参数key用来变换成一个标识符,每一个IPC对象与一个key相对应。当新建一个共享内存段时,使用参数flag的相应权限位对ipc_perm结构中的mode域赋值,对相应信号量集的shmid_ds初始化的值如表14-6所示。
表14-6 shmid_ds结构初始化值表
ipc_perm结构数据 | 初 值 | ipc_perm结构数据 | 初 值 |
Sem_otime | 0 | Sem_nsems | Nsems |
Sem_ctime | 系统当前值 |
参数nsems是一个大于等于0的值,用于指明该信号量集中可用资源数(在创建一个信号量时)。当打开一个已存在的信号量集时该参数值为0。函数执行成功,则返回信号量集的标识符(一个大于等于0的整数),失败,则返回–1。函数semop用以操作一个信号量集,函数原型如下:
#include <sys/sem.h>
int semop( int semid, struct sembuf semoparray[], size_t nops );
函数中参数semid是一个通过semget函数返回的一个信号量标识符,参数nops标明了参数semoparray所指向数组中的元素个数。参数semoparray是一个struct sembuf结构类型的数组指针,结构sembuf来说明所要执行的操作,其定义如下:
struct sembuf{
unsigned short sem_num;
short sem_op;
short sem_flg;
}
在sembuf结构中,sem_num是相对应的信号量集中的某一个资源,所以其值是一个从0到相应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。sem_op指明所要执行的操作,sem_flg说明函数semop的行为。sem_op的值是一个整数,如表14-7所示,列出了详细sem_op的值及所对应的操作。
表14-7 sem_op值详解
Sem_op | 操 作 |
正数 | 释放相应的资源数,将sem_op的值加到信号量的值上 |
0 | 进程阻塞直到信号量的相应值为0,当信号量已经为0,函数立即返回。如果信号量的值不为0,则依据sem_flg的IPC_NOWAIT位决定函数动作。sem_flg指定IPC_NOWAIT,则semop函数出错返回EAGAIN。sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生。信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;此信号量被删除(只有超级用户或创建用户进程拥有此权限),函数smeop出错返回EIDRM;进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR |
负数 | 请求sem_op的绝对值的资源。如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。当相应的资源数不能满足请求时,这个操作与sem_flg有关。sem_flg指定IPC_NOWAIT,则semop函数出错返回EAGAIN。sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:当相应的资源数可以满足请求,该信号的值减去sem_op的绝对值。成功返回;此信号量被删除(只有超级用户或创建用户进程拥有此权限),函数smeop出错返回EIDRM:进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR |
下面实例演示了关于信号量操作的基本流程。程序中使用semget函数创建一个信号量集,并使用semop函数在这个信号集上执行了一次资源释放操作。并在shell中使用命令查看系统IPC的状态。
(1)在vi编辑器中编辑该程序。
程序清单14-10 create_sem.c 使用semget函数创建一个信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
int sem_id;
int nsems = 1;
int flags = 0666;
struct sembuf buf;
sem_id = semget(IPC_PRIVATE, nsems, flags); /*创建一个新的信号量集*/
if ( sem_id < 0 ){
perror( "semget ") ;
exit (1 );
}
/*输出相应的信号量集标识符*/
printf ( "successfully created a semaphore : %d/n", sem_id );
buf.sem_num = 0; /*定义一个信号量操作*/
buf.sem_op = 1; /*执行释放资源操作*/
buf.sem_flg = IPC_NOWAIT; /*定义semop函数的行为*/
if ( (semop( sem_id, &buf, nsems) ) < 0) { /*执行操作*/
perror ( "semop");
exit (1 );
}
system ( "ipcs -s " ); /*查看系统IPC状态*/
exit ( 0 );
}
(2)在shell中编译该程序如下:
$gcc create_sem.c–o create_sem
(3)在shell中运行该程序如下:
$./ create_sem
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 294911 root 666 1
successfully created a semaphore : 294911
在上面程序中,用semget函数创建了一个信号量集,定义信号量集的资源数为1,接下来使用semop函数进行资源释放操作。在程序的最后使用shell命令ipcs来查看系统IPC的状态。
%注意:命令ipcs参数-s标识查看系统IPC的信号量集状态。
三、信号量集的操作
三个IPC对象类型中,信号量集的操作函数相对于其他两个类型的操作函数要复杂得多,当然信号量的应用也比其他两个更广泛些。像共享内存的操作一样,信号量也有自己的专属操作函数semctl,函数原型如下:
#include <sys/sem.h>
int semctl( int sem_id, int semnu, int cmd [, union semun arg]);
函数中参数sem_id是一个信号量标识符,semnum指定sem_id的信号集中的某一个信号灯,其类似于在信号量集资源数组中的下标,用来对指定资源进行操作。参数cmd定义函数所要进行的操作。其取值以及表达的意义如表14-8所示。
表14-8 cmd值详解
cmd的取值 | 操 作 |
GETVAL | 返回成员semnum的semval值 |
SETVAL | 使用arg.val对该信号量的semnum.sempid赋值(需要参数arg) |
GETPID | 返回成员semnum的sempid值 |
GETNCNT | 返回成员semnum的semncnt值 |
GETZCNT | 返回成员semnum的semzcnt值 |
GETALL | 将该信号量集的值赋值到arg.array(需要参数arg) |
SETALL | 使用arg、array数组中的值对信号量集赋值(需要参数arg) |
IPC_RMID | 删除信号量集。此操作只能由具有超级用户的进程或信号量集拥有者的进程执行,这个操作会影响到正在使用该信号量集的进程 |
IPC_SET | 设置此信号量集的sem_perm.uid、sem_perm.gid以及sem_perm.mode的值。此操作只能由具有超级用户的进程或信号量集拥有者的进程执行 |
SPC_STAT | (需要参数arg) |
函数中参数arg为可选参数,根据参数cmd的相关操作来选择使用,其定义如下:
union semun{
int val;
struct semid_ds *buf ;
unsigned short *array;
};
函数成功返回值大于等于0(当semctl的操作为GET操作时返回相应的值,其余返回0),失败返回–1并设置错误变量errno。
下面实例演示了如何使用semctl函数。程序中先使用semget函数创建了一个新的信号量集,然后通过shell命令查看系统IPC的状态,再调用一次semctl函数做删除操作,并查看系统IPC的状态
(1)在vi编辑器中编辑该程序如下:
程序清单14-11 ctl_sem.c 使用semctl删除信号量
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <stdlib.h>
int main( void )
{
int sem_id ;
int nsems = 1;
int flags = 0666;
semid = semget ( IPC_PRIVATE, nsems, flags ); /*创建一个信号量集*/
if ( sem_id < 0 ){ /* 创建信号两失败 */
perror ( "semget" );
exit ( 1 );
}
printf ( "successfully created a semaphore: %d /n", sem_id );
/*输出创建的信号量的ID */
system ( "ipcs -s" ); /*查看系统IPC状态*/
if ( (semctl (semid, 0, IPC_RMID)) < 0 ) { /* 删除指定信号量集*/
perror ( "semctl" );
exit (1 );
}
else {
printf ( "semaphore removed /n");
system ( "ipcs -s "); /*查看系统IPC状态*/
}
exit ( 0 );
}
(2)在shell中编译该程序如下:
$gcc ctl_sem.c–o ctl_sem
(3)在shell中运行该程序如下:
$./ ctl_sem
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 294911 root 666 1
0x0056a4d5 327681 root 600 1
0x00000000 360450 root 666 1
successfully created a semaphore: 360450
------ Semaphore Arrays --------
key semid owner perms nsems
0x00000000 294911 root 666 1
0x0056a4d5 327681 root 600 1
semaphore removed
在shell中可以调用如下命令来删除已存在的信号量:
$./ ipcrm –s <semaphore semid>
%说明:上述程序中,使用semget函数创建一个信号量时,有可能系统中已经有了一个跟IPC_PRIVATE键关联的信号量,此时应在shell中先删除该IPC,然后再运行程序。