信号量(System V)

一、信号量概念

信号量集和信号量一样,都是为了控制多个进程对共享资源的同步访问而引入的同步对象;System V IPC中规定:不能只单独定义一个信号量,而是只能定义一个信号量的集合,即:信号量集,其中包含一组信号量,同一信号量集中的多个信号量使用同一个唯一的ID来引用,这样做的目的是为了对多个共享资源进行同步控制的需要;

对信号量的一些限制:


二、信号量的数据结构

       Linux中信号量是通过内核提供的一系列数据结构实现的,这些数据结构存在于内核空间,对它们的分析是充分理解信号量及利用信号量实现进程间通信的基础,下面先给出信号量的数据结构(存在于include/linux/sem.h中),其它一些数据结构将在相关的系统调用中介绍。

 (1)系统中表示信号量集合(set)的数据结构(semid_ds)

struct semid_ds {

struct ipc_perm         sem_perm;              /*  IPC权限 */

unsigned short          sem_nsems;           /* 在信号量集的信号量数量 */

struct sem                   *sem_base;             /* 在信号量集中指向第一个信号量的指针 */

struct sem_undo       *undo;                        /* 在这个信号量上的undo 请求 */

struct sem_queue     *sem_pending;         /* 待处理的挂起操作*/

struct sem_queue     **sem_pending_last; /* 最后一个挂起操作 */

long        sem_otime;                                     /* 最后一次对信号量操作(semop)的时间 */

long        sem_ctime;                                     /* 对这个结构最后一次修改的时间 */
   };

         (2)信号量集中的每个信号量的数据结构(sem)

    struct sem

    {
      short   sempid;   /*pid of last operation;  /*最后一个操作的PID(进程ID)*/
      ushort  semval;   /*current value;                 /*信号量的当前值*/
      ushort  semncnt;  /*num procs awaiting increase in semval;  /*等待资源的进程数量*/
      ushort  semzcnt;  /*num procs awaiting semval=0;                  /*等待资源完全空闲的进程数量*/
    };

         (3) 系统中每一信号量集合的队列结构(sem_queue)

 struct sem_queue {   
    struct sem_queue * next;        /* 队列中下一个节点 */
    struct sem_queue **    prev;   /* 队列中前一个节点, *(q->prev) == q */
    struct wait_queue *    sleeper;    /* 正在睡眠的进程 */
    struct sem_undo *  undo;      、/ * undo 结构*/
    int            pid;                               /* 请求进程的进程识别号 */
    int            status; /* 操作的完成状态 */
    struct semid_ds *  sma;    /*有操作的信号量集合数组 */
    struct sembuf *    sops;   /* 挂起操作的数组 */
    int    nsops;  /* 操作的个数 */

};

         注意:信号量和信号量集合的区别,从上面可以看出,信号量用“sem” 结构描述,而信号量集合用“semid_ds"结构描述
他的相应的semid_ds结构被初始化。ipc_perm中各个量被设置为相应
值:
sem_nsems被设置为nsems所示的值;
sem_otime被设置为0;
sem_ctime被设置为当前时间
三、函数原型

int semget ( key_t key, int nsems, int semflg ); 
     功能: 为了创建一个新的信号量集合,或者存取一个已存在的集合
    参数:
key:与信号量集 关联的键。key是对应于信号量集标识符的键值,用于其他进程通过该键值获 得信号量集标识符,一个IPC结构对应于一个IPC标识符。key的值还可以是IPC_PRIVATEIPC_PRIVATE保证创建一个新IPC结构。使用此键值每次都可以创建一个新的IPC结构,并且一个键值对应于很多IPC结构。IPC_PRIVATE这个键值一般为0。
nsems:要创建的信号量集 中的信号量的个数,该参数只在创建信号量集时有效。
flag:
          IPC_CREAT如果内核中没有此队列,则创建它。
IPC_EXCL当和IPC_CREAT一起使用时,如果队列已经存在,则失败。
0666与open创建文件时的参数一样
       返回值: 如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1
注:
  • key仅用来让另外一个进程获得一个已创建的IPC标识符,如果单独使用IPC_CREAT,且发现key没有与任何IPC标识符绑定,则semget()返回一个新创建的共享内存标识符。如果发现key已与一个共享内存标识符绑定,则返回与此key值绑定的队列的标识符。如果IPC_EXCL和IPC_CREAT一起使用,则semget()要么创建一个新的共享内存 ,要么如果共享内存段已经存在则返回一个失败值-1。IPC_EXCL单独使用是没有用处的。key可以自己指定一个唯一的值,还可以由ftok函数生成(见XSI标识符)
  • 当调用semget创建一个信号量集时,他的相应的semid_ds结构被初始化。
           sem_nsems被设置为nsems所示的值;
                     sem_otime被设置为0;
                     sem_ctime被设置为当前时间

int semctl(int semid,int semnum,int cmd, .../*union semun arg*/);
     功能:系统调用semctl用来执行在信号量集上的控制操作
    参数:
            semid:要操作的信号量集的IPC标识符;
            semnum 用于指定某个特定信号量,即操作的信号量在信号量集中的编号,第一个信号的编号是0
            cmd:
                 IPC_STAT     读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中;
                 IPC_SET         设置此信号量集的sem_perm.uid、sem_perm.gid以及sem_perm.mode的值.值来自semun.buf结构中, 此操作只能由具有超级用户的进程或信号量集拥有者的进程执行; 设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数;
                 IPC_RMID     从系统中删除该信号量集,这种删除立即发生的,仍在使用此信号量的其他进程在下次试图对信号量集进行操作时,将会出错返回EIDRM,此操作只能由具有超级用户的进程或信号量集拥有者的进程执行
                GETVAL       返回成员semnum的semval值,即返回信号量集中的一个特定的信号量的值;
                SETVAL      设置成员semnum的semval值,由arg.val指定。即设置信号量集中的一个特定的信号量的值;
                GETPID       返回成员semnum的sempid值,即最后一个执行semop操作的进程的PID;
                GETNCNT      返回成员semnum的semncnt值,即正在等待资源的进程数目;
                         GETZCNT      返回成员semnum的semzcnt值,即正在等待完全空闲的资源的进程数目;
                GETALL       将该信号量集中所有信号量的值赋值到arg.array(需要参数arg)所指向的数组中,用于读取信号量集中的所有信号量的值;
                         SETALL       使用arg.array所指向的数组中的值对信号量集赋值(需要参数arg),即设置信号量集中的所有的信号量的值;
           可变参数(arg): 这是一个联合体类型union semun的副本,而不是一个指向联合类型的指针;联合体中各个量的使用情况与参数cmd的设置有关;
  union semun
   {
     int              val;    /* value for SETVAL */
     struct semid_ds* buf;    /* buffer for IPC_STAT&IPC_SET */
     unsigned short*  array;  /* array for GETALL&SETALL */
     structseminfo*   __buf;  /* buffer for IPC_INFO */
     void*__pad;
   } arg;
   返回值:    失败: -1。成功和cmd命令有关


int semop ( int semid, struct sembuf *sops, unsigned nsops);
         功能:  系统调用semop()对信号量集进行原子操作;PV 操作通过调用semop函数来实现
         参数:
                       semid:信号量集的IPC标识符,用于引用对应的信号量集
                       sops: sembuf结构的数组,用于指定调用semop()函数所做的操作(PV操作);

 struct sembuf

   {
     unsigned short_t        sem_num;        /* semaphore index in array:待操作的信号量集中的某一个信号量的编号,从零开始 */
     short           sem_op;         /* semaphore operatio:对该信号量所执行的PV操作 */
     short           sem_flg;        /* operation flags:操作标志 */
   };
          成员sem_num:要操作的信号量集中的某一个信号量的编号,所以,其取值范围是[0,信号量集中信号量的个数);
                                              即:操作信号量在信号集中的编号,第一个信号的编号是0;
成员sem_op:
               大于0: 释放相应数量的资源,即将sem_op的值累加到信号量的值上;如果操作后的信号量的值小于0,则阻塞,如果大于等于0,函数立即返回,进程继续执行。
   等于0:表示调用进程希望等待到该信号的值等于0;
       如果当前值是0,则此函数立即返回;
       如果当前值非0,则:
        (a)如果sem_flag指定了IPC_NOWAIT位,则semop函数出错返回EAGAIN
        (b)如果sem_flag没有指定IPC_NOWAIT,则semzcnt值加1(因为调用进程即将进入休眠状态),然后调用进程被挂起,直到下述情况发生:
       1、信号量的值为0,将信号量的semzcnt的值减1(因为调用进程已结束等待,则等待资源完全空闲的进程数少一个)
函数semop成功返回;
       2、当从系统中删除此信号量集(只有超级用户或创建用户进程拥有此权限),函数semop出错返回EIDRM;
                 3、进程捕捉到系统信号,并从信号处理函数返回,在此情况下,将此信号量的semncnt值减1(等待资源完全空闲的进程数少一个),函数semop出错返回EINTR;
  小于0:  表示要获取由该信号量控制的资源,sem_op的绝对值所表示要获取的资源数目。
如果该信号量的值大于或等于sem_op的绝对值,则从信号量值中减去sem_op的绝对值。此时信号量的值大于等于0,函数立即返回。
          如果信号量的值小于等于sem_op的绝对值(即资源不够了),则:
          (a)如果sem_flag指定了IPC_NOWAIT位,则semop函数出错返回EAGAIN
          (b)如果sem_flag没有指定IPC_NOWAIT,则将该信号量的semncnt值加1(表示等待空闲资源的进程多了一个),然后挂起进程,直到下述情况发生:
              1、此信号量的值变成大于或等于sem_op的绝对值,该信号量的semncnt值减1,并将该信号量的值减去sem_op的绝对值(表示|sem_op|个数目的共享资源被申请走了),然后成功返回;
   2、 当从系统中删除此信号量集(只有超级用户或创建用户进程拥有此权限),函数semop出错返回EIDRM; 
              3、当进程捕捉到系统信号并从信号处理函数返回时,将此信号量的semzcnt值减1,函数semop出错返回EINTR;
 
注:semop函数具有原子性
成员sem_flg:
     IPC_NOWAIT --> 对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设定错误信息.
               IPC_UNDO   --> 程序结束时(不论正常或不正常),保证信号值会被重设为semop()调用前的值.这样做的目的在于避免                              程序在异常情况下结束时未将锁定的资源解锁,造成该资源永远锁定.

                    nsops:要操作的信号量的个数.即有nsops个sembuf元素参与了PV操作;nsops恒大于等于1
     返回值:成功:0  出错:-1

四、注意事项

  • 调用semop时,如果只是将sembuf结构体中的成员赋值,其实并没有改变什么。只有在执行了semop函数后,相应的sem_op的值才会改变信号量集中相应的信号量的值。
  • 进程结束后,信号量依然存在。而且很有可能在进程结束后,相应的信号量没有被释放,要注意这一点。PV操作要成对进行

五、例程

head.h文件:
#define ERROR(_errmsg_) error(EXIT_FAILURE, errno, "%s:%d->%s\n",\
__FILE__, __LINE__, _errmsg_)

#define MEM_SIZE PAGE_SIZE
#define KEY_VALUE 0X0102333

#define BUFF_SIZE 1024

typedef struct _mesginfo_{
int pidw;
int pidr;
char buff[BUFF_SIZE];
}mesg_t;


write文件:
#include "head.h"

int main(void)
{
mesg_t *mesgbuff;
int shmid;
void *shmaddr = NULL;

int semid;
short semvals[2];
struct sembuf semopt;

/*******    create of open  shared memory   ********/
if (-1 == (shmid = shmget(KEY_VALUE, MEM_SIZE, IPC_CREAT | 0666)))
ERROR("shmget");

/*******      attach memory      *******/
if ( (void *)-1 == (shmaddr = shmat(shmid, NULL, SHM_RND)))
ERROR("shmat");

/*****    create or open semaphore arrays   ********/
if (-1 == (semid = semget(KEY_VALUE, 2, IPC_CREAT | IPC_EXCL | 0666))) {
if (EEXIST == errno) {
if (-1 == (semid = semget(KEY_VALUE, 0, 0666)))
ERROR("semget.open");
}else
ERROR("semget.create");
}else {
/****     initialize semaphore arrays   ***/
semvals[0] = 0; /*  read operation disable  */
semvals[1] = 1; /*  write operation enable  */
if (-1 == semctl(semid, 0, SETALL, semvals))
ERROR("semctl.getval");
}
/*****     initialize memory values    ******/
mesgbuff = shmaddr;
mesgbuff->pidw = getpid();

/*****  main loop   ****/
while (1) {
/*******  P operation for write  ******/
// puts("waiting for P write ...");
semopt.sem_num = 1;
semopt.sem_op = -1;
semopt.sem_flg = 0;
if (-1 == semop(semid, &semopt, 1))
ERROR("semop.P write");

/************  get user data   ***********/
printf(">>");
fflush(stdout);
fgets(mesgbuff->buff, BUFF_SIZE, stdin);

/*******  V operation for read  ******/
// puts("waiting for V read ... ");
semopt.sem_num = 0;
semopt.sem_op = 1;
semopt.sem_flg = 0;
if (-1 == semop(semid, &semopt, 1))
ERROR("semop.V read");
if (!strcmp("#quit\n", mesgbuff->buff))
goto Quit;
}

Quit:
sleep(1); /* waiting for reader exit */
if (-1 == semctl(semid, 0, IPC_RMID))
ERROR("semctl.IPC_RMID");
if (-1 == shmdt(shmaddr))
ERROR("shmdt");
if (-1 == shmctl(shmid, IPC_RMID, NULL))
ERROR("shmctl.IPC_RMID");
exit(EXIT_SUCCESS);
}


read文件:
#include "head.h"

int main(int argc, char **argv)
{
mesg_t *mesgbuff;
int shmid;
void *shmaddr = NULL;

int semid;
short semvals[2];
struct sembuf semopt;

/*******    create of open  shared memory   ********/
if (-1 == (shmid = shmget(KEY_VALUE, MEM_SIZE, IPC_CREAT | 0666)))
ERROR("shmget");

/*******      attach memory      *******/
if ( (void *)-1 == (shmaddr = shmat(shmid, NULL, 0)))
ERROR("shmat");

/*****    create or open semaphore arrays   ********/
if (-1 == (semid = semget(KEY_VALUE, 2, IPC_CREAT | IPC_EXCL | 0666))) {
if (EEXIST == errno) {
if (-1 == (semid = semget(KEY_VALUE, 0, 0666)))
ERROR("semget->open");
}else
ERROR("semget->create");
}else {
/****     initialize semaphore arrays   ***/
semvals[0] = 0; /*  read operation disable  */
semvals[1] = 1; /*  write operation enable  */
if (-1 == semctl(semid, 0, SETALL, semvals))
ERROR("semctl->getval");
}
/*****     initialize memory values    ******/
mesgbuff = shmaddr;
mesgbuff->pidr = getpid();
/*****  main loop   ****/
while (1) {
printf("<<");
fflush(stdout);

/*******  P operation for read  ******/
// puts("waiting for P read ...");
semopt.sem_num = 0;
semopt.sem_op = -1;
semopt.sem_flg = 0;
if (-1 == semop(semid, &semopt, 1))
ERROR("semop->P write");
/**********  show user data   **************/
if (!strcmp("#quit\n", mesgbuff->buff))
goto Quit;
fputs(mesgbuff->buff, stdout);
/*******  V operation for write  ******/
semopt.sem_num = 1;
semopt.sem_op = 1;
semopt.sem_flg = 0;
if (-1 == semop(semid, &semopt, 1))
ERROR("semop->V read");
}
return 0;

Quit:
if (-1 == semctl(semid, 0, IPC_RMID))
ERROR("semctl->IPC_RMID");
if (-1 == shmdt(shmaddr))
ERROR("shmdt");
if (-1 == shmctl(shmid, IPC_RMID, NULL))
ERROR("shmctl->IPC_RMID");
exit(EXIT_SUCCESS);
}












   












  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值