linux网络编程之System V 信号量(一):封装一个信号量集操作函数的工具

信号量的概念参见这里

与消息队列和共享内存一样,信号量集也有自己的数据结构:

struct semid_ds { struct ipc_perm sem_perm;  /* Ownership and permissions */ time_t     sem_otime; /* Last semop time */ time_t     sem_ctime; /* Last change time */ unsigned short  sem_nsems; /* No. of semaphores in set */ };

同样地,第一个条目也是共有的ipc 对象内核结构,剩下的是私有成员。

 Each semaphore in a semaphore set has the following associated values:            unsigned short  semval;   /* semaphore value */            unsigned short  semzcnt;  /* # waiting for zero */            unsigned short  semncnt;  /* # waiting for increase */            pid_t           sempid;   /* process that did last op */

即每一个在信号量集中的信号量都有上述4个相关的变量。

1、semval :当前某信号量的资源数目

2、semzcnt:当sem_op(见 struct sembuf)为0,且semop 函数没有设置IPC_NOWAIT 标志,且当前semval 不为0,此时semzcnt 会加1,表示等待这个信号量的资源变为0的进程数加1,且进程会阻塞等待直到4个事件其中一个发生,具体可man 2 semop 一下。

3、semncnt:当sem_op(见 struct sembuf)< 0,且semop 函数没有设置IPC_NOWAIT 标志,且当前semval < |sem_op| ,此时semncnt 会加1,表示等待这个信号量的资源增加的进程数加1,且进程会阻塞等待直到4个事件其中一个发生,具体可man 2 semop 一下。

4、当正确执行了semop 函数,则信号量集中的每个信号量的sempid 参数都被设置为改变此信号量的进程pid。

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

#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);

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

功能:用于控制信号量集 原型 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 删除信号量集

功能:用来创建和访问一个信号量集 原型 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 */            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。此时对多个信号量的操作是作为一个单元原子操作,要么全部执行,要么全部不执行。

下面来封装一个信号量集操作函数的工具:

semtool.c

#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;
}

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

    return ret;
}

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);
    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);
    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:");
    if (opt == '?')
        exit(EXIT_FAILURE);
    if (opt == -1)
    {
        usage();
        exit(EXIT_FAILURE);
    }

    key_t key = ftok(".", 's');
    int semid;
    switch (opt)
    {
    case 'c':
        sem_create(key);
        break;
    case 'p':
        semid = sem_open(key);
        sem_p(semid);
        sem_getval(semid);
        break;
    case '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;
    }

    return 0;
}

首先来介绍一个getopt 函数, int getopt(int argc, char * const argv[],const char *optstring);

可以解析命令行选项参数,前两个参数由main 函数传递,第三个参数是一个字符串集,即解析命令行参数看是否存在这些字符。如./semtool -s 3 则s

为选项,3为选项参数,optarg 是一个全局指针变量  extern char *optarg;  通过atoi(optarg) 可以获取数字3。

"cdpvs:gfm:" 表示选项s 和 m 后面可接参数,我们未使用一个while 循环去解析命令行参数,即这些选项只能同时出现一个,当未使用选项时打印输出

使用方法。

// optind: the index of first argument which has no option

// after getopt loop end: ./main -a xxx -b xxx ip port cnt    optind points to ip

// usually optind+1 <= argc when no option arguments needed.

// argc: the count of arguments include exe; agrv[0 ~ argc-1]

根据解析到的选项来调用不同的函数,这些函数内部都调用了原始的信号量集操作函数,参照函数解释都不难理解。

需要注意一点是,这里为了只创建一个信号量集,只对这个信号量集的信号量进行操作,在sem_create 中指定了IPC_EXCL 选项,

即当key 已存在时返回错误,不再创建信号量集,而我们使用了ftok 函数产生一个唯一的key,传入的参数一定,则每次产生的key 值

一样,当第二次次执行./semtool -c ,会返回file exist 的错误,当然先删除当前信号量集,再create 是可以的,此时虽然key 还是一样

的,但返回的semid 是不同的。

且这个唯一的信号量集中只有唯一的一个信号量,即0号信号量,我们只对这个信号量进行PV操作。

使用举例如下:

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s ------ Semaphore Arrays -------- key        semid      owner      perms      nsems      simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool  usage:

semtool -c

semtool -d

semtool -p

semtool -v

semtool -s <val>

semtool -g

semtool -f

semtool -m <mode>

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -c

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s ------ Semaphore Arrays -------- key         semid      owner      perms      nsems      0x730135db  98304      simba      666           1          simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -v

current val is 1 simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ 

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -v

current val is 1

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -s 3

value updated...

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -g

current val is 3

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -p

current val is 2

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -m 600

current permissions is 666

permissions updated...

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ./semtool -d

simba@ubuntu:~/Documents/code/linux_programming/UNP/system_v$ ipcs -s ------ Semaphore Arrays -------- key        semid      owner      perms      nsems     

因为我们在PV操作中指定了SEM_UNDO 选项,当进程退出时撤销操作,所以连续执行两次V操作后信号量的资源还是为0(创建后信号量默认资源为

0,不一定所有系统实现都会如此,应该显式地初始化为0)。通过-s 可以设置信号量的资源数。ipcs -s 输出中的nsems 表示信号量的个数,当前只有一个;./semtool -v 输出中的current value 表示这个信号量的资源数。

参考:《UNP》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏码匠的流水账

聊聊resilience4j的fallback

vavr-0.9.2-sources.jar!/io/vavr/control/Try.java

831
来自专栏奔跑的蛙牛技术博客

Java网络知识之socket

当我们一个client连接一个套接字时,当前线程会被阻塞直到建立连接或者超时为止 同样的当通过套接字读写数据时,当前线程也会被阻塞或者直到超时 对于这种阻塞...

934
来自专栏Java与Android技术栈

Kotlin Coroutines 笔记 (二)

协程虽然是微线程,但是并不会和某一个特定的线程绑定,它可以在A线程中执行,并经过某一个时刻的挂起(suspend),等下次调度到恢复执行的时候,很可能会在B线程...

631
来自专栏偏前端工程师的驿站

线程间通讯:WaitHandler使用实例及分析

实例效果: ? 1.点击“启动线程”会启动一个线程t每隔2秒在listbox上插入一条新记录。 2.点击“关闭线程”会停止线程t,但不是马上停止而是等待线程t当...

1695
来自专栏coder修行路

Java基础(四)线程快速了解

其实可以理解是java的压缩包 方便使用,只要在classpath设置jar路径即可 数据库驱动,ssh框架等都是以jar包体现的

920
来自专栏从零开始学自动化测试

python笔记7-多线程threading

前言 1.python环境2.7 2.threading模块系统自带 一、 单线程 1.平常写的代码都是按顺序挨个执行的,就好比吃火锅和哼小曲这两个行为事件,定...

2957
来自专栏钟绍威的专栏

模拟Executor策略的实现如何控制执行顺序?怎么限制最大同时开启线程的个数?为什么要有一个线程来将结束的线程移除出执行区?转移线程的时候要判断线程是否为空遍历线程的容器会抛出ConcurrentM

Executor作为现在线程的一个管理工具,就像管理线程的管理器一样,不用像以前一样,通过start来开启线程 Executor将提交线程与执行线程分离开来...

2646
来自专栏开发与安全

linux系统编程之管道(一):匿名管道和pipe函数

一、进程间通信 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程...

2100
来自专栏大数据-Hadoop、Spark

DataFrame常用操作

在spark-shell状态下查看sql内置函数: spark.sql("show functions").show(1000) 比如:SUBSTR(col...

3415
来自专栏王磊的博客

Thread线程的深刻理解和代理方法参数[有图有真相]

在这说的是Thread的基本用法,线程池ThreadPool在这就不说的,以前的blog有写,基本上两个用法都是相同的。基本用法和图,不需要的大鸟请绕行,谢谢!...

3128

扫码关注云+社区