linux 进程间通信-信号量(semagpore)

         linux 信号量是一种IPC(Inter-Process Communication)进程间通信,它是System V semagpore ,是一种计数器;代笔了进程对资源的占用和释放;它的实现机制稍微复杂,主要体现在以下几点:

(1)信号量的数据结构不是单个非负值;而是一个或多个信号量值的集合。也就是说,操作信号量时,你操作的首先是一个集合,集合的实现方式是数组,你要先通过集合才能操作里面的信号量值。集合中信号量值的个数可以在新建时确定。

(2)信号量和创建和赋值是分开的,也就是说不能原子性的创建信号量并对其赋值。信号量的创建函数是semget 函数,而赋值是通过semctl函数实现的。

  (3)进程终止时并不会释放分配给它的信号量。

1 内核为每一个信号量值集合建立如下数据结构(包含于文件<sys/sem.h>):

struct semid_ds {
               struct ipc_perm sem_perm;  /* Ownership and permissions */
               time_t          sem_otime; /* Last semop time */
               time_t          sem_ctime; /* Creation time/time of last
                                             modification via semctl() */
               unsigned long   sem_nsems; /* No. of semaphores in set */
           };

需要注意的是sem_nsems 是代表集合中信号量值的个数,每个信号量值的下标从0开始,到sem_nsems-1结束。ipc_perm数据结构如下:

 struct ipc_perm {
               key_t          __key; /* Key supplied to semget(2) */
               uid_t          uid;   /* Effective UID of owner */
               gid_t          gid;   /* Effective GID of owner */
               uid_t          cuid;  /* Effective UID of creator */
               gid_t          cgid;  /* Effective GID of creator */
               unsigned short mode;  /* Permissions */
               unsigned short __seq; /* Sequence number */
           };

此结构是描述权限和所有者的数据结构。

信号量值的数据结构如下,此结构没有名字

struct {

           unsigned short  semval;   /* semaphore value */
           unsigned short  semzcnt;  /* # waiting for zero */
           unsigned short  semncnt;  /* # waiting for increase */
           pid_t           sempid;   /* PID of process that last

};

需要注意的是semval 才是信号量的值,代表着资源的个数。我们最终需要的还是semval这个参数。

2 主要函数如下:

(1)

       key_t ftok(const char *pathname, int proj_id);

此函数返回一个System V IPC的键(key_t,长整型),用于下面几个函数。此函数要求pathname 的文件已存在且可以访问,此函数产生键时使用proj_id的低8位。使用IPC 结构需使用一个键。通常情况下,不同的文件名产生不同的键值。函数执行成功后返回key_t,出错时则返回-1,并赋值errno。

(2)

       int semget(key_t key, int nsems, int semflg);

此函数获得信号量值集合的标识符,它既可以用来创建信号量值的集合,也可以获得一个已存在的信号量值的集合。参数key是用第一个函数产生的键,nsems 是代表集合中信号量值的个数;semflg参数代表着信号量值集合产生的权限与方式,可用的几个值有IPC_CREAT,IPC_EXCL,作用类似open 函数的IPC_CREAT,IPC_EXCL。函数执行成功后返回一个非负的描述符,出错时则返回-1,并赋值errno。

首先在工作目录 touch mykey mykey2

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void){
int semid1,semid2;
int nsems=1;
key_t key1,key2;
key1=ftok("mykey",97);
if(key1==-1)
{
perror("ftok");
exit(EXIT_FAILURE);
}
semid1=semget(key1,nsems,0600|IPC_CREAT);
if(semid1==-1)
{
perror("semget");
exit(EXIT_FAILURE);
}
printf("ID1=%d\n",semid1);

key2=ftok("mykey2",65);
if(key2==-1)
{
perror("ftok");

exit(EXIT_FAILURE);
}
nsems++;
semid2=semget(key2,nsems,0600|IPC_CREAT);
if(semid2==-1)
{
perror("semget");
exit(EXIT_FAILURE);
}
printf("ID2=%d\n",semid2);

exit(EXIT_SUCCESS);
}

输出结果如下:

其中semid 由系统决定,从结果中可以看出,建立了2个信号量集合,第一个集合有1个信号量值,第二个集合有2个信号量值。

(3)

  int semctl(int semid, int semnum, int cmd, ...);

信号量值操作函数,可以设置信号量的初值,也可以获得现有值。第一个参数代表的是信号量值集合描述符,第二个参数semnum是此描述符中信号量值的下标,具体来说,如果semid有一个信号量值,则semnum只能取0值,代表下标为0,如果semid有2个信号量值,则semnum可取0和1;第三个参数cmd有多种,分别是:IPC_STAT,IPC_SET,IPC_RMID,IPC_INFO(linux-specific),SEM_INFO(linux-specific),SEM_STAT(linux-specific),SEM_STAT_ANY(Linux-specific,since linux 4.17),GET_ALL,GET_NCNT,GET_PID,GET_VAL,GET_ZCNT,SET_ALL,SET_VAL。有第四个可选的参数,union semun arg *(结构体如下所示);需要配合第三个参数使用。此函数执行成功时依据cmd的不同有不同的返回值,出错时返回-1,并赋值errno。

    union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };
已刚才创建的第一个信号值集合为例,

int main(void){
union semun se;
se.val=3;
int semid=1;
int val;
if((val=semctl(semid,0,GETVAL))==-1)
{
perror("semctl");
exit(EXIT_FAILURE);
}
else
printf("the initial val is %d\n",val);

if((val=semctl(semid,0,SETVAL,se))==-1)
{
perror("semctl");
exit(EXIT_FAILURE);
}
if((val=semctl(semid,0,GETVAL))==-1)
{
perror("semctl");
exit(EXIT_FAILURE);
}

else
printf("after set, val is %d\n",val);

exit(EXIT_SUCCESS);
}
结果如下:

从结果来看,未赋值前,初始值为0,赋值后是3。

(4)

  int semop(int semid, struct sembuf *sops, size_t nsops);

信号量操作函数,主要增减信号量的值,并由进程作进一步处理,semid是信号量值集合的描述符,sops 是待操作的数据,nsops是指针sops需要操作的数目。sembuf的结构如下:

struct sembuf{

                       unsigned short sem_num ;       /*semaphore number ; 0,1,2...semnum-1 */

                       short   sem_op;                         /*semaphore operation ;0,negative,positive*/

                       short   sem_flag;                       /* operation flags ;IPC_NOWAIT,IPC_UNDO*/

};

其中sem_num是信号量集合中的下标,取值从0开始,到semnum-1结束。当sem_num <0或sem_num >=semnum时,会产生File too large 的错误。此函数成功时返回0,失败时返回-1,并赋值errno。以第二个信号量集合为例

int main(void){
union semun se;
se.val=3;
int semid=2;
int val;
struct sembuf sops[2];
sops[0].sem_num=0;
sops[0].sem_op=1;
sops[0].sem_flg=0;

sops[1].sem_num=1;
sops[1].sem_op=-1;
sops[1].sem_flg=0;

for(int i=0;i<2;i++){

if((val=semctl(semid,i,SETVAL,se))==-1)
{
perror("semctl");
exit(EXIT_FAILURE);
}
}

if((val=semop(semid,sops,2))==-1)
{
perror("semop");
exit(EXIT_FAILURE);
}

for(int i=0;i<2;i++){
if((val=semctl(semid,i,GETVAL))==-1)
{
perror("semctl");
exit(EXIT_FAILURE);
}
else
printf("after semop,%dth val  is %d\n",i,val);
}

//删除2个信号量值集合

for(int i=2;i!=0;i--){
if((val=semctl(i,i,IPC_RMID))==-1){
{
perror("semctl");
exit(EXIT_FAILURE);
}
}

exit(EXIT_SUCCESS);
}


输出结果如下:

从输出结果来看,0th 的信号量值的semval加了1,1th的信号量值的semval减了1;关于sem_op的取值有三种情况。

(1)sem_op 取值为正,则将当前信号量值的semval 加上sem_op的值,代表着进程释放了sem_op个资源,此操作通常会成功,而且它不需要调用进程等待。如上述例子的0th 的信号量值。

(2)sem_op取值为负,则说明调用进程需要获得|sem_op|(sem_op的绝对值)个资源,如果semval > = | sem_op|,操作会立即执行,执行结束后smeval=semval- |sem_op|;如果semval < |sem_op|,此时若sem_flag 指定了IPC_NOWAIT,则操作出错并立即返回errno,同时sops的数据均不被执行;若sem_flag 未指定IPC_NOWAIT,进程将信号量值数据结构中semncnt加1,代表等待当前semval增加的进程多了一个,然后调用进程进入休眠,直至下列条件发生,进程被唤醒:

           (a)semval >= | sem_op|,有进程释放了资源,函数成功执行,同时semncnt 减1。如果sem_flag指定了SEM_UNDO,则调用进程结束时semval 将恢复原来的值(增加之后的值)。

              (b)信号量值被删除,函数出错返回,并赋值errno 为EIDRM

              (c)调用进程捕捉了一个信号,semncnt减1,函数出错,并赋值errno 为EINTR。

(3)sem_op取值为0,这是一个等待0的操作,如果当前semval为0,函数立即返回,如果信号值不是0,此时若sem_flag指定了IPC_NOWAIT,则函数出错,并赋值errno 为EAGAIN;若sem_flag未指定IPC_NOWAIT,semncnt 加1,则调用进程进入休眠,直至下列条件发生,进程被唤醒:

              (a)信号量值变为0,semncnt减1,同时进程唤醒,函数成功返回

              (b)信号量值被删除,函数出错返回,并赋值errno 为EIDRM

                 (c)调用进程捕捉了一个信号,semncnt减1,函数出错,并赋值errno 为EINTR。

说明下,SEM_UNDO 的实现机制是系统为每个进程的每个信号量值维持一个叫做“信号量值调整值”的整形变量semadj,如果是直接操作,如SETVAL,semadj被清除为0,如果指定SEM_UNDO,函数更改数据前,需要调整semadj的值,调整原则是semadj=-sem_op,即semadj 是当前sem_op的负值,调用进程结束时,调整后的semval+semadj为最后的值。

参考资料:

(1)https://man7.org/linux/man-pages/man2/semget.2.html

(2)https://man7.org/linux/man-pages/man2/semop.2.html

(3)https://man7.org/linux/man-pages/man2/semctl.2.html

(4)https://man7.org/linux/man-pages/man3/ftok.3.html

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值