美文网首页
Linux信号量

Linux信号量

作者: FakeCSer爱去网吧 | 来源:发表于2020-02-24 12:57 被阅读0次

    信号量原理

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

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

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

    参数表:
    key:信号量的键值
    nsems:集合中信号量的个数
    flag:权限
    返回值:
    大于0:信号量集合ID
    -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);
      }
    }
    
    • 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;
    }
    
    

    相关文章

      网友评论

          本文标题:Linux信号量

          本文链接:https://www.haomeiwen.com/subject/fndmqhtx.html