System V 信号量(一)

时间:2022-04-07 15:17:32

一.信号量

    信号量和P,V原语由Dijkstra提出
    信号量
          互斥:p,v在同一个进程中
          同步:p.v在不同进程中
    信号量值含义
          s>0:s表示可用资源的个数
          s=0:表示无可用资源,无等待进程
          s<0:|s|表示等待队列中进程个数

    struct semaphore
    {
        int value;  
//计数值
        pointer_PCB queue;   //队列
    }
    
    P原语----申请资源
        P(s)
        {
            s.value--;
            if(s.value <0)
            {
                该进程状态置为等待状态
                将该进程的PCB插入相应的等待队列s.queue末尾
            }
        }

    V原语--- 归还资源
        V(s)
        {
            s.value++;
            if(s.value <= 0)
            {
                唤醒相应等待队列s.queue中等待的一个进程
                改变其状态为就绪态
                并将其插入就绪队列
            }
        }

二,信号量集结构

     对于系统的每个信号量集,内核维护一个如下的信息结构,定义在<sys/sem.h>头文件中,ubuntu中的路径:/usr/include/linux/sem.h

System V 信号量(一)

下图中变量sem_nsems的值为2,另外该集合的两个成员分别用下标[0]和[1]标记

System V 信号量(一)

三.信号量集函数


以下是几个信号量集操作函数:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semctl(int semid, int semnum, int cmd, ...);
int semop(int semid, struct sembuf *sops, unsigned nsops);


(1)
功能:用来创建和访问一个信号量集
原型 int semget(key_t key, int nsems, int semflg);
参数
    key: 信号量集的名字
    nsems:信号量集中信号量的个数
    semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号量集的标识码;失败返回-1

(2)
功能:用于控制信号量集
原型 int semctl(int semid, int semnum, int cmd, ...);
参数
    semid:由semget返回的信号量集标识码
    semnum:信号量集中信号量的序号,从0开始编号
    cmd:将要采取的动作(有三个可取值)
    最后一个参数是 union semun,具体成员根据cmd 的不同而不同

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) */
           };
返回值:成功返回0;失败返回-1

cmd 取值如下:

SETVAL    设置信号量集中的信号量的计数值
GETVAL    获取信号量集中的信号量的计数值
IPC_STAT  把semid_ds结构中的数据设置为信号量集的当前关联值
IPC_SET    在进程有足够权限的前提下,把信号量集的当前关联值设置为semid_ds数据结构中给出的值
IPC_RMID  删除信号量集

(3)
功能:用来创建和访问一个信号量集(PV操作)
原型 int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
    semid: 是该信号量集的标识码,也就是semget函数的返回值
    sops:  是个指向一个结构体的指针
    nsops: 信号量的个数
返回值:成功返回0;失败返回-1

struct sembuf
{
    unsigned short sem_num;  // semaphore number    信号量编号,操作哪个信号量
    short          sem_op;   // semaphore operation 操作方式 , PV操作
    short          sem_flg;  // operation flags  ,一般为不等待和撤销两种标志  
};


sem_num:是信号量的编号。
sem_op:是信号量一次PV操作时加减的数值,一般只会用到两个值,一个是“-1”,也就是P操作,等待信号量变得可用;另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用。当然+-n 和0 都是允许的。需要注意的是只有+n 才确保将semval +n 后马上返回,而-n 和 0 很可能是会阻塞的,+-n 需要进程对信号量集有写的权限,而0 只需要读的权限
sem_flag:的两个取值是IPC_NOWAIT或SEM_UNDO,设为前者如果当某个信号量的资源为0时进行P操作,此时不会阻塞等待,而是直接返回资源不可用的错误;设为后者,当退出进程时对信号量资源的操作撤销;不关心时设置为0即可
当要对一个信号量集中的多个信号量进行操作时,sops 是结构体数组的指针,此时nsops 不为1。此时对多个信号量的操作是作为一个单元原子操作,要么全部执行,要么全部不执行。

四.信号量示例

#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

union semun
{
    int val;                  /* value for SETVAL */
    struct semid_ds *buf;     /* buffer for IPC_STAT, IPC_SET */
    unsigned short *array;    /* array for GETALL, SETALL */
    /* Linux specific part: */
    struct seminfo *__buf;    /* buffer for IPC_INFO */
};

//生成信号量
int sem_create(key_t key)
{
    int semid = semget(key, 1, 0666 | IPC_CREAT | IPC_EXCL);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

//打开信号量
int sem_open(key_t key)
{
    int semid = semget(key, 0, 0);
    if (semid == -1)
        ERR_EXIT("semget");

    return semid;
}

//P操作
int sem_p(int semid)
{
    struct sembuf sb = {0, -1, 0/*IPC_NOWAIT*//*SEM_UNDO*/};
    int ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");

    return ret;
}

// V操作
int sem_v(int semid)
{
    struct sembuf sb = {0, 1, 0 /*SEM_UNDO*/};
    int ret = semop(semid, &sb, 1);
    if (ret == -1)
        ERR_EXIT("semop");

    return ret;
}

// 删除信号量集
int sem_d(int semid)
{
    int ret = semctl(semid, 0, IPC_RMID, 0);
    if (ret == -1)
        ERR_EXIT("semctl");
    return ret;
}

//设置信号量集中的信号量的计数值
int sem_setval(int semid, int val)
{
    union semun su;
    su.val = val;
    int ret = semctl(semid, 0, SETVAL, su);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("value updated...\n");
    return ret;
}

//获取信号量集中的信号量的计数值
int sem_getval(int semid)
{
    int ret = semctl(semid, 0, GETVAL, 0);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("current val is %d\n", ret);
    return ret;
}

int sem_getmode(int semid)
{
    union semun su;
    struct semid_ds sem;
    su.buf = &sem;
    int ret = semctl(semid, 0, IPC_STAT, su);  // su为一个输出参数
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("current permissions is %o\n", su.buf->sem_perm.mode);  // 有两个嵌套的结构体
    return ret;
}

int sem_setmode(int semid, char *mode)
{
    union semun su;
    struct semid_ds sem;
    su.buf = &sem;

    int ret = semctl(semid, 0, IPC_STAT, su);
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("current permissions is %o\n", su.buf->sem_perm.mode);
    sscanf(mode, "%o", (unsigned int *)&su.buf->sem_perm.mode);  //格式化输入
    //在进程有足够权限的前提下,把信号量集的当前关联值设置为semid_ds数据结构中给出的值
    ret = semctl(semid, 0, IPC_SET, su); 
    if (ret == -1)
        ERR_EXIT("semctl");

    printf("permissions updated...\n");

    return ret;
}

void usage(void)
{
    fprintf(stderr, "usage:\n");
    fprintf(stderr, "semtool -c\n");
    fprintf(stderr, "semtool -d\n");
    fprintf(stderr, "semtool -p\n");
    fprintf(stderr, "semtool -v\n");
    fprintf(stderr, "semtool -s <val>\n");
    fprintf(stderr, "semtool -g\n");
    fprintf(stderr, "semtool -f\n");
    fprintf(stderr, "semtool -m <mode>\n");
}

int main(int argc, char *argv[])
{
    int opt;

    opt = getopt(argc, argv, "cdpvs:gfm:");  // 解析参数,-s,-m根参数
    if (opt == '?')
        exit(EXIT_FAILURE);
    if (opt == -1)
    {
        usage();
        exit(EXIT_FAILURE);
    }

    key_t key = ftok(".", 's');  // 生成关键字,低序8位非零
    int semid;
    switch (opt)
    {
    case 'c':  //创建
        sem_create(key);
        break;
    case 'p':  // p操作
        semid = sem_open(key);
        sem_p(semid);
        sem_getval(semid);
        break;
    case 'v':  // V操作
        semid = sem_open(key);
        sem_v(semid);
        sem_getval(semid);
        break;
    case 'd':  //删除操作
        semid = sem_open(key);
        sem_d(semid);
        break;
    case 's':  // 设置信号量 ,需要参数
        semid = sem_open(key);
        sem_setval(semid, atoi(optarg));
        break;
    case 'g':  // 获取信号量计数值
        semid = sem_open(key);
        sem_getval(semid);
        break;
    case 'f': // 获取权限
        semid = sem_open(key);
        sem_getmode(semid);
        break;
    case 'm':  // 设置权限,需要参数
        semid = sem_open(key);
        sem_setmode(semid, argv[2]);
        break;<span style="font-size:18px;"><strong>当要对一个信号量集中的多个信号量进行操作时,sops 是结构体数组的指针,此时nsops 不为1。此时对多个信号量的操作是作为一个单元原子操作,要么全部执行,要么全部不执行。</strong></span>
    }

    return 0;
}

System V 信号量(一)