LINUX系统编程__进程间通信__信号量

信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1、特点

信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。

信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

支持信号量组。

2、原型

最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

 #include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
 int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。

在semop函数中,sembuf结构的定义如下:

 struct sembuf 
{
     short sem_num; // 信号量组中对应的序号,0~sem_nums-1
     short sem_op;  // 信号量值在一次操作中的改变量
     short sem_flg; // IPC_NOWAIT, SEM_UNDO
 }

其中 sem_op 是一次操作中的信号量的改变量:

若sem_op > 0,表示进程释放相应的资源数,将 sem_op 的值加到信号量的值上。如果有进程正在休眠等待此信号量,则唤醒它们。

若sem_op < 0,请求 sem_op 的绝对值的资源。

如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。

当相应的资源数不能满足请求时,这个操作与sem_flg有关。

sem_flg 指定IPC_NOWAIT,则semop函数出错返回EAGAIN。

sem_flg 没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:

当相应的资源数可以满足请求,此信号量的semncnt值减1,该信号量的值减去sem_op的绝对值。成功返回;

此信号量被删除,函数smeop出错返回EIDRM;

进程捕捉到信号,并从信号处理函数返回,此情况下将此信号量的semncnt值减1,函数semop出错返回EINTR

若sem_op == 0,进程阻塞直到信号量的相应值为0:

当信号量已经为0,函数立即返回。

如果信号量的值不为0,则依据sem_flg决定函数动作:

sem_flg指定IPC_NOWAIT,则出错返回EAGAIN。

sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:

信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;

此信号量被删除,函数smeop出错返回EIDRM;

进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR

在semctl函数中的命令有多种,这里就说两个常用的:

SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。

IPC_RMID:删除一个信号量集合。如果不删除信号量,它将继续在系统中存在,即使程序已经退出,它可能在你下次运行此程序时引发问题,而且信号量是一种有限的资源。

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

//int semget(key_t,int nsems,int semflg);
//int semctl(int semid,int semnum,int cmd,...);
//int semop(int semid,struct sembuf *sops,unsigned nsops);
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) */
};

void pGetKey(int id)//拿锁操作函数
//P操作:
//若信号量值为1,获取资源并将信号量值-1 
//若信号量值为0,进程挂起等待
{
        struct sembuf set;

        set.sem_num = 0;//序号
        set.sem_op = -1;//操作
        set.sem_flg = SEM_UNDO;

        semop(id,&set,1);
        printf("getkey\n");
}

void vPutBackKey(int id)//放锁操作函数
//V操作:
//    释放资源并将信号量值+1
//    如果有进程正在挂起等待,则唤醒它们
{
        struct sembuf set;

        set.sem_num = 0;//序号
        set.sem_op = 1;//操作
        set.sem_flg = SEM_UNDO;

        semop(id,&set,1);
        printf("put back the key\n");
}


int main(int argc,char const *argv[])   //传参
{
    key_t key;
    int semid;

    key = ftok(".",2);  //获取键值
                       //信号量集合中有一个信号量
    semid = semget(key,1,IPC_CREAT|0666);   //获取/创建信号量

    union semun initsem;

    initsem.val = 0;
                 //操作第0个信号量
    semctl(semid,0,SETVAL,initsem);//初始化信号量
                    //SETVAL设置信号量的值,设置为initsem
    int pid = fork();
        if(pid > 0){
        pGetKey(semid);    //去拿锁,开始没有锁,等待先执行子进程
        printf("this is father\n");
        vPutBackKey(semid);    //锁放回去
        semctl(semid,0,IPC_RMID);//销毁锁

    }
    else if(pid == 0){

        printf("this is child\n");
        vPutBackKey(semid);    //放锁
    }else{

        printf("fork error!\n");
    }

    return 0;
}

共享内存+信号量的使用

函数原型

 #include <sys/shm.h>
 // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
 int shmget(key_t key, size_t size, int flag);
 // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
 void *shmat(int shm_id, const void *addr, int flag);
 // 断开与共享内存的连接:成功返回0,失败返回-1
 int shmdt(void *addr); 
 // 控制共享内存的相关信息:成功返回0,失败返回-1
 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

例子

此例子,使用了【共享内存+信号量+消息队列】的组合来实现服务器进程与客户进程间的通信。

共享内存用来传递数据;

信号量用来同步;

消息队列用来 在客户端修改了共享内存后 通知服务器读取。

server.c

   #include<stdio.h>
   #include<stdlib.h>
   #include<sys/shm.h>  // shared memory
   #include<sys/sem.h>  // semaphore
   #include<sys/msg.h>  // message queue
   #include<string.h>   // memcpy
   
   // 消息队列结构
   struct msg_form {
      long mtype;
      char mtext;
  };
  
  // 联合体,用于semctl初始化
  union semun    //联合体的使用,使用时只能调用一个内容
  {
      int              val; /*for SETVAL*/
      struct semid_ds *buf;
      unsigned short  *array;
  };
  
  // 初始化信号量
  int init_sem(int sem_id, int value)
  {
      union semun tmp;
      tmp.val = value;
                                 //SETVAL设置信号量,设置成tmp,tmp.val等于value,传参有value个信号量。
      if(semctl(sem_id, 0, SETVAL, tmp) == -1)//初始化信号量,失败返回-1;
      {
          perror("Init Semaphore Error");
          return -1;
       }
     return 0;
  }
  
  // P操作:
  //  若信号量值为1,获取资源并将信号量值-1 
  //  若信号量值为0,进程挂起等待
  int sem_p(int sem_id)
  {
      struct sembuf sbuf;
      sbuf.sem_num = 0; /*序号*/
      sbuf.sem_op = -1; /*P操作*/
      sbuf.sem_flg = SEM_UNDO;//默认无信号量时等待
  
      if(semop(sem_id, &sbuf, 1) == -1)
      {
          perror("P operation Error");
          return -1;
      }
      return 0;
  }
  
  // V操作:
  //  释放资源并将信号量值+1
  //  如果有进程正在挂起等待,则唤醒它们
  int sem_v(int sem_id)
  {
      struct sembuf sbuf;
      sbuf.sem_num = 0; /*序号*/
      sbuf.sem_op = 1;  /*V操作*/
      sbuf.sem_flg = SEM_UNDO;
  
      if(semop(sem_id, &sbuf, 1) == -1)
      {
          perror("V operation Error");
          return -1;
      }
      return 0;
  }
  
  // 删除信号量集
  int del_sem(int sem_id)
  {
      union semun tmp;
     if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)//删除信号量集
      {
          perror("Delete Semaphore Error");
          return -1;
      }
      return 0;
  }
  
  // 创建一个信号量集
  int creat_sem(key_t key)
  {
      int sem_id;
      if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
      {
          perror("semget error");
          exit(-1);
      }
      init_sem(sem_id, 1);  /*初值设为1资源未占用*/  //一个信号量
      return sem_id;
  }
  
  
  int main()
  {
      key_t key;
      int shmid, semid, msqid;
      char *shm;
      char data[] = "this is server";
      struct shmid_ds buf1;  /*用于删除共享内存*/
      struct msqid_ds buf2;  /*用于删除消息队列*/
      struct msg_form msg;  /*消息队列用于通知对方更新了共享内存*/
 
     // 获取key值,键值很重要
     if((key = ftok(".", 'z')) < 0)
     {
        perror("ftok error");
         exit(1);
     }
 
     // 创建共享内存
     if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)    
     {
         perror("Create Shared Memory Error");
         exit(1);
     }
 
     // 连接共享内存
     shm = (char*)shmat(shmid, 0, 0);//挂载成功返回内存指针,失败返回-1;
     if((int)shm == -1) //强制类型转换,判断返回值
     {
         perror("Attach Shared Memory Error");
         exit(1);
     }
 
 
     // 创建消息队列
     if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
     {
         perror("msgget error");
         exit(1);
     }
 
     // 创建信号量
     semid = creat_sem(key);
     
     // 读数据
     while(1)
     {
         msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
         if(msg.mtext == 'q')  /*quit - 跳出循环,退出*/ 
             break;
         if(msg.mtext == 'r')  /*read - 读共享内存*/
         {
             sem_p(semid);    //p操作
             printf("%s\n",shm);    //读取共享内存里面的信息
             sem_v(semid);    //v操作
         }
     }
 
     // 断开连接
     shmdt(shm);
      //注意最后需要处理创建的共享内存、消息队列、信号量
     /*删除共享内存、消息队列、信号量*/
     shmctl(shmid, IPC_RMID, &buf1);
     msgctl(msqid, IPC_RMID, &buf2);
     del_sem(semid);
     return 0;
 }

client.c

   #include<stdio.h>
   #include<stdlib.h>
   #include<sys/shm.h>  // shared memory
   #include<sys/sem.h>  // semaphore
   #include<sys/msg.h>  // message queue
   #include<string.h>   // memcpy
   
   // 消息队列结构
   struct msg_form {
     long mtype;
     char mtext;
  };
  
  // 联合体,用于semctl初始化
  union semun
  {
      int              val; /*for SETVAL*/
      struct semid_ds *buf;
      unsigned short  *array;
  };
    
 // P操作:
 //  若信号量值为1,获取资源并将信号量值-1 
 //  若信号量值为0,进程挂起等待
  int sem_p(int sem_id)
  {
      struct sembuf sbuf;
      sbuf.sem_num = 0; /*序号*/
      sbuf.sem_op = -1; /*P操作*/
      sbuf.sem_flg = SEM_UNDO;
  
      if(semop(sem_id, &sbuf, 1) == -1)
      {
          perror("P operation Error");
          return -1;
      }
      return 0;
  }
  
  // V操作:
  //  释放资源并将信号量值+1
  //  如果有进程正在挂起等待,则唤醒它们
  int sem_v(int sem_id)
  {
      struct sembuf sbuf;
      sbuf.sem_num = 0; /*序号*/
      sbuf.sem_op = 1;  /*V操作*/
      sbuf.sem_flg = SEM_UNDO;
  
      if(semop(sem_id, &sbuf, 1) == -1)
      {
          perror("V operation Error");
          return -1;
      }
      return 0;
  }
  
  
  int main()
  {
      key_t key;
      int shmid, semid, msqid;
      char *shm;
      struct msg_form msg;
      int flag = 1; /*while循环条件*/
  
      // 获取key值
      if((key = ftok(".", 'z')) < 0)
      {
          perror("ftok error");
          exit(1);
      }
  
      // 获取共享内存
      if((shmid = shmget(key, 1024, 0)) == -1)
      {
          perror("shmget error");
          exit(1);
      }
  
      // 连接共享内存
      shm = (char*)shmat(shmid, 0, 0);    //挂载
      if((int)shm == -1)
      {
          perror("Attach Shared Memory Error");
          exit(1);//挂载失败就退出
     }
  
      // 创建消息队列
      if ((msqid = msgget(key, 0)) == -1)
      {
          perror("msgget error");
          exit(1);
      }
  
      // 获取信号量
      if((semid = semget(key, 0, 0)) == -1)
      {
          perror("semget error");
         exit(1);
     }
     
     // 写数据
     printf("***************************************\n");
     printf("*                 IPC                 *\n");
     printf("*    Input r to send data to server.  *\n");
     printf("*    Input q to quit.                 *\n");
     printf("***************************************\n");
     
     while(flag)
     {
         char c;
         printf("Please input command: ");
         scanf("%c", &c);
         switch(c)
         {
             case 'r':
                 printf("Data to send: ");
                 sem_p(semid);  /*访问资源*/
                 scanf("%s", shm);//写入共享内存里
                 sem_v(semid);  /*释放资源*/
                 /*清空标准输入缓冲区*/
                 while((c=getchar())!='\n' && c!=EOF);
                 msg.mtype = 888;  //写入携带的消息
                 msg.mtext = 'r';  /*发送消息通知服务器读数据*/
                 msgsnd(msqid, &msg, sizeof(msg.mtext), 0);//将msg所携带的信息发送
                 break;
             case 'q':
                 msg.mtype = 888;
                 msg.mtext = 'q';
                 msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                 flag = 0;//改变flag退出程序
                 break;
             default:
                 printf("Wrong input!\n");
                 /*清空标准输入缓冲区*/
                 while((c=getchar())!='\n' && c!=EOF);
         }
     }
 
     // 断开连接
     shmdt(shm);
 
     return 0;
 }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值