信号量/灯(system V IPC)

 

信  号  量

信号量的原理是一种数据操作锁的概念,它本身不具备数据交换的功能,而是通过控制其他的通信资源(文件,外部设备等)来实现进程间通信。信号量本身不具备数据传输的功能,其只是一种外部资源的标识。本小节将深入介绍信号量的操作。

一、信号量的概念

信号量本身不具备数据传输的功能,它只是一种外部资源的标识,通过该标识可以判断外部资源是否可用,信号量在此过程中负责数据操作的互斥、同步等功能。

当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值,以判断相应的资源是否可用。当信号量的值大于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,然后再运行程序。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值