linux学习:进程通信(信号量)

目录

信号量类别

作用

基本概念

 api semget

api semop

api semctl

例子


信号量跟前面的 MSG 和 SHM 有极大的不同,SEM 不是用来传输数据的, 而是作为“旗语”,用来协调各进程或者线程工作的

信号量类别

分为三种:ststem-V信号量,POSIX有名信号量和POSIX无名信号量

作用

用来表征一种资源的数量,当多 个进程或者线程争夺这些稀缺资源的时候,信号量用来保证他们合理 地、秩序地使用这些资源,而不会陷入逻辑谬误之中

基本概念

  • 多个进程或线程有可能同时访问的资源(变量、链表、文件等等)称为共享资源,也叫临界资源
  • 访问这些资源的代码称为临界代码,这些代码区域称为临界区
  • 程序进入临界区之前必须要对资源进行申请,这个动作被称为 P 操作,这就像你要把车开进停车场之前,先要向保安申请一张停车卡一样,P 操作就是申请资源,如果申请成功,资源数将会减少。如果申请失败,要不在门口等,要不走人
  • 程序离开临界区之后必须要释放相应的资源,这个动作被称为 V 操作,这就像你把车开出停车场之后,要将停车卡归还给保安一样,V 操作就是释放资源,释放资源就是让资源数增加

 api semget

注意  在/proc/sys/kernel/sem 中可查看

  • SEMMNI:系统中信号量的总数最大值
  • SEMMSL:每个信号量中信号量元素的个数最大值
  • SEMMNS:系统中所有信号量中的信号量元素的总数最大值

api semop

注意

  • 信号量操作结构体
    struct sembuf
    {
        unsigned short sem_num; /* 信号量元素序号(数组下标) */
        short sem_op; /* 操作参数 */
        short sem_flg; /* 操作选项 */
    };

    信号量元素的序号从 0 开始,实际上就是数组下标

  • 根据 sem_op 的数值,信号量操作分成 3 种情况
    • 当 sem_op 大于 0 时:进行 V 操作,即信号量元素的值(semval)将会被加上 sem_op 的值。如果 SEM_UNDO 被设置了,那么该 V 操作将会被系统记录。V 操作永远不会导致进程阻塞
    • 当sem_op等于0时:进行等零操作,如果此时semval恰好为0,则semop( )立即成功返回,否则如果 IPC_NOWAIT 被设置,则立即出错返回并将 errno 设置为EAGAIN,否则将使得进程进入睡眠,直到以下情况发生
      • semval 变为 0
      • 信号量被删除。(将导致 semop( )出错退出错误码为 EIDRM)
      • 收到信号。(将导致 semop( )出错退出,错误码为 EINTR)
    • 当 sem_op 小于 0 时:进行 P 操作,即信号量元素的值(semval)将会被减 去 sem_op 的绝对值。如果 semval 大于或等于 sem_op 的绝对值,则 semop( )立 即成功返回,semval 的值将减去 sem_op 的绝对值,并且如果 SEM_UNDO 被设置 了,那么该 P 操作将会被系统记录。如果 semval 小于 sem_op 的绝对值并且设置了 IPC_NOWAIT,那么 semop( )将会出错返回且将错误码置为 EAGAIN,否则将使得 进程进入睡眠,直到以下情况发生
      • semval 的值变得大于或者等于 sem_op 的绝对值。
      • 信号量被删除。(将导致semop( )出错退出,错误码为 EIDRM)
      • 收到信号。(将导致semop( )出错退出,错误码为 EINTR)

api semctl

注意

  • 这是一个变参函数,根据 cmd 的不同,可能需要第四个参数,第四个参数是一个 如下所示的联合体,用户必须自己定义
    union semun
    {
        int val; /* 当 cmd 为 SETVAL 时使用 */
        struct semid_ds *buf; /* 当 cmd 为 IPC_STAT 或 IPC_SET 时使用 */
        unsigned short *array; /* 当 cmd 为 GETALL 或 SETALL 时使用 */
        struct seminfo *__buf;/* 当 cmd 为 IPC_INFO 时使用 */
    };
    
  • 使用 IPC_STAT 和 IPC_SET 需要用到以下属性信息结构体
    struct semid_ds
    {
        struct ipc_perm sem_perm; /* 权限相关信息 */
        time_t sem_otime; /* 最后一次 semop( )的时间 */
        time_t sem_ctime; /* 最后一次状态改变时间 */
        unsigned short sem_nsems; /* 信号量元素个数 */
    };

    权限结构体

    struct ipc_perm
    {
        key_t __key; /* 该信号量的键值 key */
        uid_t uid; /* 所有者有效 UID */
        gid_t gid; /* 所有者有效 GID */
        uid_t cuid; /* 创建者有效 UID */
        gid_t cgid; /* 创建者有效 GID */
        unsigned short mode; /* 读写权限 */
        unsigned short __seq; /* 序列号 */
    };
  • 使用 IPC_INFO 时,需要提供以下结构体:
    struct seminfo
    {
        int semmap; /* 当前系统信号量总数 */
        int semmni; /* 系统信号量个数最大值 */
        int semmns; /* 系统所有信号量元素总数最大值 */
        int semmnu; /* 信号量操作撤销结构体个数最大值 */
        int semmsl; /* 单个信号量中的信号量元素个数最大值 */
        int semopm; /* 调用 semop( )时操作的信号量元素个数最大值 */
        int semume; /* 单个进程对信号量执行连续撤销操作次数的最大值 */
        int semusz; /* 撤销操作的结构体的尺寸 */
        int semvmx; /* 信号量元素的值的最大值 */
        int semaem; /* 撤销操作记录个数最大值 */
    };
  • 使用 SEM_INFO 时,跟 IPC_INFO 一样都是得到一个 seminfo 结构体,但其中 几个成员的含义发生了变化
    • semusz 此时代表系统当前存在的信号量的个数
    • semaem 此时代表系统当前存在的信号量中信号量元素的总数

例子

Jack 需要写数据,Rose 需要读数据,所以对于 Jack 而言内 存空间是资源——有这个资源才能写数据,同理对于 Rose 而言内存中的数据是资源——有 了这个资源才能读数据,而且 Jack 一旦写了数据,内存空间资源就减少了,数据资源就增 加了,当 Rose 读取数据的时候,情况恰好相反。 为了协调他们,使用两个信号量元素,来分别表示“内存空间”和“数据”这两种资源, 在刚开始的时候,“内存空间”的可用数目是 1(假设将整块 SHM 当做一个资源),而“数 据”的可用数据是 0(刚开始啥也没有),根据访问临界资源的一般原则

照上图所示,两个信号量元素就好像两盏红绿灯,有效地协调了双方的读写操作—— Jack 不会重复写入而把尚未读出的数据覆盖,Rose 也不会将一个数据重复读取多次,每 当他们申请相应的资源而不可获得时,信号量会自动使得他们睡眠

head4sem.h

1 #ifndef _HEAD4SEM_H_
2 #define _HEAD4SEM_H_
3
4 #include <stdio.h>
5 #include <stdlib.h>
6 #include <unistd.h>
7 #include <errno.h>
8 #include <string.h>
9 #include <strings.h>
10
11 #include <sys/stat.h>
12 #include <sys/ipc.h>
13 #include <sys/shm.h>
14 #include <sys/sem.h>
15
16 #define SHMSZ 128
17
18 #define PROJ_PATH "." // 用以产生键值 key
19 #define ID4SHM 1
20 #define ID4SEM 2
21
22 union semun // 自定义的信号量操作联合体
23 {
24     int val;
25     struct semid_ds *buf;
26     unsigned short *array;
27     struct seminfo *__buf;
28 };
29
30 static void sem_p(int semid, int semnum) // P 操作
31 {
32     struct sembuf op[1];
33     op[0].sem_num = semnum;
34     op[0].sem_op = -1;
35     op[0].sem_flg = 0;
36
37     semop(semid, op, 1);// PV操作
38 }
39
40 static void sem_v(int semid, int semnum) // V 操作
41 {
42     struct sembuf op[1];
43     op[0].sem_num = semnum;
44     op[0].sem_op = 1;
45     op[0].sem_flg = 0;
46
47     semop(semid, op, 1);// PV操作
48 }
49
50 static void seminit(int semid, int semnum, int value) // 初始化
51 {
52     union semun a;
53     a.val = value;
54     semctl(semid, semnum, SETVAL, a); //设置设置该信号量元素的值
55 }
56
57 #endif

Jack.c

1 #include "head4sem.h" 
2
3 int main(int argc, char **argv)
4 {
5     key_t key1 = ftok(PROJ_PATH, ID4SHM); // 获取 SHM 对应的键共享内存
6     key_t key2 = ftok(PROJ_PATH, ID4SEM); // 获取 SEM 对应的键信号量
7
8     // 获取 SHM 的 ID,并将之映射到本进程虚拟内存空间中
9     int shmid = shmget(key1, SHMSZ, IPC_CREAT|0644);
10     char *shmaddr = shmat(shmid, NULL, 0);
11
12     // 获取 SEM 的 ID,若新建则初始化之,否则直接获取其 ID
13     int semid = semget(key2, 2, IPC_CREAT|IPC_EXCL|0644);
14     if(semid == -1 && errno == EEXIST)
15     {
16         semid = semget(key2, 2, 0644); // 直接获取 SEM 的 ID
17     }
18     else
19     {
20         seminit(semid, 0, 0); // 将第 0 个元素初始化为 0,代表数据
21         seminit(semid, 1, 1); // 将第 1 个元素初始化为 1,代表空间
22     }
23
24     while(1)
25     {
26         sem_p(semid, 1); // 向第 1 个信号量元素申请内存空间资源
27         fgets(shmaddr, SHMSZ, stdin); // 从标准输入读取一行数据,并存储到共享内存中
28         sem_v(semid, 0); // 增加代表数据资源的第0个信号量元素的值
29     }
30
31     return 0;
32 }

Rose.c

1 #include "head4sem.h" 
2
3 int main(int argc, char **argv)
4 {
5     key_t key1 = ftok(PROJ_PATH, ID4SHM);
6     key_t key2 = ftok(PROJ_PATH, ID4SEM);
7
8     int shmid = shmget(key1, SHMSZ, IPC_CREAT|0644);
9     char *shmaddr = shmat(shmid, NULL, 0);
10
11     int semid = semget(key2, 2, IPC_CREAT|IPC_EXCL|0644);
12     if(semid == -1 && errno == EEXIST)
13     {
14         semid = semget(key2, 2, 0644);
15     }
16     else
17     {
18         seminit(semid, 0, 0);
19         seminit(semid, 1, 1);
20     }
21
22     while(1)
23     {
24         sem_p(semid, 0); // 向第 0 个信号量元素申请数据资源
25         printf("from Jack: %s", shmaddr);
26         sem_v(semid, 1); // 增加代表空间资源的第1个信号量元素的值
27     }
28
29     return 0;
30 }

  • 19
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值