信号量原理

  • 保证多进程(线程)互斥访问某种共享资源(共享内存,文件)
  • 一个用于协调同步互斥的计数器
  • 与操作系统的PV操作类似
    信号量的值等于临界区中资源的数量,进程进临界区前需要多少减多少,不够减就阻塞进阻塞队列
    同属于System V IPC

API以及与信号量相关的各种数据结构

  1. 创建信号量集合
#include <sys/sem.h>
int semget(key_t key,int nsems,int flg);

参数表:
key:信号量的键值
nsems:集合中信号量的个数
flag:权限
返回值:
大于0:信号量集合ID
-1:失败

  1. 操作信号量集合
#include <sys/sem.h>
int semctl(int semid,int semnum,int cmd[,union semun arg]);

参数表:
semid:信号量集合ID
semnum:对集合中哪个信号量进行操作(索引类似数组下标)
cmd:命令
arg:根据cmd不同而不同
返回值:
非-1:cmd不同而不同
-1:失败

union semun
此共用体用于对应cmd的不同操作来返回(设置)不同的数值,此数据结构有些系统必须自行实现

union semun
{
  int val;
  struct semid_ds *buf;
  unsigned short *array;
}

cmd 操作 arg
IPC_STAT 获取semid_ds arg.buf
IPC_SET 设置semid_ds arg.buf
IPC_RMID 删除信号量集合 NULL
GETVAL 返回semnum号信号量的值 NULL
SETVAL 设置semnum号信号量的值 arg.val
GETALL 返回集合中所有信号量的值 arg.array
SETALL 设置集合中所有信号量的值 arg.array
GETPID 返回semnum号信号量的sempid NULL
GETNCNT 返回semnum号信号量的semncnt NULL
SETZCNT 返回semnum号信号量的semzcnt NULL
赋初值示例代码(封装成函数)

sem_init(int sem_id,int semnum, int val)
{
  union semun
  {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
  }initval;
  initval.val = val;
  if(semctl(semid,semnum,SETVAL,initval)==-1)
  {
      perror("semctl");
      exit(EXIT_FAILURE);
  }
}
  1. PV操作
#include <sys/sem.h>
int semop(int semid,struct sembuf semarray[],size_t nops);

参数表:
semid:信号量集合ID
semarray:存放操作的数组,数组中每个元素代表一步操作
nops:操作的步数
返回值:
0:成功
-1:失败
struct sembuf结构体
此结构体存放具体的操作

struct sembuf
{
  unsigned short sem_num;//对几号信号量操作
  short sem_op;//5表示v5个资源,-2表示p2两资源
  short sem_flg;//资源不够怎么办(阻塞还是出错)(IPC_NOWAIT或SEM_UNDO)
}

SEM_UNDO用于将修改的信号量值在进程正常退出(调用exit退出或main执行完)或异常退出(如段异常、除0异常、收到KILL信号等)时归还给信号量。有效防止死锁

PV操作示例代码(写进程与写进程之间的互斥)
每次一能有一个写进程进入临界区,只有一个信号量,初始值为1

 void sem_p(int sem_id)
 {//p操作
         cout << "now lock" << endl;        
         struct sembuf action[1];//一步操作
   
         action[0].sem_num = 0;  //对0号信号量操作
         action[0].sem_op = -1;   //信号量减1,进入临界区
         action[0].sem_flg = SEM_UNDO;

         if(semop(sem_id,action,1)==-1)
         {       
                 perror("semop");
                exit(EXIT_FAILURE);
          }
 }
 void sem_v(int sem_id)
 {//v操作
         cout << "now unlock" << endl;        
         struct sembuf action[1];//一步操作
   
         action[0].sem_num = 0;  //对0号信号量操作
         action[0].sem_op = +1;   //出临界区,信号量加一
         action[0].sem_flg = SEM_UNDO;

         if(semop(sem_id,action,1)==-1)
         {       
                 perror("semop");
                exit(EXIT_FAILURE);
          }
 }

示例代码

一个写进程,多个读进程互斥访问共享内存
写时临界区内不能有读
读时临界区内不能有写,但可以有读

写端:写进程负责共享内存创建删除,信号量创建删除

//写进程

#include <sys/shm.h>
#include <sys/sem.h>
#include <unistd.h>
#include <cstring>
#include <iostream>
using namespace std;

#define SHM_KEY 99
#define SEM_KEY 111

//共用体
union semun
{
    int val;//信号量的值
    struct semid_ds *buf;
    unsigned short *array;  
};

/************信号量初始化*******/
void sem_init(int sem_id,int semnum,int value)
{
    union semun initval;
    initval.val = value;
    if(semctl(sem_id,semnum,SETVAL,initval)==-1 )
    {
        perror("semctl");
        exit(EXIT_FAILURE);
    }

}

/***************p操作,写前加锁*/
void sem_p(int sem_id)
{
    cout << "now lock for wirte" << endl;

    struct sembuf action[2];//一步操作

    action[0].sem_num = 0;  //对0号信号量(读锁)操作
    action[0].sem_op = 0;   //期待信号量为0(临界区内无进程在读)
    action[0].sem_flg = SEM_UNDO;       

    action[1].sem_num = 1;  //对1号信号量(写锁)操作
    action[1].sem_op = +1;  //表示写进程进入互斥区
    action[1].sem_flg = SEM_UNDO;       
    if(semop(sem_id,action,2)==-1)
    {
        perror("semop");
        exit(EXIT_FAILURE);
    }
}

/*****************v操作,写后减锁*/
void sem_v(int sem_id)
{

    cout << "now unlock for wirte" << endl;

    struct sembuf action[1];

    action[0].sem_num = 1;  //对1号互斥量(写锁)操作
    action[0].sem_op = -1;  //写进程退出临界区
    action[0].sem_flg =SEM_UNDO;
    if(semop(sem_id,action,1)==-1)
    {
        perror("semop");
        exit(EXIT_FAILURE);
    }
}

int main()
{
    int shm_id; //共享内存id
    int sem_id; //信号量(集合)id
    char * shm_ptr; //共享内存起始地址
    int i=10;       //while计数

    /*共享内存创建*/  
    shm_id = shmget(SHM_KEY,1024,IPC_CREAT|0777);
    if(shm_id == -1)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    /*共享内存连接*/
    shm_ptr = (char *)shmat(shm_id,NULL,0);
    if(shm_ptr == NULL)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    /*创建信号量集合*/
    sem_id = semget(SEM_KEY,2,IPC_CREAT|0666);

    /*信号量初始化*/
    sem_init(sem_id,0,0);//表示临界区内的读进程数量
    sem_init(sem_id,1,0);//表示临界区内的写进程数量
    while(i>0)
    {//循环写入10次
        /*p操作*/ 
        sem_p(sem_id);

        /*****临界区**********************************************************************/

        strcpy(shm_ptr,"111111");
        sleep(2);   //写进程的时间,此时读进程阻塞
        /*****临界区**********************************************************************/

        /*v操作*/
        sem_v(sem_id);
        i--;

    }
    shmctl(shm_id,IPC_RMID,NULL);//删除共享内存
    semctl(sem_id,0,IPC_RMID,NULL);//删除信号量
}

读端:写进程循环写时,读端可以多次读来观察互斥情况

//读进程
#include <iostream>
using namespace std;

#include <sys/shm.h>
#include <sys/sem.h>
#define SHM_KEY 99
#define SEM_KEY 111

//共用体
union semun
{
    int val;//信号量的值
    struct semid_ds *buf;
    unsigned short *array;  
};

/***************p操作,读前加锁*/
void sem_p(int sem_id)
{
    cout << "now lock for read" << endl;

    struct sembuf action[2];
    
    action[0].sem_num = 1;  //对1号信号量(写锁)操作,
    action[0].sem_op = 0;   //期待没有进程在写
    action[0].sem_flg = SEM_UNDO;       

    action[1].sem_num = 0;  //对0号信号量(写锁)操作
    action[1].sem_op = +1;  //一个读进程进入临界区
    action[1].sem_flg = SEM_UNDO;       
    if(semop(sem_id,action,2)==-1)
    {
        perror("semop");
        exit(EXIT_FAILURE);
    }
}

/*****************v操作,读后减锁*/
void sem_v(int sem_id)
{
    
    cout << "now unlock for read" << endl;

    struct sembuf action[1];
    
    action[0].sem_num = 0;  //对0号互斥量(读锁)操作
    action[0].sem_op = -1;  //一个读进程退出临界区
    action[0].sem_flg =SEM_UNDO;
    if(semop(sem_id,action,1)==-1)
    {
        perror("semop");
        exit(EXIT_FAILURE);
    }
}


int main()
{
    int shm_id;
    int sem_id;
    char * shm_ptr;


    shm_id = shmget(SHM_KEY,1024,0777);
    if(shm_id == -1)
    {
        perror("shmget");
        exit(EXIT_FAILURE);
    }


    shm_ptr = (char *)shmat(shm_id,NULL,0);
    if(shm_ptr == NULL)
    {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    //引用信号量集合(写进程已经创建好并初始化)
    sem_id = semget(SEM_KEY,2,0);

    /*p操作*/
    sem_p(sem_id);

/*****临界区***/


    cout << shm_ptr <<endl; 


/******临界区**/
    
    /*V操作*/
    sem_v(sem_id);

    /*共享内存摆脱*/
    shmdt(shm_ptr);

    return 0;
}