【Linux】进程间通信之信号量篇

在Linux中支持System V 进程通信的手段有三种:消息队列(Message queue)、信号量(Semaphore)、共享内存(Shared memory)。消息队列点击打开链接、共享内存点击打开链接,今天我们主要来看信号量。。。。

在看信号量之前,我们先来看几个概念

进程互斥:由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程竞争使用这些资源,进程的这种关系为进程的互斥。也就是说一个资源每次只能被一个进程访问。

进程同步:在访问资源的时候,以某种特定顺序的方式区访问资源,也就是多个进程需要相互配合共同完成一项资源。

临界资源(互斥资源):系统中某些资源一次只允许一个进程使用。

临界区:在进程中涉及到互斥资源的程序段。

原子性:不可被中断的操作,通俗点就是一件事情只会有两种情况,要么是做了,要么就没做。

信号量是什么

(1)信号量本质上是一个具有原子性的计数器,用来描述临界资源的,不能用全局变量count加加减减替换(因为他没有原子性)

(2)信号量以保护临界资源为目的,但他本身也是个临界资源;他控制多个进程对共享资源的访问,通常描述临界资源当中,临界资源的数量,常常被当做锁来使用,防止一个进程访问另外一个进程正在使用的资源

(3)其中最简单的信号量=1,也叫做二元信号量(互斥锁),可控制单个资源,只能取0和1;信号量>=2为多远信号量

为什么要使用信号量

其实还是要拿临界资源来说,进程间通信时,可能会出现多个进程同时区访问同一临界资源,那这个时候就会出现让谁来访问的问题,为了防止出现这样的矛盾,我们需要一种方法,控制在任意时刻只能有一个执行线程来访问临界区,而信号量就是解决这种矛盾的一种机制,信号量就是用来协调进程对共享资源的访问的。

信号量的特点

(1)同消息队列一样,生命周期随内核,当进程结束的时候,信号来那个还没有被销毁;

(2)信号量并非是单个非负值,而必须定义为含有一个或多个信号量值的集合,当创建信号量的时候,需要制定集合当中信号量的数量

(3)信号量的创建和初始化不能同时进行,因为操作不具有原子性,由于不互斥,可能会导致创建后未初始化就被另一个进程获取。

信号量的P、V操作---->必须保证操作的原子性

(1)信号量:互斥---->P、V在同一进程中

                       同步---->P、V不在同一进程中

(2)信号量值含义:S>0:S表示可用资源的个数

                                 S=0:S表示无可用资源,无等待进程

                                  S<0:|S|表示等待队列中进程个数

为了更好理解信号量以及P、V操作,看下面的伪代码

信号量本质上是一个计数器
struct semaphore
{
   int count;//计数
   pointer_PCB queue;//等待队列
}

P操作

P(s)//申请资源
{
   s.count=s.count--;//申请成功,资源减1
   if(s.count<0)
   {
       //该进程状态置为等待状态
       //将该进程的PCB插入相应的等待队列s.queue末尾
   }
}

V操作

V(s)
{
   s.count=s.cout++;//释放资源
   if(s.count<=0)
   {
       //唤醒相应等待队列s.queue中等待的一个进程
       //改变其状态为就绪态
       //并将其插入就绪队列
   }
}

信号集结构

struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned short sem_nsems; /* No. of semaphores in set */
};

相关函数

//头文件
#include
【Linux】进程间通信之信号量篇_第1张图片
//1.ftok--->获取key值
#include
#include
key_t ftok(const char* pathname,int id)//pathname:路径名  id:项目id,非0整数(只有低8位有效)
//2.semget函数
int semget(key_t key,int nsems,int semflg);
//参数:key-->信号集的名字
//nsems:信号集中信号量的个数
//semflg:由九个权限构成,用法和创建文件时使用的mode模式一样
//返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1
//semctl函数
int semctl(int semid,int semnum,int cmd,...);
//参数:
//semid:mesget返回的信号集标识码
//semnum:信号集中信号量的序号
//cmd:将要采取的动作(有三个取值)
//最后一个参数根据命令不同而不同
//返回值:成功返回0;失败返回-1

【Linux】进程间通信之信号量篇_第2张图片

第四个参数是可选的,如果使用该参数,则他的类型是semun,它是一个联合

union semun
{
   int val;
   struct semid_ds *buf;
   unsigned short *array;
   struct seminfo *_buf;
}
//4semop函数
int semop(int semid,struct sembuf* sops,unsigned nsops);
//参数:
//semid:semget函数返回值(信号量的标识码)
//sops:是指向一个结构数值的指针
//nsops:信号量的个数
//返回值:成功返回0,失败返回-1

【sembuf结构体】

sembuf结构体:
struct sembuf {
short sem_num;//信号量的编号0,1,.....nsems-1
short sem_op;//信号量一次PV操作时加减的数值(一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用)
short sem_flg;//两个取值是IPC_NOWAIT或SEM_UND
};

【信号量实例】

下面所示例子是利用信号量,实现了父子进程的互斥

如果使用了P/V操作会出现如下情况

【Linux】进程间通信之信号量篇_第3张图片

如果未使用P/V操作呢?


【实现代码】

Makefile

【Linux】进程间通信之信号量篇_第4张图片

comm.h

 #ifndef __COMM_H_
 #define __COMM_H_
 #include
 #include
 #include
 #include
 
 #define PATHNAME "."
 #define PROJ_ID 0x4444                                                                                                    
 union semun{
     int val;
     struct semid_ds *buf;
     unsigned short *array;
     struct seminfo *_buf;
 };
 int createsemset(int nums);//创建sem
 int initsem(int semid,int nums,int initval);
 int getsemset(int nums);
 int P(int semid,int who);//申请资源
 int V(int semid,int who);//释放资源
 int destroysemset(int semid);//销毁资源
 
 #endif //__COMM_H_

comm.c

   #include"comm.h"
   static int commsemset(int nums,int flags)
   {
       key_t key=ftok(PATHNAME,PROJ_ID);
       if(key<0){
           printf("ftok error\n");
           return -1;
       }
       int semid=semget(key,nums,flags);
      if(semid<0){
          printf("semget error\n");
          return -2;
      }
      return semid;
  }
  int createsemset(int nums)
  {
      return commsemset(nums,IPC_CREAT|IPC_EXCL|0666);
  }
  int getsemset(int nums)
  {                                                                                                                         
       return commsemset(nums,IPC_CREAT);
  }
  int initsem(int semid,int nums,int initval)
  {
      union semun _un;
      _un.val=initval;
      if(semctl(semid,nums,SETVAL,_un)<0){
          printf("semctl error\n");
          return -1;
      }
      return 0;
  }
  static int commPV(int semid,int who,int op)
  {
      struct sembuf _buf;
      _buf.sem_num=who;
      _buf.sem_op=op;
      _buf.sem_flg=0;
      if(semop(semid,&_buf,1)<0){
         printf("semop error\n");
          return -1;
      }
      return 0;
  }
  int P(int semid ,int who)
  {
      return commPV(semid,who,-1);
  }
  int V(int semid,int who)
  {   
     return commPV(semid,who,1);
  }
  int destroysemset(int semid)
  {
      if(semctl(semid,0,IPC_RMID)<0){
          printf("semctl error");
          return -1;
      }
 }                      

【测试代码】test.c

   #include "comm.h"
   int main()
   {
       int semid=createsemset(1);
       initsem(semid,0,1);
       pid_t id=fork();
       if(id==0){
           int ssemid=getsemset(0);
           while(1){
              //P(ssemid,0);
              printf("A");
              fflush(stdout);
              usleep(101000);
              printf("A ");
              fflush(stdout);
              usleep(100000);
              //V(ssemid,0);
          }
      }else{
          while(1)
          {
              //P(semid,0);
              printf("B");
              fflush(stdout);
              usleep(100100);
              printf("B ");
              fflush(stdout);
              usleep(120000);
              //V(semid,0);                                                                                                 
          }
          wait(NULL);
      }
      destroysemset(semid);
      return 0;
  }

【封装】

1、使用gcc将上述文件生成静态库

【Linux】进程间通信之信号量篇_第5张图片

【注】在第四步时,链接时-l参数后不加空格制定所需链接的库(库名虽然是libmysem.a,但是这里只需要给出-lmysem);

         -L指定库路径,这里的-L.(指的是当前路径);

         其中目标文件生成后,静态库删除,程序照样可以运行

【Linux】进程间通信之信号量篇_第6张图片

2、使用gcc生成动态库

【Linux】进程间通信之信号量篇_第7张图片

【注】

使用ldd test命令查看test使用动态库,如果libmysemshared无法找到,直接执行test就会出现错误。

解决方法:首先使用export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH将当前目录加入LD_LIBRARY_PATH变量中。再次运行ldd test

另一种编译test.c并链接libmymysemshared.so的方式如下(该方式通过./libmymysemshared.so直接指定使用当前目录下的libmymysemshared.so文件)

----->命令:gcc -o test test.c ./libmymysemshared.so

至此,我们已经了解了在Linux中支持System V 进程通信的三种手段。






你可能感兴趣的:(linux)