关于System V 信号量

System V的信号量与Posix的信号量略有差异
在原有的二值信号量与计数信号量基础上, System V信号量多了一种计数 信号量集:
    一个或多个信号量(构成的集合), 其中每个都是计数信号量. 每个集合的信号量数存在一个限制.
所以, 一般我们谈论Posix信号量, 指的是单个信号量; System V信号量则表示多个信号量集



操作函数:
int semget(key_t key, int nsems, int semflg);
第二个参数指明集合中的信号量的数量, 如果是访问已存在的信号量, 那么值填为0. 在创建完一个信号量集后, 就不能再修改其中的信号量数量了
关于信号量的初始化, 这里必须注意的是:
    在函数的声明中, 并没有让我们指定信号量的初始值. 所以 初始值是未定义的
    因此, 我们就认为它并不初始化各个信号量. 若要设置信号量的值, 必须通过其他函数. 所以创建一个信号量与初始化它就需要两个函数来完成
这样一来, 我们只能如下解决:
    在调用semget时指定IPC_CREAT | IPC_EXCL标志, 成功返回表明该进程是创建者进程, 随后调用函数初始化; semget返回失败EEXIST就勉强承认已被初始化, 于是再次调用semget, 只是这次不再指定IPC_CREAT和IPC_EXCL标志了
    但是, 这就出现这竞争状态. 若是在创建者进程成功创建信号量之后,在调用初始化函数之前, 另一个进程突然也调用semget, 那么另一个进程得到的信号量仍旧是未初始化的
    不过有办法绕过这个问题, 在semget创建一个新的信号量时, semid_ds的sem_otime成员(该成员用于指示上一次操作的时间)被初始化为0的, 只有在semop成功调用时才被设置为新的值
    因此, 另一个进程在调用第二次semget成功返回后, 必须用IPC_STAT命令调用semctl获取sem_otime的值, 等待其变为非0值, 就可已断定信号量已被初始化过了
    所以, 这就要求信号量的创建者必须初始化它的值

int semop(int semid, struct sembuf *sops, unsigned nsops);
此函数用于操作信号量集中的每个信号量
最后一个参数指定sembuf数组中元素的个数
struct sembuf
{
  unsigned short int sem_num;   /* semaphore number */
  short int sem_op;             /* semaphore operation */
  short int sem_flg;            /* operation flag */
};
sem_num为sembuf数组中元素的下标, 0代表第一个元素, 1代表第二个...直到第nsems-1个(这里的nsems是semget的第二个参数)
每个信号量特定的操作是由sem_op来确定的, sem_op可以是负数, 正数, 0
    如果sem_op是正数, 其值就加到semval上, 对应与释放由某个信号量控制的资源
    如果sem_op为0, 那么调用着希望等到semval为0. 如果semval就是0, 那么立即返回
    如果为负数, 那么调用者希望等待到semval变为大于或等于sem_op的绝对值 , 对应与分配走资源


int semctl(int semid, int semnum, int cmd, ...);
对一个信号量进行各种操作
第二个参数指定信号量集中的某个成员(0, 1, ...)
第三个参数可选:
    
IPC_STAT :获取此信号量集合的 semid_ds 结构,存放在第四个参数 arg 的 buf 中;
    IPC_SET :通过 arg.buf 来设定信号量集相关联的 semid_ds 中信号量集合权限为 sem_perm 中的 uid , gid , mode 。
    IPC_RMID :从系统中删除该信号量集合。这种删除立即发生,仍在使用该信号量集的其他进程,在下次对该信号量集进行操作的时候,会发生错误并返回 EIDRM 。这和 POSIX 信号量是不一样的。 POSIX 信号量 sem_unlink 只是会立即删除信号量的在文件系统中的文件,而信号量的析构是在最后一个 sem_close 发生是进行的。
    GETVAL :返回第 semnum 个信号量的值;
    SETVAL :设置第 semnum 个信号量的值,该值由第四个参数 arg 中的 val 指定;
    GETPID :返回第 semnum 个信号量的 sempid ,最后一个操作的 pid ;
    GETNCNT :返回第 semnum 个信号量的 semncnt 。等待 semval 变为大于当前值的线程数;
    GETZCNT :返回第 semnum 个信号量的 semzcnt 。等待 semval 变为 0 的线程数。
    GETALL :去信号量集合中所有信号量的值,将结果存放到 arg 中的 array 所指向的数组。
    SETALL :按 arg.array 所指向的数组中的值,设置集合中所有信号量的值。
第四个参数:
    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 */
    };



简单的程序:
//创建
#define FILE_MODE    (S_IRUSR | S_IWUSR | S_IROTH)

int main(int ac, char *av[])
{
    int flag, sems, sid;
    if(ac < 3){
        fprintf(stderr, "Usage : semc <pathname> count\n");
        exit(1);
    }
    sems = atoi(av[2]);
    flag = FILE_MODE | IPC_CREAT | IPC_EXCL;
    sid = semget(ftok(av[1], 0), sems, flag);
    if(sid < 0){
        perror("semget error");
        exit(-1);
    }
    return 0;
}



//删除
int main(int ac, char *av[])
{
    if(ac == 1){
        fprintf(stderr, "Usage <pathname>\n");
        exit(1);
    }
    int semid = semget(ftok(av[1], 0), 0, 0);
    semctl(semid, 0, IPC_RMID);
    return 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 */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};
                                                          

int main(int ac, char *av[])
{
    int semid;
    struct semid_ds seminfo;
    union semun arg;
    unsigned short *ptr;

    if(ac < 2){
        fprintf(stderr, "Usage : setvalue <pathname> [values]");
        exit(-1);
    }
    semid = semget(ftok(av[1], 0), 0, 0);
    if(semid < 0){
        perror("semget error:");
        exit(-1);
    }

    arg.buf = &seminfo;
    if(semctl(semid, 0, IPC_STAT, arg) < 0){
        perror("semctl error");
        exit(-1);
    }
    unsigned short nsems = seminfo.sem_nsems;

    if(ac-2 != nsems){
        fprintf(stderr, "not enough values set to ...\n");
        exit(-1);
    }

    ptr = calloc(nsems, sizeof(unsigned short));
    int i=0;
    for(; i<nsems; i++)
        ptr[i] = atoi(av[i+2]);
    
    arg.array = ptr;
    semctl(semid, 0, SETALL, arg);
    return 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 */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO(Linux-specific) */
};
                                                          
//大体步骤就是从semid_ds中取出信号量集中信号量的数目, 之后分配足够的数组空间去接收这些值
int main(int ac, char *av[])
{
    int semid;
    struct semid_ds seminfo;
    union semun arg;
    unsigned short *ptr;

    if(ac < 2){
        fprintf(stderr, "Usage : setvalue <pathname> \n");
        exit(-1);
    }
    semid = semget(ftok(av[1], 0), 0, 0);
    if(semid < 0){
        perror("semget error:");
        exit(-1);
    }

    arg.buf = &seminfo;
    if(semctl(semid, 0, IPC_STAT, arg) < 0){
        perror("semctl error");
        exit(-1);
    }
    unsigned short nsems = seminfo.sem_nsems;

    ptr = calloc(nsems, sizeof(unsigned short));
    arg.array = ptr;
    semctl(semid, 0, GETALL, arg);
    int i;
    for(i=0; i<nsems; i++)
        printf("semval[%d] = %d\n", i, arg.array[i]);
    return 0;
}



//对信号量的操作
int main(int ac, char *av[])
{
    int c, ret;
    int flag = 0;
    while((c=getopt(ac, av, "nu")) != -1){
        switch(c){
        case 'n':
            flag |= IPC_NOWAIT;
            break;
        case 'u':
            flag |= SEM_UNDO;
            break;
        }
    }

    if(ac - optind < 2){
        fprintf(stderr, "Usage : semops [-n] [-u] <pathname> option\n");
        exit(-1);
    }

    int semid = semget(ftok(av[optind], 0), 0, 0);
    if(semid < 0){
        perror("semget error:");
        exit(-1);
    }
    optind ++;
    int nops = ac - optind;

    struct sembuf *ptr = calloc(nops, sizeof(struct sembuf));
    int i;
    for(i=0; i<nops; i++)
    {
        ptr[i].sem_num = i;
        ptr[i].sem_op = atoi(av[optind++]);
        ptr[i].sem_flg = flag;
    }

    ret = semop(semid, ptr, nops);
    if(ret < 0){
        perror("semop error");
        exit(-1);
    }
    return 0;
}


操作结果:
$ ./semr /path/to/file 3
$ ./setvalues /home/Ben/workspace/UNP2/Chapter11/used_for_ftok 1 2 3
$ ./getvalues /path/to/file
semval[0] = 1
semval[1] = 2
semval[2] = 3
$ ./semop /path/to/file -- -1 -2 -3
$ ./getvalues /path/to/file
semval[0] = 0
semval[1] = 0
semval[2] = 0
$ ./semop /path/to/file -- -1 -2 -3
semop error: Resource temporarily unavailable



最后放上一个书上的 文件锁例子:
#define LOCK_PATH  /xxx/xxx
#define MAX_TRIES  10

int semid, initflag;
struct sembuf postop, waitop;

void my_lock(int fd)
{
    int oflag, i;
    union smeun arg;
    struct semid_ds seminfo;

    if(initflag == 0){
        oflag = IPC_CREAT | IPC_EXCL | FILE_MODE;
        if((semid=semget(ftok(LOCK_PATH, 0), 1, oflag)) >= 0){
            arg.val = 1;
            semctl(semid, 0, SETVAL, arg);
        }
        else if(errno == EEXIST){
            semid = semget(ftok(LOCK_PATH, 0), 1, FILE_MODE);
            arg.buf = &seminfo;
            for(i=0; i<MAX_TRIES; i++)        //之前提到, 如果信号量没有被初始化,那么它的sem_otime变量为0. 我们要等到被初始化后才能进行操作, 但这里我们限制了尝试等待的次数
            {
                semctl(semid, 0, IPC_STAT, arg);
                if(arg.buf->sem_otime != 0)
                    goto init;
                sleep(1);
            }
            fprintf(stderr,"not be initialized\n");
        }
        else{
            perror("semget error");
            exit(-1);
        }
    init:
        initflag = 1;
        postop.sem_num = 0;            //只有一个信号量, 所以PV操作的都是下标为0的信号量
        postop.sem_op = 1;
        postop.sem_flg = SEM_UNDO;        //设置这个变量, 如果进程在持锁期间终止了, 那么内核会释放掉
        waitop.sem_num = 0;
        waitop.sem_op = -1;
        waitop.sem_flg = SEM_UNDO;
    }
    semop(semid, &waitop, 1);
}

void my_unlock(int fd)
{
    semop(semid, &postop, 1);
}

//不过, 书上,网上提到关于此信号量的一些讨论:
//对于SEM_UNDO来说, 内核记录信号是根据进程相关的,一个进程在lock的时候设置一个UNDO, 那么对应该进程的UNDO计数就多一个, unlock的时候设置一个UNDO, 那么计数就减少一个. 对于临界区互斥的应用来说, lock和unlock是在一个进程中完成的, UNDO确实可以发挥作用.
//然而, 如果是一个进程lock, 另一个进程unlock, 那么UNDO就不起作用了, 导致UNDO对单一进程而言, 只朝着一个方向发展, 最后必定超过内核限定的值, 最后出现ERANGE错误



Posix信号量与System V信号量之间的一些区别:
    System V信号量由一组值构成, 当指定应用到某个信号量集的一组信号量操作时, 要么所有的操作都执行, 要么一个都不执行
    可以应用到一个System V信号量集的每个成员的操作有3种 : 测试其值是否为0, 往其值加一个整数或是剪掉一个整数(值非负). 然而Posix信号量的操作只能是加一或是减一
    创建一个信号量集需要技巧, 因为创建集合后初始化其各个值需要两个操作, 可能导致竞争状态
    System V信号量提供"复旧"特性, 此特性保证在进程终止时逆转某个信号量的操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值