• 《Linux应用进程间通信(四) — 信号量》


    1.信号量

      信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

       信号量有两种实现方式,分别对应system V信号量和Posix信号量。

      

      目前大多数情况下System V信号量用在进程间通信,而POSIX信号量用于线程间同步。具体区别参考:https://www.cnblogs.com/Zoran-/p/5819256.html

    为了获取共享资源,进程需要执行下列操作

    1.测试控制该资源的信号量。

    2.若信号量的值大于0,则进程可以使用该资源。在这种情况下,进程会将信号量的值减1,表示它使用了一个资源。

    3.若信号量的值为0,则进程进入休眠,直至信号量的值大于0。进程被唤醒后,它返回至步骤1。

    2.semget

    #include <sys/sem.h>
    
    int semget(key_t key, int num_sems, int sem_flags);
    第一个参数key:一个整型值对应内核中一个信号量对象,可以自己指定,不同信号量的key值不一样。不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个响应的信号标识符。 
    第二个参数num_sems:指定需要的信号量数目。
    第三个参数sem_flags:设置一组标志,与open函数的标志非常相似,包括信号量的权限等。IPC_CREAT标志是创建或者使用已有的信号量。 
                而IPC_CREAT和IPC_EXCL结合使用可以确保创建出的是一个新的、唯一的信号量,如果该信号量已存在,它将返回一个错误。 
                创建时给出的权限可以是:0600。  返回值: 成功:正数(非零)值; 失败:-1

      作用:第一次使用时创建信号量,以后使用时获取信号量。

      注意:如果是创建新的信号量(一般在服务器进程中),则必须指定num_sems。如果是引用现有信号量,则将num_sems指定为0。

    3.semop

    #include <sys/sem.h>
    
    int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);
    第一个参数sem_id:信号量标识符。
    第二个参数sem_ops是指向一个结构数组的指针,每个数组元素至少包含以下几个成员:
    struct sembuf{
       short sem_num; //信号量编号,除非使用一组信号量,否则它的取值为0
       short sem_op;  //信号量在一次操作中需要改变的数值。通常用到两个值,-1,也就是p操作,它等待信号量变为可用;+1,也就是V操作,它发送信号表示信号量现在已可用。
       short sem_flg; //通过被设置为SEM_UNDO。表示操作系统会跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量,防止其他进程一直处于等待状态。 
    };
    第三个参数num_sem_ops指的是结构数组的元素个数。就上上面这个结构数组。

    4.semctl

    #include <sys/shm.h>
    
    int semctl(int sem_id, int sem_num, int command,...);
    第一个参数sem_id:信号量标识符
    第二个参数sem_num:信号量编号,当用到成组的信号量,就要用到这个参数。它一般取值为0,表示这是第一个信号量。
    第三个参数command:要采取的动作
    有很多不同的值,有两个常用的SETVAL:用来把信号量初始化成一个已知的值,这个值通过union semun中的val成员设置。
                  IPC_RMID:用于删除一个已经无需使用的信号量标识符。
    第四个参数:可选。是否使用取决于所请求的命令。如果使用该参数,则其类型是semun。
    union semun{
      int val;
      struct semid_ds *buf;
      unsigned short *array;
    };
    一般只使用val这个成员,来为信号量赋初值。当信号量值为0时,进程会阻塞运行。

      作用:初始化或者删除信号量。

    5.sembuf中sem_flg的设置问题

      通常设置为SEM_UNDO,使操作系统跟踪信号量, 并在进程没有释放该信号量而终止时,操作系统释放信号量 ,例如在二元信号量中,你不释放该信号量 而异常退出,就会导致别的进程一直申请不到信号量,而一直处于挂起状态。

      是否设置sem_flg为SEM_UNDO的区别

    6.信号量的命令操作 

    查看:ipcs -s 
    

      

    删除:ipcrm -s semid 
    

      semid指的是信号量的id,可以同过查看命令看到。

    7.实例

     sem.h

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/sem.h>
    #include <unistd.h>
    
    struct semun
    {
        int val;
    };
    
    void sem_init();
    
    void sem_p();
    
    void sem_v();
    
    void sem_destroy();

    sem.c

    #include "sem.h"
    
    static int semid = 0;
    
    void sem_init()
    {
        semid = semget((key_t)1234,1,IPC_CREAT | IPC_EXCL| 0600);
        if(semid == -1)
        {
            semid = semget((key_t)1234,1,IPC_CREAT | 0600);
            if(semid == -1)
            {
                printf("semget error");
            }
        }
        else
        {
            union semun a;//a传值
            a.val = 1;
            if(semctl(semid,0,SETVAL,a)==-1)//0代表信号量下表
            {
                perror("semctl init error");
            }
        }
    }
    
    void sem_p()
    {
        struct sembuf buf;
        buf.sem_num = 0;//信号量下标
        buf.sem_op = -1;//p操作
        buf.sem_flg = SEM_UNDO;
        if(semop(semid,&buf,1)==-1)
        {
            perror("p error");
        }
    }
    
    void sem_v()
    {
        struct sembuf buf;
        buf.sem_num = 0;
        buf.sem_op = 1;
        buf.sem_flg = SEM_UNDO;//设置在进程出现错误时信号量值自动恢复,防止一个进程占着信号量
        if(semop(semid,&buf,1)==-1)//1表示操作数,sembuf的数量
        {
            perror("v error");
        }
    }
    
    void sem_destroy()
    {
        if(semctl(semid,0,IPC_RMID)==-1)//0代表信号量集
        {
            perror("semctl destroy error");
        }
    }

      模拟对打印机的使用,同一时刻只能一个人打印,printf代表打印,打印a开始,打印第二个a结束打印,打印a时不能打印b,可以出现4个a或4个b的情况,不能出现交错打印的情况。

      思路:a先访问打印机时,b中的p操作阻塞住,当a执行完,进行v操作,b执行p操作,此时a中的p操作阻塞,当b执行完,执行v操作,a中的p操作就不会阻塞。 

    a.c

    #include <stdio.h>
    #include <assert.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include "sem.h"
    
    int main()
    {
        sem_init();
        int i = 0;
        for(;i<10;++i)
        {
            sem_p();
            printf("A");
            fflush(stdout);
    
            int n = rand()%3;
            sleep(n);
    
            printf("A");
            fflush(stdout);
    
            sem_v();
            n = rand()%3;
            sleep(n);
        }
    }

    b.c

    #include <stdio.h>
    #include <assert.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include "sem.h"
    
    int main()
    {
        sem_init();
        int i = 0;
        for(;i<10;++i)
        {
            sem_p();
            printf("B");
            fflush(stdout);
    
            int n = rand()%3;
            sleep(n);
    
            printf("B");
            fflush(stdout);
    
            sem_v();
            n = rand()%3;//产生随机睡眠时间
            sleep(n);
        }
        sleep(10);
        sem_destroy();
    }

    输出结果:

  • 相关阅读:
    Linux 修改时区(debain)
    C#生成验证码
    httpPost 请求
    C# Array.FindAll
    C#删除字符串中所有的空字符
    C# 获取汉字区位码
    Qinq Vlan mapping 和DHCP 服务器
    Sql 分页存储过程
    request 之getParameterMap 方法
    如果我是面试官
  • 原文地址:https://www.cnblogs.com/zhuangquan/p/13154007.html
Copyright © 2020-2023  润新知