zoukankan      html  css  js  c++  java
  • 【linux】系统编程-3-system-V IPC 信号量


    前言

    • 原文链接
    • 知识点
      • 消息队列、信号量共享内存 被统称为 system-V IPC
        • 以上都是“持续性”资源,即它们被创建之后, 不会因为进程的退出而消失
    • 说明:
      • 以下 信号量 如无说明,均为 system-V IPC 信号量

    5. 信号量

    5.1 概念

    • 信号量
      • 信号量可以理解为一个计数器
      • 主要用于保护资源
    • 原子操作:单指令的操作称为原子操作,单条指令的执行时不会被打断的

    5.2 工作原理

    • 信号量就是两种操作:P 操作和 V 操作
      • P 操作:就是申请资源,资源 -1
      • V 操作:就是释放资源,资源 +1
      • 资源为 0 时,无资源申请

    5.3 操作函数

    • 使用 semget() 创建或获取一个信号量
    • 使用 semop() 进行 PV 操作
    • 使用 semctl() 进行一系列控制操作

    5.3.1 semget()

    • 使用 semget() 创建或获取一个信号量
    • 通过命令 man 了解更多
    • 函数原型:int semget(key_t key, int nsems, int semflg);
      • key:信号量键值,可以自定义一个键值,也可以使用 IPC_PRIVATE 创建一个没有 key 的信号量
      • nsems信号量数目
      • semflg:表示创建的信号量的模式标志参数,主要有IPC_CREAT,IPC_EXCL和权限mode,如:
        • IPC_CREAT:没有关键字 key 的信号量就新建一个,有就直接打开
        • IPC_CREAT | IPC_EXCL:信号量不存在,则新建一个,如果信号量存在,则报错
        • IPC_CREAT | 0666:(注:信号量不在意执行权限)
      • 返回:
        • 成功:返回信号量标识符
        • 失败:返回 -1,原因记录在变量 error
          • EACCES:没有访问该信号量集的权限
          • EEXIST:信号量集已经存在,无法创建
          • EINVAL:参数nsems的值小于0或者大于该信号量集的限制;或者是该key关联的信号量集已存在,并且nsems大于该信号量集的信号量数
          • ENOENT:信号量集不存在,同时没有使用IPC_CREAT
          • ENOMEM :没有足够的内存创建新的信号量集
          • ENOSPC:超出系统限制
    • 创建信号量也受下面值限制:(通过命令 ipcs -l 可查)
      • SEMMNI:系统中信号量总数的最大值
      • SEMMSL:每个信号量中信号量元素个数的最大值
      • SEMMNS:系统中所有信号量中的信号量元素总数的最大值。

    5.3.2 semop()

    • 使用 semop() 进行 PV 操作
    • 通过命令 man 了解更多
    • 函数原型:int semop(int semid, struct sembuf *sops, size_t nsops);
      • semid:信号量标识符
      • sops:指向存储信号操作结构的数组指针,信号操作结构的原型如下:
        struct sembuf
        {
            unsigned short int sem_num;   /* 信号量的序号从0 ~ nsems-1 */
            short int sem_op;            /* 对信号量的操作,>0, 0, <0 */
            short int sem_flg;            /* 操作标识:0, IPC_WAIT, SEM_UNDO */
        };
        
        • sem_num:标识信号量中第几个信号量,从 0 开始
        • sem_op:对信号量所进行的操作类型
          • 0:V 操作(回收资源),把 sem_op 的值加到该信号量的信号量当前值 semval 上

          • = 0:表示进程要阻塞等待,直至信号量当前值 semval 变为 0
            • 如果没有设置 IPC_NOWAIT ,则调用该操作的进程或者线程将暂时睡眠,直到信号量的值为0
            • 如果设置 IPC_NOWAIT,则进程或者线程不会睡眠,函数返回错误EAGAIN
          • < 0:P 操作(申请资源),如果其绝对值大于信号值 semval ,则操作会阻塞;直到信号值 semval 大于等于 sem_op 的绝对值
      • nsops:信号操作标志
        • 0:正常操作
        • SEM_UNDO:程序结束时(不论正常或异常),保证信号值会被重设为 semop() 调用前的值。目的是避免资源永远锁定。
        • 信号操作结构的数量,恒大于或等于1
      • 返回:
        • 成功:返回 0
        • 失败:返回 -1,原因记录在变量 error
          • E2BIG:一次对信号的操作数超出系统的限制
          • EACCES:调用进程没有权能执行请求的操作,并且不具有CAP_IPC_OWNER权能
          • EAGAIN:信号操作暂时不能满足,需要重试
          • EFAULT:sops或timeout指针指向的空间不可访问
          • EFBIG:sem_num指定的值无效
          • EIDRM:信号集已被移除
          • EINTR:系统调用阻塞时,被信号中断
          • EINVAL:参数无效
          • ENOMEM:内存不足
          • ERANGE:信号所允许的值越界

    5.3.3 semctl()

    • 使用 semctl() 进行一系列控制操作
    • 通过命令 man 了解更多
    • 函数原型:int semctl(int semid, int semnum, int cmd, ...);
      • semid:System V信号量的标识符
      • semnum:表示信号量集中的第 semnum 个信号量。它的取值范围: 0 ~ nsems-1
      • cmd:操作命令,主要有以下命令:
        • IPC_STAT:获取此信号量集合的semid_ds结构,存放在第四个参数的buf中
        • IPC_SET:通过第四个参数的buf来设定信号量集相关联的semid_ds中信号量集合权限为sem_perm中的uid,gid,mode
        • IPC_RMID:从系统中删除该信号量集合
        • GETVAL:返回第semnum个信号量的值
        • SETVAL:设置第semnum个信号量的值,该值由第四个参数中的val指定
        • GETPID:返回第semnum个信号量的sempid,最后一个操作的pid
        • GETNCNT:返回第semnum个信号量的semncnt。等待semval变为大于当前值的线程数
        • GETZCNT:返回第semnum个信号量的semzcnt。等待semval变为0的线程数
        • GETALL:去信号量集合中所有信号量的值,将结果存放到的array所指向的数组
        • SETALL:按arg.array所指向的数组中的值,设置集合中所有信号量的值
      • 第四个参数为一个可选的联合体:
        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)*/
        };
        

    5.4 例程

    • 只有一个资源
    • 等待子进程释放了资源后,父进程才继续往下执行
    • 信号量操作封装文件
    
    #include <sys/sem.h>
    #include <sys/ipc.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/shm.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include "sem.h"
    
    /**
      * @brief  初始化第一个信号量的资源数
      * @param  sem_id:信号量标识符
      * @param  init_value:初始化值
      */
    int init_sem(int sem_id, int init_value)
    {    
        union semun sem_union;    
        sem_union.val = init_value; /*init_value 为初始值*/
        if (semctl(sem_id, 0, SETVAL, sem_union) == -1)    
        {
            perror("Initialize semaphore");
            return -1;
        }
        return 0;
    }
    
    /**
      * @brief  从系统中删除信号量的函数
      * @param  sem_id:信号量标识符
      */
    int del_sem(int sem_id)
    {
        union semun sem_union;    
        if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)    
        {
            perror("Delete semaphore");
            return -1;
        }
    }
    
    /**
      * @brief  对第一个信号量进行 P 操作
      * @param  sem_id:信号量标识符
      */
    int sem_p(int sem_id)
    {
        struct sembuf sops;
        sops.sem_num = 0; /* 单个信号量的编号应该为 0 */    
        sops.sem_op = -1; /* 表示 P 操作 */    
        sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将还原信号量*/
    
        if (semop(sem_id, &sops, 1) == -1)    
        {
            perror("P operation");
            return -1;
        }
        return 0;
    }
    
    /**
      * @brief  对第一个信号量进行 V 操作
      * @param  sem_id:信号量标识符
      */
    int sem_v(int sem_id)
    {
        struct sembuf sops;
        sops.sem_num = 0; /* 单个信号量的编号应该为 0 */    
        sops.sem_op = 1; /* 表示 V 操作 */    
        sops.sem_flg = SEM_UNDO; /* 若进程退出,系统将还原信号量 */
    
        if (semop(sem_id, &sops, 1) == -1)    
        {
            perror("V operation");
            return -1;
        }
        return 0;
    }
    
    • 信号量demo APP
    #include <sys/types.h>
    #include <sys/shm.h>
    #include <sys/sem.h>
    #include <sys/ipc.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <errno.h>
    #include "sem.h"
    #define DELAY_TIME 5 /* 子进程释放信号量延时时间 */
    int main(void)
    {
        pid_t result;
        int sem_id;
        sem_id = semget((key_t)6666, 1, 0666 | IPC_CREAT); /* 创建一个信号量*/
        init_sem(sem_id, 0); // 初始化已创建的信号量的资源数为0
        /*调用 fork()函数*/
        result = fork();
        if(result == -1)
        {
            perror("Fork
    ");
        }    
        else if (result == 0) /*返回值为 0 代表子进程*/    
        {
            printf("Child process will wait for %d seconds...
    ", DELAY_TIME);
            sleep(DELAY_TIME);
            printf("The returned value is %d in the child process(PID = %d)
    ",result, getpid());
            sem_v(sem_id); // V 操作,释放资源
        }
        else /*返回值大于 0 代表父进程*/    
        {
            sem_p(sem_id); // 申请资源
            printf("The returned value is %d in the father process(PID = %d)
    ",result, getpid());
            sem_v(sem_id); // 释放资源
            del_sem(sem_id); // 删除信号量
        }
        exit(0);
    }
    

    参考:

    * 野火
  • 相关阅读:
    网站实时信息采集和统计graphite
    内存检查工具Valgrind
    usr/bin/ld: cannot find 错误解决方法和 /etc/ld.so.conf
    通用makefile
    关于/proc/进程idpid/fd ,根据fd来查找连接
    boost enable_shared_from_this
    cdll和windll的差别
    一些项目——空白格式化
    Session笔记
    黑马程序猿_7K面试题之交通灯系统
  • 原文地址:https://www.cnblogs.com/lizhuming/p/14208759.html
Copyright © 2011-2022 走看看