• Linux进程间通信--信号量


    本系列文章主要是学习记录Linux下进程间通信的方式。

    常用的进程间通信方式:管道、FIFO、消息队列、信号量以及共享存储。

    参考文档:《UNIX环境高级编程(第三版)》

    参考视频:Linux进程通信  推荐看看,老师讲得很不错

    Linux核心版本:2.6.32-431.el6.x86_64

    注:本文档只是简单介绍IPC,更详细的内容请查看参考文档和相应视频。

    本文介绍利用信号量进行进程间的通信

    1  介绍

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

    2  信号量集属性

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

    3  函数原型

    1 #include <sys/types.h>
    2 #include <sys/ipc.h>
    3 #include <sys/sem.h>
    4 int semget(key_t key, int nsems, int semflg);
    5 说明:创建信号量集
    6 返回:成功返回信号量集ID,出错返回-1
    7 参数key:用户指定的信号量集键值
    8 参数nsems:信号量集中信号量个数
    9 参数semflg:IPC_CREAT、IPC_EXCL等权限组合
     1 #include <sys/types.h>
     2 #include <sys/ipc.h>
     3 #include <sys/sem.h>
     4 int semctl(int semid, int semnum, int cmd, .../*union semum arg*/);
     5 union semun {
     6     int              val;    /* Value for SETVAL */
     7     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
     8     unsigned short  *array;  /* Array for GETALL, SETALL */
     9     struct seminfo  *__buf;  /* Buffer for IPC_INFO
    10                                 (Linux-specific) */
    11 };
    12 说明:信号量集控制
    13 参数semid:信号量集ID;
    14 参数semnum:0表示对所有信号量操作,信号量编号从0开始;
    15 参数val:放置获取或设置信号量集中某个信号量的值;
    16 参数buf:信号量集属性指针
    17 参数array:放置获取或设置信号量集中所有信号量的值。
    18 参数cmd:设定对信号量集要执行的操作
    19 IPC_STAT:获取信号量集的属性(buf)
    20 IPC_SET:设置信号量集的属性(buf)
    21 IPC_RMID:删除信号量集(buf)
    22 GETVAL:返回信号量的值(val)
    23 SETVAL:设置semnum信号量的值(val)
    24 GETALL:获取所有信号量的值(array)
    25 SETALL:设置所有信号量的初始值(array)
     1 #include <sys/types.h>
     2 #include <sys/ipc.h>
     3 #include <sys/sem.h>
     4 int semop(int semid, struct sembuf *sops, unsigned nsops);
     5 struct sembuf
     6 {
     7     unsigned short sem_num;  /* semaphore number */
     8     short          sem_op;   /* semaphore operation */
     9     short          sem_flg;  /* operation flags */
    10 };
    11 说明:信号量集操作
    12 返回:成功返回0,出错返回-1
    13 参数semid:信号量集ID
    14 参数sops:sembuf结构体数组指针
    15 参数nsops:第二个参数中结构体数组的长度。
    16 参数sem_num:信号量集中信号量的编号
    17 参数sem_op:正数为V操作,负数为P操作,0可用于对共享资源时候已用完的测试。
    18 参数sem_flg:SEM_UNDO标志,表示在进程结束时,相应的操作将被取消。如果设置了此标志,那么在进程没有释放共享资源就退出时,内核将代为释放。
    19 注:用于信号量集中信号量的加和减操作(PV操作),可用于进程间的互斥和同步。

    4  测试案例

    (1)实例1  利用信号量实现互斥操作

     银行账户的头文件,主要是对账户的一些操作的申明:

     1 #ifndef __ACCOUNT_H__
     2 #define __ACCOUNT_H__
     3 
     4 typedef struct 
     5 {
     6     int     code;
     7     double  balance;
     8     int     semid;  //在共享资源上绑定一个信号量集
     9 }Account;
    10 
    11 //取款
    12 extern double withdraw(Account *a, double amt);
    13 
    14 //存款
    15 extern double deposit(Account *a, double amt);
    16 
    17 //查看账户余额
    18 extern double get_balance(Account *a);
    19 
    20 
    21 #endif
    View Code

    银行账户的C文件,账户操作的实现:

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

    把对信号量得操作封装为单独的文件。

    信号量操作头文件:

     1 #ifndef __PV_H__
     2 #define __PV_H__
     3 
     4 // 初始化semnums个信号灯/信号量的值(value)
     5 extern int I(int semnums, int value);
     6 
     7 // 对信号量集(semid)中的信号灯(semnum)做P(value)操作
     8 extern void P(int semid, int semnum, int value);
     9 
    10 // 对信号量集(semid)中的信号灯(semnum)做V(value)操作
    11 extern void V(int semid, int semnum, int value);
    12 
    13 // 销毁信号量集(semid)
    14 extern void D(int semid);
    15 
    16 #endif
    View Code

    信号量操作C文件:

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

    测试代码,父子进程同时对银行账户进行操作:

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

    测试步骤:

    1、编译:[root@192 ipc]# gcc -o bin/account_test -Iinclude obj/pv.o account.c account_test.c 

    2、运行:

    可以看出,通过信号量能够对银行账户实现互斥访问。

    (2)案例2  利用信号量实现同步操作

    这里实现的读者和写者问题和书本中有一些差别。

    测试代码:

      1 #include <stdio.h>
      2 #include <stdlib.h>
      3 #include <string.h>
      4 #include <sys/types.h>
      5 #include <sys/ipc.h>
      6 #include <sys/sem.h>
      7 #include <assert.h>
      8 #include <unistd.h>
      9 #include <sys/shm.h>
     10 
     11 // 读者和写者的共享资源
     12 typedef struct 
     13 {
     14     int val;
     15     int semid;
     16 }Storage;
     17 
     18 void init_s(Storage *s) 
     19 {
     20     assert(s != NULL);
     21     // 创建信号量集,包含两个信号量
     22     if ((s->semid = semget(IPC_PRIVATE, 2, IPC_CREAT | IPC_EXCL | 0777)) < 0) {
     23         perror("semget error");
     24         exit(0);
     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_s(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 write_s(Storage *s, int val)
     52 {
     53     assert(s != NULL);
     54     // 写入数据到storage
     55     s->val = val;
     56     printf("%d write %d
    ", getpid(), val);
     57 
     58     // 设置0号信号量(s1)做V(1)操作
     59     struct sembuf ops_v[1] = {{0, 1, SEM_UNDO}};
     60     // 设置1号信号量(s2)做P(1)操作
     61     struct sembuf ops_p[1] = {{1, -1, SEM_UNDO}};
     62     
     63     // V(s1)  0号信号量做V(1)
     64     if (semop(s->semid, ops_v, 1) < 0) {
     65         perror("semop error");
     66     }
     67     // P(s2)  1号信号量做V(1)
     68     if (semop(s->semid, ops_p, 1) < 0) {
     69         perror("semop error");
     70     }
     71 }
     72 
     73 void read_s(Storage *s)
     74 {
     75     assert(s != NULL);
     76 
     77     // 设置0号信号量(s1)做P(1)操作
     78     struct sembuf ops_p[1] = {{0, -1, SEM_UNDO}};
     79     // 设置1号信号量(s2)做V(1)操作
     80     struct sembuf ops_v[1] = {{1, 1, SEM_UNDO}};
     81 
     82     // P(s1)  o号信号量做P(1)操作
     83     if (semop(s->semid, ops_p, 1) < 0) {
     84         perror("semop error");
     85     }
     86 
     87     // 从Storage中读取数据
     88     printf("%d read %d
    ", getpid(), s->val);
     89 
     90     // V(s2)  1号信号量做V(1)操作
     91     if (semop(s->semid, ops_v, 1) < 0) {
     92         perror("semop error");
     93     }
     94 }
     95 
     96 int main(void)
     97 {
     98     // 将共享资源Storage创建在共享内存中
     99     int shmid;
    100     if ((shmid = shmget(IPC_PRIVATE, sizeof(Storage), IPC_CREAT | IPC_EXCL | 0777)) < 0) {
    101         perror("shmget error");
    102         exit(1);
    103     }
    104     // 父进程进行共享内存映射
    105     Storage *s = (Storage *)shmat(shmid, 0, 0);
    106     if (s == (Storage *)-1) {
    107         perror("shmat error");
    108         exit(1);
    109     }
    110     // 创建信号量集并初始化
    111     init_s(s);
    112 
    113     pid_t pid;
    114     pid = fork();
    115     if (pid < 0) {
    116         perror("fork error");
    117         exit(1);
    118     } else if (pid > 0) {
    119         // 父进程写入数据
    120         int i = 1;
    121         for (; i <= 5; i++) {
    122             write_s(s, i);
    123         }
    124         wait(0);
    125         destroy_s(s);
    126         shmdt(s);  //解除映射
    127         shmctl(shmid, IPC_RMID, NULL);  //释放共享内存
    128     } else {
    129         // 子进程读取数据
    130         int i = 1;
    131         for (; i <= 5; i++) {
    132             read_s(s);
    133         }
    134         shmdt(s);
    135     }
    136 
    137     return 0;
    138 }
    View Code

    测试步骤:

    1、编译:[root@192 ipc]# gcc -o bin/read_write -g reader_writer.c 

    2、运行:[root@192 ipc]# ./bin/read_write 

  • 相关阅读:
    人脸识别2
    neo4j可视化展示3
    20220608 08:00:04
    neo4j可视化展示
    20220609 08:00:02
    DNN(深度神经网络算法)
    人脸识别1
    20220610 08:00:02
    opencv加载视频源
    neo4j可视化展示
  • 原文地址:https://www.cnblogs.com/mrlayfolk/p/13047366.html
Copyright © 2020-2023  润新知