信号量集的操作
三个IPC对象类型中,信号量集的操作函数相对于其他两个类型的操作函数要复杂得多,当然信号量的应用也比其他两个更广泛些。像共享内存的操作一样,信号量也有自己的专属操作函数semctl,函数原型如下:
#include int semctl( int sem_id, int semnu, |
函数中参数sem_id是一个信号量标识符,semnum指定sem_id的信号集中的某一个信号灯,其类似于在信号量集资源数组中的下标,用来对指定资源进行操作。参数cmd定义函数所要进行的操作。其取值以及表达的意义如下表所示。
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{ |
函数成功返回值大于等于0(当semctl的操作为GET操作时返回相应的值,其余返回0),失败返回-1并设置错误变量errno。
下面实例演示了如何使用semctl函数。程序中先使用semget函数创建了一个新的信号量集,然后通过shell命令查看系统IPC的状态,再调用一次semctl函数做删除操作,并查看系统IPC的状态
(1)在vi编辑器中编辑该程序如下:
程序清单14-11 ctl_sem.c 使用semctl删除信号量
#include int main( void ) semid = semget ( IPC_PRIVATE, nsems, flags ); /*创建一个信号量集*/ if ( sem_id < 0 ){ /* 创建信号量失败 */ printf ( "successfully created a semaphore: %d \n", sem_id ); system ( "ipcs -s" ); /*查看系统IPC状态*/ if ( (semctl (semid, 0, IPC_RMID)) < 0 ) { /* 删除指定信号量集*/ exit ( 0 ); |
(2)在shell中编译该程序如下:
$gcc ctl_sem.c-o ctl_sem |
(3)在shell中运行该程序如下:
$./ ctl_sem ------ Semaphore Arrays -------- successfully created a semaphore: 360450 ------ Semaphore Arrays -------- semaphore removed |
在shell中可以调用如下命令来删除已存在的信号量:
$./ ipcrm -s |
说明:上述程序中,使用semget函数创建一个信号量时,有可能系统中已经有了一个跟IPC_PRIVATE键关联的信号量,此时应在shell中先删除该IPC,然后再运行程序。
理解主要有4点:
1,信号量其实是一个计数变量,它只能为正整数和0,为0时表示该信号量对应的可用资源数为0;
2,semget可以创建多个信号量,而对应的semid所以称为信号量集的id;
#define SEM_KEY 0x11223344
#define NUM_SEM 2
//创建了一个包含2个信号量的信号量集,两个信号量的索引号是0和1
semid=semget(SEM_KEY,NUM_SEM,IPC_CREAT|IPC_EXCL|0666);
3,semop可以一次性进行多个操作,多个操作可以针对同一信号量,也可针对不同信号量,取决于struct sembuf的设置;
struct sembuf sem[2];
sem[0].sem_num = 0;/*对索引号为0的信号量进行操作*/
sem[1].sem_num = 0;/*对索引号为1的信号量进行操作*/
sem[0].sem_op = 0;
sem[1].sem_op = 1;
semop(semid,sem,2); /*最后一个参数nsops=2,所以在函数中同时执行sem[0],sem[1]两个操作,如果为1则执行sem[0]操作*/
4,sem_op数量的含义
信号量的 SysV 实现比上述解决方案更通用。首先,信号量的值不需要是 0 或 1;它可以是 0 或任何正数。其次,可以执行一系列信号量操作,与用于 msgrcv 的 type 参数非常类似。这些操作作为一个指令集提供给内核,并且这些指令要么全部运行,要么一个也不会运行。内核要求将这些指令放在一个名为 sembuf 的结构中,该结构具有以下成员(按顺序):
1. sem_num:描述正在操作该集合中的哪一个信号量。
2. sem_op:一个有符号整数,其中包含要执行的指令或测试。
3. sem_flg:熟悉的 IPC_NOWAIT 标志(它指示测试应该立即返回还是阻塞直至通过)和 SEM_UNDO(它在进程提前退出时导致撤销该信号量操作)的组合。
sem_op 是放置许多配置的地方:
· 如果 sem_op 为 0,则测试 sem_num 以确定它是否为 0。如果 sem_num 为 0,则运行下一个测试。如果 sem_num 不为 0,则在未设置 IPC_NOWAIT 时,操作将阻塞直至信号量变为 0,而在设置了 IPC_NOWAIT 时,则跳过其他测试。
· 如果 sem_op 是某个正数,则将信号量的值加上 sem_op 的值。
· 如果 sem_op 是一个负整数,并且信号量的值大于或等于 sem_op 的绝对值,则从信号量的值减去该绝对值。
· 如果 sem_op 是一个负整数,并且信号量的值小于 sem_op 的绝对值,则在 IPC_NOWAIT 为 true 时立即停止测试的执行,而在该值为 false 时则阻塞,直至信号量的值变得大于 sem_op 的绝对值。
#include"unp.h"
#define PERMS (S_IRUSR | S_IWUSR)
union semun {
int val;
struct semid_ds *buf;
unsignedshort*array;
}arg;
int main(void)
{
int semid, i, j;
struct sembuf semwait[1], semsignal[1];
char buf[]="hello china\r\n";
if((semid = semget(IPC_PRIVATE, 1, PERMS))==-1)
err_sys
("sem_get");
arg.val = 1;
if(semctl(semid, 0, SETVAL,arg)==-1)
err_sys
("semctl");
semwait
[0].sem_num = 0;
semwait
[0].sem_op =-1;
semwait
[0].sem_flg = 0;
semsignal
[0].sem_num = 0;
semsignal
[0].sem_op = 1;
semsignal
[0].sem_flg = 0;
for(i = 0; i < 5; i++)
if(fork())/* parent process jump out loop */
break;
if(semop(semid, semwait, 1)==-1)
err_sys
("semop");
for(j = 0; buf[j]!='\0'; j++)
fputc(buf[j],stdout);
if(semop(semid, semsignal, 1)==-1)
err_sys
("semop");
exit(0);
}
void err_sys(constchar*errmsg)
{
perror(errmsg);
exit(1);
}