• System V信号量


    1. System V IPC

    概述

    以下三种类型的IPC合称为System V IPC:

    • System V信号量
    • System V消息队列
    • System V共享内存

    System V IPC在访问它们的函数和内核为它们维护的信息上有一些类似点,主要包括:

    • IPC键和ftok函数
    • ipc_perm结构
    • 创建或打开时指定的用户访问权限
    • ipcs和ipcrm命令

    下表汇总了所有System V IPC函数。

      信号量 消息队列 共享内存
    头文件 sys/sem.h sys/msg.h sys/shm.h
    创建或打开IPC的函数 semget msgget shmget
    控制IPC操作的函数 semctl msgctl shmctl
    IPC操作函数 semop msgsnd
    msgrcv
    shmat
    shmdt

    IPC键和ftok函数

    三种类型的System V IPC都使用IPC键作为它们的标识,IPC键是一个key_t类型的整数,该类型在sys/types.h中定义。
    IPC键通常是由ftok函数赋予的,该函数把一个已存在的路径名pathname和一个非0整数id组合转换成一个key_t值,即IPC键。

    #include <sys/ipc.h>
    
    //成功返回IPC键,失败返回-1
    key_t ftok(const char *pathname, int id);
    

    参数说明:

    • pathname在是程序运行期间必须稳定存在,不能反复创建与删除
    • id不能为0,可以是正数或者负数

    ipc_perm结构

    内核给每个IPC对象维护一个信息结构,即struct ipc_perm结构,该结构及System V IPC函数经常使用的常值定义在sys/ipc.h头文件中。

    struct ipc_perm
    {
        uid_t   uid;   //owner's user id
        gid_t   gid;   //owner's group id
        uid_t   cuid;  //creator's group id
        gid_t   cgid;  //creator's group id
        mode_t  mode;  //read-write permissions
        ulong_t seq;   //slot usage sequence number
        key_t   key;   //IPC key
    };
    

    创建与打开IPC对象

    创建或打开一个IPC对象使用相应的xxxget函数,它们都有两个共同的参数:

    • 参数key,key_t类型的IPC键
    • 参数oflag,用于指定IPC对象的读写权限(ipc_perm.mode),并选择是创建一个新的IPC对象还是打开一个已存在的IPC对象

    对于参数key,应用程序有两种选择:

    • 调用ftok,给它传pathname和id
    • 指定key为IPC_PRIVATE,这将保证会创建一个新的、唯一的IPC对象,但该标志不能用于打开已存在的IPC对象,只能是新建

    对于参数oflag,如上所述,它包含读写权限、创建或打开这两方面信息:

    • 可以指定IPC_CREAT标志,其含义和Posix IPC的O_CREAT一样
    • 还可以设置为下表所示的常值来指定读写权限

    ipcs和ipcrm命令

    • 由于System V IPC的三种类型不是以文件系统路径名标识的,因此无法使用ls和rm命令查看与删除它们
    • ipcs和ipcrm分别用于查看与删除系统中的System V IPC
    usage : ipcs -asmq -tclup 
        ipcs [-s -m -q] -i id
        ipcs -h for help.
    
    usage: ipcrm [ [-q msqid] [-m shmid] [-s semid]
              [-Q msgkey] [-M shmkey] [-S semkey] ... ]
    

    2. System V信号量

    计数信号量集

    我们已经知道了Posix信号量采用计数信号量,System V信号量在此基础上增加了一级复杂度,它采用计数信号量集,计数信号量集是由一个或多个计数信号量构成的集合。
    对于系统中的每个信号量集,内核都维护一个struct semid_ds信息结构,它定义在sys/sem.h头文件中。

    struct semid_ds
    {
        struct ipc_perm  sem_perm;
        struct sem       *sem_base;  //指向信号量集的指针
        ushort           sem_nsems;  //信号量集中的信号量个数
        time_t           sem_otime;  //上一次调用semop的时间
        time_t           sem_ctime;  //创建时间或上一次以IPC_SET调用semctl的时间
    };
    

    其中,sem_base是指向信号量集的指针,信号量集中的每个成员都对应一个struct sem结构:

    struct sem
    {
        ushort_t  semval;  //信号量的值
        short     sempid;  //上一次成功调用semop,或以SETVAL、SETALL调用semctl的进程ID
        ushort_t  semncnt; //等待semval变为大于当前值的线程数
        ushort_t  semzcnt; //等待semval变为0的线程数
    };
    

    semget

    semget用于创建一个新的信号量集或打开一个已存在的信号量集。

    • nsems参数指定集合中的信号量个数,如果是打开一个已存在的信号量集,就把该参数设为0
    • oflag参数可设置为IPC_CREAT,以及SEM_R和SEM_A指定的访问权限,如果是打开一个已存在的信号量集,就把该参数设为0
    • 成功时返回一个称为信号量标识符的非负整数,semop和semctrl函数将使用它
    //成功返回信号量标识符,失败返回-1
    int semget(key_t key, int nsems, int oflag);
    

    当实际操作为创建一个新的信号量集时,相应semid_ds结构中与集合中每个信号量关联的struct sem结构并不初始化,而是在以SETVAL或SETALL命令调用semctrl时初始化的。
    也就是说,创建一个新的System V信号量集(semget)并将它初始化(semctl)需要两次函数调用,
    这是System V信号量的一个致命缺陷。

    semop

    使用semget打开一个信号量集后,对其中一个或多个信号量值的操作就使用semop函数。

    //成功返回0,失败返回-1
    int semop(int semid, struct sembuf *ops, size_t nops);
    
    • semid指定待操作的信号量集
    • nops为集合中的信号量个数
    • ops指向一个struct sembuf类型的结构数组,该数组中的每个元素给目标信号量集中某个特定的信号量指定sem_op操作,这个特定的信号量由sem_num指定

    我们只保证sembuf含有以下三个成员,不保证这三个成员的顺序,也不保证还有其他成员,因此sembuf数组元素必须采用如ops[0].sem_num = 0所示的方法进行初始化。

    struct sembuf
    {
        unsigned short sem_num;  /* semaphore number */
        short          sem_op;   /* semaphore operation */
        short          sem_flg;  /* operation flags */
    };
    

    sem_op指定的操作有三类:

    • sem_op > 0:操作内容为semval += sem_op,这对应于释放某个信号量控制的资源
    • sem_op = 0:调用者阻塞等待直到semval变为0
    • sem_op < 0:调用者阻塞等待直到semval >= abs(sem_op),调用者阻塞等待直到semval>这对应于分配资源

    sem_flg可设置的值有:0、IPC_NOWAIT、SEM_UNDO,一般使用0,对于其余两个值:

    • IPC_NOWAIT用于给信号量集中某个特定信号量设置非阻塞标志
    • SEM_UNDO为System V信号量特有的复旧机制

    semctl

    semctl函数对一个信号量集执行各种控制操作。

    //成功根据cmd返回相应的非负值,失败返回-1
    int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
    
    • semid指定待控制的信号量集
    • semnum指定信号量集内的某个成员,仅在cmd为GETVAL、SETVAL、GETNCNT、GETZCNT和GETPID时使用
    • cmd指定控制命令
    • arg是可选的,取决于cmd的具体值

    第四个参数arg是可选的,取决于cmd的值,当需要用到该参数时,应用程序必须按如下结构定义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) */
    };
    

    定义如下术语用于后续cmd说明:

    • semval:信号量的当前值
    • sempid:上一次成功调用semop,或以SETVAL、SETALL调用semctl的进程ID
    • semncnt:等待semval变为大于当前值的线程数
    • semzcnt:等待semval变为0的线程数

    System V支持下列cmd值,除非特殊说明,否则成功时返回值均为0。

    cmd 说 明
    GETVAL 把semval的当前值作为函数返回值返回,它是一个unsigned short类型的整数
    SETVAL 把semval的值设为arg.val
    GETPID 把sempid的当前值作为函数返回值返回
    GETNCNT 把semncnt的当前值作为函数返回值返回
    GETZCNT 把semzcnt的当前值作为函数返回值返回
    GETALL 返回信号量集内每个成员的semval值,这些值通过arg.array返回,arg.array需由调用者分配内存
    SETALL 设置信号量集内每个成员的semval值,这些值通过arg.array指定,此时第二个参数semnum设为0即可
    IPC_STAT 返回信号量集当前的semid_ds结构,该结构通过arg.buf返回,arg.buf需由调用者分配内存,可以用该命令获得信号量集中的信号量个数
    IPC_SET 设置信号量集对应semid_ds结构中的sem_perm.uid、sem_perm.gid和sem_perm.mode,设置的值来自arg.buf
    IPC_RMID 删除由semid指定的信号量集,此时第二个参数semnum设为0即可

    其中,前五个命令针对的都是信号量集semid中由semnum指定的信号量。

    3. 测试程序

    代码实现

    semcreate.c

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    #define FTOK_FILE       "/home/delphi/ftok.file"
    #define FTOK_ID         1
    #define SEM_MODE        0666
    
    /*
    #define SEM_MODE_OWNER  SEM_R | SEM_A
    #define SEM_MODE_GROUP  (SEM_R >> 3) | (SEM_A >> 3)
    #define SEM_MODE_OTHER  (SEM_R >> 6) | (SEM_A >> 6)
    #define SEM_MODE        (SEM_MODE_OWNER | SEM_MODE_GROUP | SEM_MODE_OTHER)
    */
    
    int main()
    {
        int nsems = 3;
        int oflag = IPC_CREAT | SEM_MODE;
        key_t key = ftok(FTOK_FILE, FTOK_ID);
        int semid = semget(key, nsems, oflag);
    
        if (semid >= 0)
        {
            printf("semcreate success, semid = %d
    ", semid);
        }
    
        return 0;
    }
    

    这里遇到个问题,SEM_MODE一开始是按注释部分定义的,但man semget给出的三个头文件都已经包含了,编译时却报错,提示SEM_R和SEM_A未定义,不知道为什么,只能用0666定义了。

    semrmid.c

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    #define FTOK_FILE       "/home/delphi/ftok.file"
    #define FTOK_ID         1
    
    int main()
    {
        key_t key = ftok(FTOK_FILE, FTOK_ID);
        int semid = semget(key, 0, 0);
        semctl(semid, 0, IPC_RMID);
    
        return 0;
    }
    

    semsetvalues.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    #define FTOK_FILE       "/home/delphi/ftok.file"
    #define FTOK_ID         1
    
    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) */
    };
    
    int main(int argc, char **argv)
    {
        key_t key;
        int semid;
        int nsems;
        unsigned short *semvals;
        union semun arg;
        struct semid_ds seminfo;
        int i;
    
        /* 打开semcreate创建的信号量集 */
        key = ftok(FTOK_FILE, FTOK_ID);
        semid = semget(key, 0, 0);
    
        /* 获得信号量集中的信号量个数 */
        arg.buf = &seminfo;
        semctl(semid, 0, IPC_STAT, arg);
        nsems = arg.buf->sem_nsems;
    
        /* 设置信号量集中每个信号量的值 */
        semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));
    
        for (i = 0; i < nsems; i++)
        {
            semvals[i] = atoi(argv[i + 1]); //通过命令行参数分别指定集合中每个信号量的值
        }
    
        arg.array = semvals;
        semctl(semid, 0, SETALL, arg);
    
        return 0;
    }
    

    semgetvalues.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    #define FTOK_FILE       "/home/delphi/ftok.file"
    #define FTOK_ID         1
    
    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) */
    };
    
    int main(int argc, char **argv)
    {
        key_t key;
        int semid;
        int nsems;
        unsigned short *semvals;
        union semun arg;
        struct semid_ds seminfo;
        int i;
    
        /* 打开semcreate创建的信号量集 */
        key = ftok(FTOK_FILE, FTOK_ID);
        semid = semget(key, 0, 0);
    
        /* 获得信号量集中的信号量个数 */
        arg.buf = &seminfo;
        semctl(semid, 0, IPC_STAT, arg);
        nsems = arg.buf->sem_nsems;
    
        /* 获得信号量集中每个信号量的值 */
        semvals = (unsigned short *)calloc(nsems, sizeof(unsigned short));
        arg.array = semvals;
        semctl(semid, 0, GETALL, arg);
    
        for (i = 0; i < nsems; i++)
        {
            printf("semvals[%d] = %d
    ", i, semvals[i]);
        }
    
        return 0;
    }
    

    semops.c

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    
    #define FTOK_FILE       "/home/delphi/ftok.file"
    #define FTOK_ID         1
    
    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) */
    };
    
    int main(int argc, char **argv)
    {
        key_t key;
        int semid;
        int nsems;
        union semun arg;
        struct semid_ds seminfo;
        struct sembuf *semops;
        int i;
    
        /* 打开semcreate创建的信号量集 */
        key = ftok(FTOK_FILE, FTOK_ID);
        semid = semget(key, 0, 0);
    
        /* 获得信号量集中的信号量个数 */
        arg.buf = &seminfo;
        semctl(semid, 0, IPC_STAT, arg);
        nsems = arg.buf->sem_nsems;
    
        /* 对信号量集中的所有信号量进行相同的semop操作 */
        semops = (struct sembuf *)calloc(nsems, sizeof(struct sembuf));
    
        for (i = 0; i < nsems; i++)
        {
            semops[i].sem_num = i;
            semops[i].sem_op = atoi(argv[1]); //操作类型由命令行参数指定,>0, 0, <0
            semops[i].sem_flg = 0;
        }
    
        semop(semid, semops, nsems);
    
        return 0;
    }
    

    运行测试

    • 运行semcreate,通过运行前后的ipcs -s,可以确认信号量集创建成功

    • 运行semsetvalues,将三个信号量的值分别设为1、2、3
    • 然后运行semgetvalues,打印信息和我们设置的值一致,符合预期

    • 运行semops,并指定sem_op > 0
    • 然后运行semgetvalues,打印信息显示每个信号量的值都增加了1,符合预期

    • 运行semrmid,通过运行前后的ipcs -s,可以确认信号量集已经从系统删除

  • 相关阅读:
    文件参数Python读取wav格式文件
    电子工程术语和定义列表,按字母顺序排列
    MAC地址加减1算法
    uboot通过kernel command line 动态分区 CONFIG_MTD_CMDLINE_PARTS
    c调用shell脚本
    busybox提示can't access tty.job control turned off
    cut命令如何截取以空格隔开的字段
    DS28E01100
    busybox 中的ntpd使用
    Debugfs
  • 原文地址:https://www.cnblogs.com/songhe364826110/p/11537835.html
Copyright © 2020-2023  润新知