《unix高级环境编程》进程间通信——信号量

        这里所介绍的信号量是一种计数信号量集,它是一个计数器,用于多进程对共享数据对象的访问。共享资源通常分为两类:一类是互斥共享资源,即任一时刻只允许一个进程访问该资源;另一类是同步共享资源,即同一时刻允许多个进程访问该资源;信号量是解决互斥共享资源的同步问题而引入的机制。

        当有进程要求使用共享资源时,需要执行以下操作:

  1. 系统首先要检测该资源的信号量;
  2. 若该资源的信号量值大于0,则进程可以使用该资源,此时,进程将该资源的信号量值减1;
  3. 若该资源的信号量值为0,则进程进入休眠状态,直到信号量值大于0时进程被唤醒,访问该资源;

       当进程不再使用由一个信号量控制的共享资源时,该信号量值增加1,如果此时有进程处于休眠状态等待此信号量,则该进程会被唤醒。

       每个信号量集都有一个与其相对应的结构,该结构定义如下:

/* Data structure describing a set of semaphores.  */
struct semid_ds
{
    struct ipc_perm sem_perm;   /* operation permission struct */
    struct sem *sem_base;       /* ptr to array of semaphores in set */
    unsigned short sem_nsems;   /* # of semaphores in set */
    time_t sem_otime;           /* last-semop() time */
    time_t sem_ctime;           /* last-change time */
};

/* Data structure describing each of semaphores.  */
struct sem
{
    unsigned short semval;  /* semaphore value, always >= 0 */
    pid_t          sempid;  /* pid for last successful semop(), SETVAL, SETALL */
    unsigned short semncnt; /* # processes awaiting semval > curval */
    unsigned short semzcnt; /* # processes awaiting semval == 0 */
};
      信号量集的结构图如下所示:

《unix高级环境编程》进程间通信——信号量_第1张图片


信号量集的创建与打开

信号量的创建函数定义如下:

/* Create a set of semaphores */
/* 函数功能:创建一个新的信号量或打开一个现有的信号量;
 * 返回值:若成功则返回信号量ID,若出错则返回-1;
 * 函数原型:
 */
#include <sys/sem.h>

int semget(key_t key, int nsems, int flag);
/*
 * 说明:
 * key是信号量集的键;
 * nsems表示信号量的个数;
 * flag用于表示调用函数的操作类型,也可用于设置信号量集的访问权限;
 * flag访问权限值:IPC_CREAT,IPC_EXCL以及IPC指定的权限位,若IPC_CREAT | IPC_EXCL,当信号量集返回出错,errno设为EEXIST;
 */
        当成功创建了一个新信号量集,则相应的 semid_ds 结构成员会被初始化为以下值,但是必须注意,信号量集中的每个信号量并没有被初始化,必须调用函数 semctl 对其进行初始化:

  1. sem_perm 结构的 uid 和 cuid 成员被置为调用进程的有效用户ID,gid 和 cgid 成员被置为调用进程的有效组ID。
  2. flag 参数中的读写权限位存入sem_perm.mode。
  3. sem_otime 被置为0,sem_ctime 则被置为当前时间。
  4. sem_nsems被置为nsems参数的值。
  5. 与该信号量集合中每个信号量关联的各个 sem 结构并不初始化。这些结构时在以 SET_VAL 或 SETALL 命令调用 semctl 时初始化的。

信号量的操作

       对信号量的操作,可以使用函数 semop 实现,具体实现如下:
/* 函数功能:对信号量进行操作;
 * 返回值:若成功则返回0,若出错则返回-1;
 * 函数原型:
 */
#include <sys/sem.h>
int semop(int semid, struct sembuf semoparray[], size_t nops);
/*
 * 说明:
 * semid是信号量的引用ID;
 * nops是semoparray数组元素的个数;
 * semoparray是一个指针,指向信号量操作数组;
 */
       semoparray 是一个指向数组的指针,数组每个元素表示一个操作,由于此函数是一个原子操作,一旦执行就将执行数组中的所有操作。其中 sembuf 结构定义如下:
struct sembuf
{
    unsigned short sem_num; /* number # in set (0,1,..., nsems-1) */
    short          sem_op;  /* operation (negative, 0, or positive) */
    short          sem_fig; /* IPC_NOWAIT, SEM_UNDO */
}
每个信号量成员操作 sem_op 不同的取值,会执行不同的操作,下面是不同取值对应的操作:
  1. 若 sem_op 大于0,表示进程对资源使用完毕,交回该资源,即对应进程释放占用的资源数;此时信号量集 semid_ds 结构成员 sem_base.semval 将加上 sem_op 的值,即信号量值增加 sem_op;若此时,信号量指定了标志 sem_flg 为 SEM_UNDO 时,则信号量调整值减去 sem_op 的绝对值。
  2. 若 sem_op 等于0,则表示进程等待,直到信号量值 sem_base.semval 变为0。
  3. 若 sem_op 小于0,表示希望使用由该信号量控制的资源,此时需比较 sem_base.semval 和  sem_op 的绝对值的大小;
    • 如果 sem_base.semval 大于等于  sem_op 的绝对值,说明有足够的资源给进程使用,则从信号量值 sem_base.semval 减去 sem_op 的绝对值。若此时,信号量指定了标志 sem_flg 为 SEM_UNDO 时,则信号量调整值加上 sem_op 的绝对值。
    • 如果 sem_base.semval 小于  sem_op 的绝对值,表示资源不够进程使用,若信号量指定了标志 sem_flg 为 IPC_NOWAIT 时,则该函数出错返回 EAGAIN;否则 semid_ds 结构成员 sem_base.semncnt 将加1,进程等待直到 sem_base.semval 大于等于  sem_op 的绝对值或该信号量被删除。

信号量的控制

        对信号量的具体控制可以通过函数 semctl 来实现,该函数定义如下:
/* 函数功能:对信号量进行控制;
 * 函数原型:
 */
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, .../* union semun arg */);
/*
 * 该函数的返回依赖与参数cmd;
 * semid是信号量集的ID;
 * semnum是指定某个特定的信号量;
 * cmd是希望该函数执行的操作;
 */
其中联合体 semun 参数结构如下:
union semun
{
    int val;                /* for SETVAL */
    struct semid_ds *buf;   /* for IPC_STAT and IPC_SET */
    unsigned short *array;  /* for GETALL and SETALL */
};

该函数的 cmd 参数可以取如下值:
  1. IPC_STAT:获取此信号量集合的 semid_ds 结构,保存在 arg.buf 所指向的缓冲区中;
  2. IPC_SET:按参数 arg.buf 指向的结构中的值来设定该信号量对应的 semid_ds 结构中 sem_perm 中的uid,gid,mode;
  3. IPC_RMID:从系统中删除该信号量集合。这种删除立即发生,仍在使用该信号量集的其他进程,在下次对该信号量集进行操作的时候,会发生错误并返回 EIDRM;
  4. GETVAL:获取 semid 所表示的信号量集的 semnum 所指定信号量的值;
  5. SETVAL:设置获取 semid 所表示的信号量集的 semnum 所指定信号量的值,该值由第四个参数 arg 中的 val 指定;
  6. GETPID:获取 semid 所表示的信号量集中最后一个操作 semop 函数的进程ID,即 semid_ds 结构中的 sem.sempid的值;
  7. GETNCNT:获取 semid 所表示的信号量集中的等待给定信号量锁的进程数目,即 semid_ds 结构中的 sem.semncnt 的值;
  8. GETZCNT:获取 semid 所表示的信号量集中的等待信号量成为0的进程数目,即 semid_ds 结构中的 sem.semzcnt 的值;
  9. GETALL:获取 semid 所表示的信号量集中信号量的个数,并保存在 arg.array 中;
  10. SETALL:按 arg.array 所指向的数组中的值,设置集合中信号量的个数;

测试程序:

#include "apue.h"
#include <sys/sem.h>
#include <fcntl.h>
#include <sys/ipc.h>

//#define PATH_NAME "./Sem"
key_t MakeKey(const char *pathname);

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};    //信号量值
int main(int argc, char *argv[])
{
    int semid, nsems, i;
    key_t key;
    struct semid_ds seminfo;
    unsigned short *ptr;
    union semun arg;
    if(argc != 2)
        err_quit("usage: a.out <pathname>");

    key = MakeKey(argv[1]);
    if((semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666)) >= 0)
    {
        arg.val = 2;
        semctl(semid, 0, SETVAL, arg);
    }
    else if(errno == EEXIST)
    {
        semid = semget(key, 1, 0666);
        arg.val = 4;/* 设置信号量值*/
        semctl(semid, 0, SETVAL, arg);
    }
    else
        err_quit("semget error");
    arg.buf = &seminfo;
    /* 对信号量集取ipc_perm结构,并保存在array.buf所指的缓冲区中 */
    if(semctl(semid, 0, IPC_STAT, arg) < 0)
        err_quit("semctl error");
    nsems = arg.buf->sem_nsems;/* 获取信号量的个数 */

    ptr = (unsigned short *)calloc(nsems, sizeof(unsigned short));
    arg.array = ptr;

    /* 获取信号量集中信号量的值,并保存在arg.array所指向的数组中,即保存在ptr所指的地址 */
    semctl(semid, 0, GETALL, arg);
    for(i = 0; i < nsems; i++)
        printf("semval[%d] = %d\n", i, ptr[i]);

    exit(0);
}
key_t MakeKey(const char *pathname)
{
    int fd;
    if((fd = open(pathname, O_CREAT, 0666)) < 0)
        err_quit("open error");
    close(fd);

    return ftok(pathname, 0);
}

输出结果:

./sem Sem
semval[0] = 4


参考资料:

《UNIX高级环境编程》

你可能感兴趣的:(信号量)