linux-进程通信(5)信号量(实现读者写者问题)

信号量

信号量(semaphore)与已经介绍过的 IPC 结构不同(前面的管道,消息队列,共享内存,信号都可以携带数据),它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
原理
临界资源(各进程采取互斥的方式,实现共享的资源称作临界资源。)
多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机、磁带机等。

可以将信号量比喻成一个盒子,初始化时在盒子里放入N把钥匙,钥匙先到先得,当N把钥匙都被拿走完后,再来拿钥匙的人就需要等待了,只有等到有人将钥匙归还了,等待的人才能拿到钥匙;
临界资源就像一间房间,用锁锁起来,只有持有钥匙的人才能进去访问,当盒子没有钥匙的时候,外面的人需要等里面的人出来之后归还钥匙,才可以进去访问。

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

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

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

(4)支持信号量组。

注意:
PV操作是一种实现进程互斥与同步的有效方法。PV操作与信号量的处理相关,P(拿钥匙)表示通过的意思,V(放回钥匙)表示释放的意思。
原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch(切换到另一个线程)。

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

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

 // 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int nsems, int semflg);

// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid,  struct sembuf *sops, unsigned nsops);  

// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

semget函数

创建或获取一个信号量组:
函数原型

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

参数
key:所创建或打开信号量集的键值。
nsems:信号量的个数,通常为1;如果是引用一个现有的集合,则将nsems指定为 0,该参数只在创建信号量集时有效。
(也可以设置多个,比如设置成2,理解成一个管理a事件,另一个管理b事件)
semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过 | 表示。
返回值
若成功返回信号量集ID,失败返回-1

semop函数

对信号量组进行操作,改变信号量的值:
函数原型

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

参数
semid:semget()返回的信号量标识符。
*sops:结构的定义如下:

struct sembuf{
    short sem_num; // 除非使用一组信号量,否则它为0
    short sem_op;  // 信号量在一次操作中需要改变的数据,
    //通常是两个数,一个是-1,即P(拿钥匙)操作,一个是+1,即V(放回)操作。
    short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
                   // 并在进程没有释放该信号量而终止时,操作系统释放信号量
};

nsops:信号操作结构的数量,恒大于或等于1。
返回值
成功返回0,失败返回-1

semctl函数

控制信号量的相关信息
函数原型

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

参数
semid:semget()返回的信号量标识符。
semnum:是操作信号在信号集中的编号,第一个信号的编号是0。
cmd:cmd通常是下面的两个值:
SETVAL:
用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。

IPC_RMID:
用于删除一个已经无需继续使用的信号量标识符。
cmd中其他可以使用的命令如下:

IPC_STAT//读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
IPC_SET//设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
IPC_RMID//将信号量集从内存中删除。
GETALL//用于读取信号量集中的所有信号量的值。
GETNCNT//返回正在等待资源的进程数目。
GETPID//返回最后一个执行semop操作的进程的PID。
GETVAL//返回信号量集中的一个单个的信号量的值。
GETZCNT//返回正在等待完全空闲的资源的进程数目。
SETALL//设置信号量集中的所有的信号量的值。
SETVAL//设置信号量集中的一个单独的信号量的值。

如果有第四个参数(需要用户自行定义),它通常是一个union semun结构,定义如下:

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) */
};

作用是在信号量第一次使用前对它进行设置。

对多个信号量初始化
semid =semget(key,3,IPC_CREAT|0666)
union semun arg;
unsigned short vals[3]={0};//假设初始化信号量集的值都为0
arg.array = vlas
semctl(sem_id, 0, SETALL, arg) //使用SETALL

参考生产者和消费者
示例1(父子进程)

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

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) */
};
//p操作
//将信号量值-1 
void pGet(int id)
{
        struct sembuf set;
        set.sem_num=0;
        set.sem_op=-1;
        set.sem_flg =SEM_UNDO;
        semop(id,&set,1);
        printf("get\n");
}
//v操作
//将信号量值+1 
void vPut(int id)
{
        struct sembuf set;
        set.sem_num=0;
        set.sem_op=1;
        set.sem_flg =SEM_UNDO;
        semop(id,&set,1);
        printf("put\n");
}
//父进程获取信号量将被阻塞,直到子进程将信号量+1
int main(int argc,char **argv)
{
        key_t key;
        int semid;
        key = ftok(".",8);// 获取key值
        semid =semget(key,1,IPC_CREAT|0666);// 创建信号量集,其中只有一个信号量
        union semun initsem;
        initsem.val = 0;//设置SETVAL的值(一开始没有钥匙)
        semctl(semid,0,SETVAL,initsem);//SETVAL:用来把信号量初始化为一个已知的值。
        int pid = fork();//建立进程
        if(pid>0){
                pGet(semid);//父进程将信号量-1,因为一开始为0,会堵塞到子进程将信号量+1才会继续执行
                printf("this is father\n");
                vPut(semid);
                semctl(semid,0,IPC_RMID);//删除信号量
        }
        else if(pid == 0){
        		sleep(1);
                printf("this is child\n");
                vPut(semid);//子进程将信号量+1
               	putchar('\n');
        }else{
                printf("fork error\n");
        }
}

在这里插入图片描述
如果没有pv操作,父进程肯定时先执行完的,因为子进程sleep了2秒

示例2 (不同进程访问共享内存—读者写者问题–写者优先)
在这里插入图片描述

  1. 先实现一个共享资源只能有一个进程(一个读者或者一个写者)访问
semaphore rw=1;//用于实现对共享数据的互斥访问

	写进程:			
	writer(){			
		while(1){	
			P(rw);	
			"写操作"	
			V(rw);	
		}				
	}

	读进程:			
	reader(){			
		while(1){
			P(rw);		
			"读操作"
			V(rw);
			
		}				
	}				
  1. 实现多个读者可以一起访问共享资源,写者想访问的时候需要等待读者全部访问结束才能访问。(也就是第一个读者进来的时候执行p操作,直到最后一个读者出去的时候在执行v操作)
semaphore rw=1;//用于实现对共享数据的互斥访问
int count=0//记录当前几个读进程在访问共享数据

	写进程:			
	writer(){			
		while(1){	
			P(rw);	
			"写操作"	
			V(rw);	
		}				
	}

	读进程:			
	reader(){			
		while(1){
			if(count==0){	
				P(rw);
			}
			count++;	
			"读操作"
			count--;		
			if(count==0){
				V(rw);
			}

			
		}				
	}				

上面的代码时有bug的,如果读进程A执行了P(rw)操作,但是在执行"count ++"之前(此时count还是0),读进程B进来也是会执行P(rw)操作,就会被堵塞。 所以我们需要对count进行上锁

semaphore rw=1;//用于实现对共享数据的互斥访问
int count=0//记录当前几个读进程在访问共享数据
semaphore mutex=1;//对count变量的互斥访问
	写进程:			
	writer(){			
		while(1){	
			P(rw);	
			"写操作"	
			V(rw);	
		}				
	}

	读进程:			
	reader(){			
		while(1){
			P(mutex);
			if(count==0){	
				P(rw);
			}
			count++;
			V(mutex);			
			"读操作"			
			P(mutex);
			count--			
			if(count==0){
				V(rw);
			}
			V(mutex);
			
		}				
	}				
  1. 假设读者源源不断,那么count会一直累加,写者会一直等待。所以需要再新增一个信号量来实现写者优先。
semaphore rw=1;//用于实现对共享数据的互斥访问
int count=0//记录当前几个读进程在访问共享数据
semaphore mutex=1;//对count变量的互斥访问
semaphore w=1;//用于实现写者优先
	写进程:			
	writer(){			
		while(1){
			P(w);	
			P(rw);	
			"写操作"	
			V(rw);	
			V(w);
		}				
	}

	读进程:			
	reader(){			
		while(1){
			P(w);
			P(mutex);
			if(count==0){	
				P(rw);
			}
			count++;
			V(mutex);
			V(w);
			
			"读操作"
			
			P(mutex);
			count--;
			
			if(count==0){
				V(rw);
			}
			V(mutex);
			
		}				
	}				

代码实现
//semWrite.c 写者

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<string.h>
#include <sys/sem.h>
#include <sys/shm.h>
#define rw      0//用于实现对共享数据的互斥访问
#define mutex   1//对count变量的互斥访问
#define w       2//用于实现写者优先

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 p(int id,int num)
{
        struct sembuf set;
        set.sem_num=num;
        set.sem_op=-1;
        set.sem_flg=SEM_UNDO;
        semop(id,&set,1);
}
void v(int id,int num)
{
        struct sembuf set;
        set.sem_num=num;
        set.sem_op=1;
        set.sem_flg=SEM_UNDO;
        semop(id,&set,1);
}


int main()
{
        key_t key;
        key =ftok(".",'q');
        int shmid;
        int semid;
        shmid=shmget(key,1024*4,IPC_CREAT|0666);
        if(shmid==-1){
                printf("shmget error\n");
        }
        char *shmAddr;
        shmAddr=shmat(shmid,0,0);
        memset(shmAddr,0,1024*4);
        int i;
        semid=semget(key,3,IPC_CREAT|0666);
        union semun initsem;
        unsigned short arr[3]={1,1,1};//设置信号集的值为1
        initsem.array=arr;
        semctl(semid,0,SETALL,initsem);
        printf("rw:%d\n",semctl(semid,0,GETVAL));
        printf("mutex:%d\n",semctl(semid,1,GETVAL));
        printf("w:%d\n",semctl(semid,2,GETVAL));

        for(i=0;i<10;i++){
            	p(semid,w);
                p(semid,rw);
                memset(shmAddr,0,1024*4);
                sprintf(shmAddr,"from is write msg %d",i);
                printf("write over\n");
                v(semid,rw);
                v(semid,w);
                sleep(1);
        }

        shmdt(shmAddr);
        printf("write pro over!\n");

        return 0;
}


//semRead 读者

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include<string.h>
#include <sys/sem.h>
#include <sys/shm.h>
#define rw      0
#define mutex   1
#define w       2
int count =     0;

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 p(int id,int num)
{
        struct sembuf set;
        set.sem_num=num;
        set.sem_op=-1;
        set.sem_flg=SEM_UNDO;
        semop(id,&set,1);
}
void v(int id,int num)
{
        struct sembuf set;
        set.sem_num=num;
        set.sem_op=1;
        set.sem_flg=SEM_UNDO;
        semop(id,&set,1);
}

int main()
{
        key_t key;
        key =ftok(".",'q');
        int shmid;
        int semid;
        shmid=shmget(key,1024*4,0);//打开共享内存
        if(shmid==-1){
                printf("shmget error\n");
        }
        char *shmAddr;
        shmAddr=shmat(shmid,0,0);//连接共享内存
        int i;
        semid=semget(key,3,0);//获取信号量集

        for(i=0;i<10;i++){
	 			p(semid,w);
                p(semid,mutex);
                if(count==0){ 
                        p(semid,rw);
                }
                count++;
                v(semid,mutex);
                v(semid,w);
                printf("get msg:%s\n",shmAddr);
                p(semid,mutex);
                count--;
                if(count==0){
                        v(semid,rw);
                }
                v(semid,mutex);
                sleep(1);
               
        }
        shmdt(shmAddr);//断开连接
        printf("read pro over!\n");
        shmctl(shmid,IPC_RMID,0);//关闭共享内存
        semctl(semid,0,IPC_RMID);

        return 0;
}
  

在这里插入图片描述
在这里插入图片描述

示例3(消息队列,信号量,共享内存)
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;
      if(semctl(sem_id, 0, SETVAL, tmp) == -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);
     if(shm == NULL)
     {
         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);
             printf("%s\n",shm);
             sem_v(semid);
         }
     }
 
     // 断开连接
     shmdt(shm);
 
     /*删除共享内存、消息队列、信号量*/
     shmctl(shmid, IPC_RMID, &buf1);
     msgctl(msqid, IPC_RMID, &buf2);
     del_sem(semid);
     return 0;
 }

cllient.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(shm == NULL)
     {
         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);
                 break;
             case 'q':
                 msg.mtype = 888;
                 msg.mtext = 'q';
                 msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                 flag = 0;
                 break;
             default:
                 printf("Wrong input!\n");
                /*清空标准输入缓冲区*/
                 while((c=getchar())!='\n' && c!=EOF);
         }
     }
 
     // 断开连接
     shmdt(shm);
 
     return 0;
 }

在这里插入图片描述

注意:当scanf()输入字符或字符串时,缓冲区中遗留下了\n,所以每次输入操作后都需要清空标准输入的缓冲区。使用: while((c=getchar())!=‘\n’ && c!=EOF);
参考文章

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值