• 五十一、进程间通信——System V IPC 之进程信号量


    51.1 进程信号量

    51.1.1 信号量

    • 本质上就是共享资源的数目,用来控制对共享资源的访问
    • 用于进程间的互斥和同步
    • 每种共享资源对应一个信号量,为了便于大量共享资源的操作引入了信号量集,可对所有信号量一次性操作。对信号量集中所有操作可以要求全部成功,也可以部分成功
    • 二元信号量(信号灯)值为 0 和 1
    • 对信号量做 PV 操作2

    51.1.2 信号量集属性

      

    51.1.3 创建信号量集

      

    • 函数参数:
      • key:用户指定的信号量集键值
      • nsems:信号量集中信号量个数
      • semflg:IPC_CREAT,IPC_EXCL 等权限组合
    • 返回值:成功,返回信号量集 ID,出错,返回 -1

    51.1.4 信号量集控制

      

      

      

      

    • 函数参数:
      • semid:信号量集 ID
      • semnum:0 表示对所有信号量操作,信号量编号从 0 开始
      • cmd:控制命令,通过 cmd 参数设定对信号量集要执行的操作
        • IPC_STAT:获取信号量集的属性    ---> buf
        • IPC_SET:设置信号量集的属性      ---> buf
        • IPC_RMID:删除信号量集               ---> buf
        • GETVAL:返回信号量的值               ---> val
        • SETVAL:设置 semnum 信号量的值 ---> val
        • GETALL:获取所有信号量的值           ---> arryr
        • SETALL:设置所有信号量的初始值  ---> array
      • arg:即 ... ,semun 联合体变量
        • val:放置获取或设置信号量集中某个信号量的值
        • buf:信号量集属性指针
        • array:放置获取或设置信号量集中所有信号量的值

    51.1.5 信号量集操作

      

      

    • 函数参数:
      • semid:信号集 ID
      • sops:sembuf 结构体数组指针
        • sem_num:信号集中信号量的编号
        • sem_op:正数为 V 操作,负数为 P 操作,0 可用于对共享资源是否已用完的测试
        • sem_flg:SEM_UNDO 标识,表示在进程结束时,相应的操作将被取消。如果设置了该标志,那么在进程没有释放共享资源就退出时,内核将代为释放
      • nsops:第二个参数中结构体数组的长度
    • 返回值:成功返回 0;出错返回 -1
    • 其他说明:
      • 用于信号量集中信号量的加和减操作(PV 操作)
      • 可用于进程间的互斥或同步

     51.2 信号量例子

     51.2.1 PV 操作

    (1)PV模块

      sem_pv.h

     1 #ifndef INCLUDE_SEM_PV_H_
     2 #define INCLUDE_SEM_PV_H_
     3 
     4 #include <sys/sem.h>
     5 #include <stdio.h>
     6 #include <stdlib.h>
     7 #include <assert.h>
     8 #include <malloc.h>
     9 
    10 union semun {
    11     int             val;
    12     struct semid_ds    *buf;
    13     unsigned short    *array;
    14 };
    15 
    16 /** 初始化 semnums 个信号灯/信号量值(value) */
    17 extern int sem_I(int semnums, int value);
    18 
    19 /** 对信号量集(semid)中的信号灯(semnum)作 P() */
    20 extern void sem_P(int semid, int semnum, int value);
    21 
    22 /** 对信号集(semid) 中的信号灯(semnum)作V(value)操作 */
    23 extern void sem_V(int semid, int semnum, int value);
    24 
    25 /** 销毁信号量集(semid) */
    26 extern void sem_D(int semid);
    27 
    28 #endif /* INCLUDE_SEM_PV_H_ */

      sem_pv.c

     1 #include "sem_pv.h"
     2 
     3 /** 初始化 semnums 个信号灯/信号量值(value) */
     4 int sem_I(int semnums, int value)
     5 {
     6     /** 创建信号量集 */
     7     int semid;
     8     /** 创建信号量集 */
     9     semid = semget(IPC_PRIVATE, semnums, IPC_CREAT | IPC_EXCL | 0777);
    10     if(semid < 0){
    11         return -1;
    12     }
    13 
    14     union semun un;
    15     unsigned short *array = (unsigned short *)calloc(semnums, sizeof(unsigned short));
    16     int i;
    17     for(i = 0; i < semnums; i++){
    18         array[i] = value;
    19     }
    20     un.array = array;
    21 
    22     /**
    23      *  初始化信号量集中所有信号灯的初值
    24      *  0: 表示要初始化所有的信号灯
    25      */
    26     if(semctl(semid, 0, SETALL, un) < 0){
    27         perror("semctl error");
    28         return -1;
    29     }
    30     free(array);
    31     return semid;
    32 }
    33 
    34 /** 对信号量集(semid)中的信号灯(semnum)作 P() */
    35 void sem_P(int semid, int semnum, int value)
    36 {
    37     assert(value >= 0);
    38 
    39     /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
    40     struct sembuf ops[] = {{semnum, -value, SEM_UNDO}};
    41     if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < 0){
    42         perror("semop error");
    43     }
    44 }
    45 
    46 /** 对信号集(semid) 中的信号灯(semnum)作V(value)操作 */
    47 void sem_V(int semid, int semnum, int value)
    48 {
    49     assert(value >= 0);
    50 
    51     /** 定义 sembuf 类型的结构体数组,放置若干个结构体变量,对应要操作的信号量、P或V操作 */
    52     struct sembuf ops[] = {{semnum, value, SEM_UNDO}};
    53     if(semop(semid, ops, sizeof(ops)/sizeof(struct sembuf)) < 0){
    54         perror("semop error");
    55     }
    56 }
    57 
    58 /** 销毁信号量集(semid) */
    59 void sem_D(int semid)
    60 {
    61     if(semctl(semid, 0, IPC_RMID, NULL) < 0){
    62         perror("semctl error");
    63     }
    64 }

      编译:

      gcc -o obj/sem_pv.o -Iinclude -c src/sem_pv.c

    (2)互斥操作

      

      

      atm_account.h

     1 #ifndef INCLUDE_ATM_ACCOUNT_H_
     2 #define INCLUDE_ATM_ACCOUNT_H_
     3 
     4 #include <malloc.h>
     5 #include <assert.h>
     6 #include <string.h>
     7 
     8 
     9 typedef struct {
    10     int     code;
    11     double    balance;
    12     int     semid;    ///< 在共享资源上绑定一个信号量集
    13 }atm_account;
    14 
    15 /** 取款 */
    16 extern double atm_account_withdraw(atm_account *a, double amt);
    17 
    18 /** 存款 */
    19 extern double atm_account_deposit(atm_account *a, double amt);
    20 
    21 /** 查看账户余额度 */
    22 extern double amt_account_balanceGet(atm_account *a);
    23 
    24 #endif /* INCLUDE_ATM_ACCOUNT_H_ */

      atm_account.c

     1 #include "sem_pv.h"
     2 #include "atm_account.h"
     3 
     4 /** 取款 */
     5 double atm_account_withdraw(atm_account *a, double amt)
     6 {
     7     assert(a != NULL);
     8 
     9     /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
    10     sem_P(a->semid, 0, 1);
    11     if(amt < 0 || amt > a->balance){
    12         /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
    13         sem_V(a->semid, 0, 1);
    14         return 0.0;
    15     }
    16 
    17     double balance = a->balance;
    18     sleep(1);
    19     balance -= amt;
    20     a->balance = balance;
    21     /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
    22     sem_V(a->semid, 0, 1);
    23     return amt;
    24 }
    25 
    26 /** 存款 */
    27 double atm_account_deposit(atm_account *a, double amt)
    28 {
    29     assert(a != NULL);
    30 
    31     /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
    32     sem_P(a->semid, 0, 1);
    33     if(amt < 0){
    34         /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
    35         sem_V(a->semid, 0, 1);
    36         return 0.0;
    37     }
    38     double balance = a->balance;
    39     sleep(1);
    40     balance += amt;
    41     a->balance = balance;
    42     /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
    43     sem_V(a->semid, 0, 1);
    44 
    45     return amt;
    46 }
    47 
    48 /** 查看账户余额度 */
    49 double amt_account_balanceGet(atm_account *a)
    50 {
    51     assert(a != NULL);
    52     /** 对信号量集 semid 中的0号信号量/信号灯作 P(1) 操作 */
    53     sem_P(a->semid, 0, 1);
    54     double balance = a->balance;
    55     /** 对信号量集 semid 中的0号信号量/信号灯作 V(1) 操作 */
    56     sem_V(a->semid, 0, 1);
    57     return balance;
    58 }

      测试代码:atm_account_test.c

     1 #include "atm_account.h"
     2 #include "sem_pv.h"
     3 #include <unistd.h>
     4 #include <sys/shm.h>
     5 #include <sys/wait.h>
     6 #include <stdlib.h>
     7 #include <stdio.h>
     8 #include <string.h>
     9 
    10 int main(void)
    11 {
    12     /** 在共享内存中创建银行账户 */
    13     int shmid;
    14     if((shmid = shmget(IPC_PRIVATE, sizeof(atm_account), IPC_CREAT | IPC_EXCL | 0777)) < 0){
    15         perror("shmget error");
    16         return 1;
    17     }
    18 
    19     /** 进行共享内存映射(a 为映射的地址) */
    20     atm_account *a = (atm_account *)shmat(shmid, 0, 0);
    21     if(a == (atm_account *)-1){
    22         perror("shmat error");
    23         return 1;
    24     }
    25     a->code = 123456789;
    26     a->balance = 10000;
    27 
    28     /** 创建信号量集并初始化(1 个信号量/信号灯,初值为 1) */
    29     a->semid = sem_I(1, 1);
    30     if(a->semid < 0){
    31         perror("sem_I(1, 1) error");
    32         return 1;
    33     }
    34     printf("balance: %f
    ", a->balance);
    35 
    36     pid_t pid;
    37     if((pid = fork()) < 0){
    38         perror("fork error");
    39         return 1;
    40     }
    41     else if(pid > 0){
    42         /** 父进程执行取款操作 */
    43         double amt = atm_account_withdraw(a, 10000);
    44         printf("pid %d withdraw %f form code %d
    ", getpid(), amt, a->code);
    45         wait(0);
    46 
    47         /** 对共享内存的操作要在解除映射之前 */
    48         printf("balance: %f
    ", a->balance);
    49 
    50         sem_D(a->semid);    ///< 销毁信号量集
    51         shmdt(a);    ///< 解除共享内存的映射
    52         shmctl(shmid, IPC_RMID, NULL);///< 释放共享内存
    53     }
    54     else {
    55         /** 子进程进行取款操作 */
    56         double amt = atm_account_withdraw(a, 10000);
    57         printf("pid %d withdraw %f form code %d
    ", getpid(), amt, a->code);
    58 
    59         shmdt(a);    ///< 解除共享内存的映射
    60     }
    61 
    62     return 0;
    63 }

      编译运行如下:

       

    51.2.2 PV操作--读者写者案例

      目的:利用进程信号量的 PV操作实现进程间的同步问题

      共享内存中读写数据(读者和写者问题)

      

      

      1 #include <sys/shm.h>
      2 #include <sys/sem.h>
      3 #include <sys/wait.h>
      4 #include <unistd.h>
      5 #include <string.h>
      6 #include <stdlib.h>
      7 #include <stdio.h>
      8 #include <assert.h>
      9 
     10 /** 读者和写者的共享资源 */
     11 typedef struct {
     12     int     val;
     13     int     semid;
     14 }Storage;
     15 
     16 void init(Storage *s)
     17 {
     18     assert(s != NULL);
     19 
     20     /** 创建信号量集(包含 2 个信号量) */
     21     if((s->semid = semget(IPC_PRIVATE, 2, IPC_CREAT | IPC_EXCL | 0777)) < 0){
     22         perror("semget error");
     23         exit(1);
     24     }
     25 
     26     /** 对信号量集中的所有信号量初始化 */
     27     union semun{
     28         int                 val;
     29         struct semid_ds        *ds;
     30         unsigned short        *array;
     31     };
     32     union semun    un;
     33     /** 2 个信号量的初值设置为 0 */
     34     unsigned short array[2] = {0, 0};
     35     un.array = array;
     36     if(semctl(s->semid, 0, SETALL, un) < 0){
     37         perror("semctl error");
     38         exit(1);
     39     }
     40 }
     41 
     42 void destroy(Storage *s)
     43 {
     44     assert(s != NULL);
     45     if(semctl(s->semid, 0, IPC_RMID, NULL) < 0){
     46         perror("semctl error");
     47         exit(1);
     48     }
     49 }
     50 
     51 void writer(Storage *s, int val)
     52 {
     53     /** 写入数据到 Storage */
     54     s->val = val;
     55     printf("%d write %d
    ", getpid(), val);
     56 
     57     /** 设置信号量 0 号作 V(1) 操作 */
     58     struct sembuf ops_v[1] = {{0, 1, SEM_UNDO}};
     59     /** 设置信号量 1 号作 P(1) 操作 */
     60     struct sembuf ops_p[1] = {{1, -1, SEM_UNDO}};
     61 
     62     /** V(s1) */
     63     if(semop(s->semid, ops_v, 1) < 0){
     64         perror("semop error");
     65     }
     66 
     67     /** P(s2) */
     68     if(semop(s->semid, ops_p, 1) < 0){
     69         perror("semop error");
     70     }
     71 }
     72 
     73 void reader(Storage *s)
     74 {
     75     assert(s != NULL);
     76 
     77     /** 设置信号量 0 号作 P(1) 操作 */
     78     struct sembuf ops_p[1] = {{0, -1, SEM_UNDO}};
     79     /** 设置信号量 1 号作 V(1) 操作 */
     80     struct sembuf ops_v[1] = {{1, 1, SEM_UNDO}};
     81     /** P(s1) */
     82     if(semop(s->semid, ops_p, 1) < 0){
     83         perror("semop error");
     84     }
     85     /** 从 Storage 中读取数据 */
     86     printf("%d read %d
    ", getpid(), s->val);
     87     /** V(s2) */
     88     if(semop(s->semid, ops_v, 1) < 0){
     89         perror("semop error");
     90     }
     91 }
     92 
     93 int main(void)
     94 {
     95     /** 将共享资源 Storage 创建在共享内存中 */
     96     int shmid;
     97     if((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | 0777)) < 0){
     98         perror("shmget error");
     99         exit(1);
    100     }
    101 
    102     /** 父进程进行共享内存映射 */
    103     Storage *s = (Storage *)shmat(shmid, 0, 0);
    104     if(s == (Storage *)-1){
    105         perror("shmat error");
    106         exit(1);
    107     }
    108 
    109     /** 创建信号量并初始化 */
    110     init(s);
    111 
    112     pid_t pid;
    113     pid = fork();
    114     if(pid < 0){
    115         perror("fork error");
    116         exit(1);
    117     }
    118     else if(pid > 0){
    119         int i = 1;
    120         for(;i <= 20; i++){
    121             writer(s, i);
    122         }
    123         wait(0);
    124         destroy(s);
    125         shmdt(s);
    126         shmctl(shmid, IPC_RMID, NULL);
    127     }
    128     else{
    129         int i = 1;
    130         for(;i <= 20; i++){
    131             reader(s);
    132         }
    133         shmdt(s);
    134     }
    135 }
  • 相关阅读:
    Linux下通过二进制方式安装mysql5.7版本和系统优化
    SQL中的real、float、decimal、numeric数据类型区别
    SQL中的事务ACID
    一台服务器搭建部署两个或多个Redis实例
    SQLServer数据库镜像高性能模式下维护
    SQLServer配置镜像,无法将 ALTER DATABASE 命令发送到远程服务器实例,数据库镜像配置未更改。请确保该服务器已连接,然后重试。
    阿里云数据库MongoDB版清理oplog日志和compact命令详解
    Linux下shell脚本实现mongodb定时自动备份
    List分组
    Sql Server日期查询-SQL查询今天、昨天、7天内、30天
  • 原文地址:https://www.cnblogs.com/kele-dad/p/10332035.html
Copyright © 2020-2023  润新知