zoukankan      html  css  js  c++  java
  • 信号量分析

    以struct和union为线索来观察信号量


    第一部分 semid_ds ipc_perm

    内核为每个信号量集合维护一个结构体semid_ds:

    struct semid_ds {

           struct ipc_perm     sem_perm;

           unsigned short       sem_nsems;    /* # of semaphore in set */

           time_t                   sem_otime;     /* last-semop() time */

           time_t                   sem_ctime;     /* last-change time */

           …

    };

    semid_ds的一个结构体成员ipc_perm是XSI IPC为每一个IPC结构设置的, 定义如下:

    struct ipc_perm {

           uid_t      uid;        /* owner’s effective user id */

           gid_t      gui;        /* owner’s effective group id */

           uid_t      cuid;       /* creator’s effective user id */

           gid_t      cgid;       /* creator’s effective group id */

           mode_t   mode;     /* access mode */

           …..

    };

    好, 有了以上两个结构体, 我们先认识semget

    #include <sys/sem.h>

    int semget (key_t key; int nsems, int flag);

    返回值: 若成功则返回信号量ID, 若出错则返回-1

    先说semget和这两个struct的关系:

    如果是创建一个新的信号量集合, 内核为这个信号量集合维护一个结构体semid_ds, 同时对semid_ds的成员进行初始化:

    ipc_perm结构赋初值, ipc_perm中的mode被设置为flag中的相应权限位.

    sem_nsems设置为nsems

    sem_otime设置为0.

    sem_ctime设置为当前时间.

    另外, 注意如果创建新集合, 则必须指定nsems, 如果引用一个集合, 则nsems设定为0;

    一句话概括, 就是semget创建信号量集合的时候初始化了semid_ds.

    第二部分 semun和一个无名结构体

    semctl参数中使用了一个union, 定义如下

    union semun {

           int                         val;         /* for SETVAL */

           struct semid_ds      *buf;       /* for IPC_STAT and IPC_SET */

           unsigned short              *array;    /* for GETALL and SETALL */

    };

    还要认识一个无名结构体. (我总觉得无名结构体听起来就很cool). 每个信号量由这样一个结构体表示. 定义如下:

    struct {

           unsigned short              semval;   /* semaphore value, always >= 0 */

           pid_t                     sempid;   /* pid for last operation */

           unsigned short              semncnt; /* # processes awaiting semval>curval */

           unsigned short              semzcnt; /* # processes awaiting semval == 0 */

           ….

    };

    有必要说明一下, semget创建的是一个信号量集合, 所以semid_ds是针对这个信号量集合的. 而上面这个无名结构体是针对的一个信号量.

    好, 有了以上的一个union和一个struct, 我们引出semctl的定义.

    #include <sys/sem.h>

    int semctl (int semid, int semnum, int cmd, … /* union semun arg */);

    返回值: 有点复杂, 下面详细说明

    先说semctl和semun的关系.

    semctl的第四个参数为可选参数, 如果使用, 则应该为semun类型, 要注意的是semun必须显式的定义在用户的程序中. 具体用法和cmd有关系.

    再说semctl和无名结构体的关系. 具体用法还是和cmd有关系.

    所以, 我们必须介绍一下cmd的用法.

    cmd包括十种命令, 而针对的信号量用semnum指定. 范围为[0, nsems-1]

    IPC_STAT
    读取semid_ds到arg.buf指向的struct中

    IPC_SET
    将arg.buf指向的struct设置到sem_perm.uid, sem_perm.gid和sem_perm.mode

    IPC_RMID
    删除信号量集合

    GETVAL
    返回semnum信号量的无名结构体的成员semval值

    SETVAL
    arg.val设置到由semnum信号量的无名结构体成员semval中

    GETPID
    返回无名结构体成员sempid

    GETNCNT
    返回无名结构体成员semncnt

    GETZCNT
    返回无名结构体成员semzcnt

    GETALL
    取该集合中所有信号量(无名结构体)的值, 存放在arg.array指向的数组中

    SETALL
    将arg.array指向的数组的值设置该集合所有信号量的值(无名结构体)


    总结一下.

    围绕union semun来说:

    1.       int val由SETVAL使用, semctl将会把arg.val设置到信号量的semval中.

    2.       struct semid_ds *buf由IPC_STAT和IPC_SET使用, 读取时将信号量集合的semid_ds读取到arg.buf中. 设置时使用arg.buf的三项内容.

    3.       unsigned short *array由GETALL和SETALL使用, 将读取和设置集合中的所有信号量

    围绕无名结构体来说:

    1.       semval为R/W, 读取时直接为semop的返回值, 设置时将arg.val设置给semval

    2.       sempid, semncnt, semzcnt为只读, 读取时职位为semop的返回值.

    一句话概括. semctl读取和设置整个信号量集合, 读取和设置信号量集合中的每个信号量, 删除信号量集合

    注意. semget只是创建了信号量集合, 在使用之前必须使用semctl设置你要使用的信号量

    第三部分 sembuf

    semop的一个参数为struct sembuf类型, 定义如下:

    struct sembuf {

           unsigned short              sem_num;      /* member # in set [0, nsems-1] */

           short                     sem_op;         /* operation (negative, 0, or positive) */

           short                     sem_flg;         /* IPC_NOWAIT, SEM_UNDO */

    };

    #include <sys/sem.h>

    int semop (int semid, struct sembuf semoparray[], size_t nops);

    返回值: 若成功返回0, 若出错则返回

    参数nops规定该数组中操作的数量(元素数)

    先说明一下, 第二个参数之所以定义为当前形式, 而没有定义成struct sembuf *semoparray. 是因为semop可以执行数组中的nops个操作.

    整个semop围绕sembuf来进行不同的操作.

    成员sem_num指定了信号量

    成员sem_op和sem_flg联合指定了semop的行为. 根据书上的描述, 如下:

    1.       sem_op为正, 则将sem_op加到semval上.

    2.       sem_op为负,
    semval大于或等于sem_op的绝对值, 则从semval减去sem_op的绝对值.
    semval小于sem_op的绝对值, 因为信号量为非负值, 则根据sem_flg有如下行为:
    (a) 设定了IPC_NOWAIT, 则semop返回EAGAIN.
    (b) 没设定IPC_NOWAIT, 则semncnt值加1, 然后进程挂起直到下列时间之一发生.
        (i) semval变成大于或等于sem_op的绝对值. semncnt值减一.
        (ii) 信号量被删除, semop返回EIDRM
        (iii) 进程捕获到一个信号, 并从信号处理程序返回.semncnt减1, semop返回EINTR.

    3.       sem_op为0的情况
    semval为0, 正常返回.
    semval非0, 则:

    (a) 指定了IPC_NOWAIT, sem_op返回EAGAIN
    (b) 未指定IPC_NOWAIT, semzcnt加1, 进程挂起, 直到下列事件之一发生
        (i) semval变为0, semzcnt减1
        (ii) 信号量被删除, semop返回EIDRM

    (iii) 进程捕获到一个信号, 并从信号处理程序返回,semzcnt减1,semop返回EINTR

    4.       如果设置了SEM_UNDO, 则在调用semop时, 对semval的操作将会记录到信号量调整值上, 当前进程退出后, 内核将按调整值对信号量进行处理. 但是如果使用带有SETVAL或SETALL的semctl设置某一信号量, 则在所有进程中, 该信号量的调整值都设置为0.

    书上说的很严谨, 但是带来的问题就是罗嗦. 其实我们简单的理解, 就是, semop用来获得和释放资源, 释放资源时比较简单, 获得资源时, 如果资源不足, 则根据IPC_NOWAIT标志, 挂起或者直接返回. 取消挂起有三个条件, 有足够的资源了, 信号量被删除了, 捕获并处理完了一个信号. 进程醒来后semop返回相应的值.

    一句话概括, semop根据semoparray进行nops个获取和释放资源的动作.

    第四部分 信号量和记录锁

    书中最后对比了信号量和记录锁. 给出的结论为如果只需锁一个资源, 并且不需要使用XSI信号量的所有花哨(fancy)的功能, 则宁可使用记录锁, 尽管记录锁比信号量耗时, 但是记录锁使用简单, 且进程终止时系统会处理任何遗留下来的锁.

  • 相关阅读:
    工厂模式简介
    设计模式
    idea的安装与配置及基本用法
    软件架构设计的七大原则
    C#设计模式开启闯关之路
    基础知识详解系列目录
    .Net Core2.2 使用 AutoMapper进行实体转换
    通俗易懂设计模式解析——解释器模式
    通俗易懂设计模式解析——备忘录模式
    通俗易懂设计模式解析——访问者模式
  • 原文地址:https://www.cnblogs.com/lixiaofei1987/p/3200729.html
Copyright © 2011-2022 走看看