• 进程间通信(IPC):共享内存


    进程间通信 IPC :管道、信号量、共享内存、消息队列、套接字

    进程间通信,两个进程间传递信息

    除了套接字,前面几个主要是在同一台主机上两个进程间通信

    共享内存

    共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了 数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。

    接口

    1. #include <sys/ipc.h>
    2. #include <sys/shm.h>
    3. #include <sys/types.h>

    int shmget(key_t key, size_t size, int shmflg); //创建共享内存

    shmget()  用于创建或者获取共享内存

    shmget()  成功返回共享内存的 ID, 失败返回-1

    key: 不同的进程使用相同的 key 值可以获取到同一个共享内存

    size: 创建共享内存时,指定要申请的共享内存空间大小,以字节为单位

    shmflg: 包含9个比特的权限标志

                 IPC_CREAT IPC_EXCL 

                 IPC_CREAT:存在则打开,不存在则创建

                 IPC_EXCL和IPC_CREAT同时使用存在则会报错,不存在创建;

                                                         (权限直接加在后面进行赋值)

    void* shmat(int shmid, const void *shmaddr, int shmflg); //映射,将共享内存映射到进程的地址空间

    shmat()  将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上 

    shmat()  成功返回返回共享内存的首地址,失败返回 NULL

    shmaddr:一般给NULL,由系统自动选择映射的虚拟地址空间

    shmflg: 一组标志位,SHM_RND(这个标志与shm_addr联合使用,用来控制共享内存链接共享内存链接的地址)  SHM_RDONLY(它使得连接的内存只读),不指定时给0就可以

    int shmdt(const void *shmaddr); //断开映射

    shmdt()  断开当前进程的 shmaddr 指向的共享内存映射

    shmdt()  成功返回 0, 失败返回-1

    shmaddr  返回的地址指针

    int shmctl(int shmid, int cmd, struct shmid_ds *buf);  //控制共享内存,eg:删除

    shmctl()  控制共享内存

    shmctl()  成功返回 0,失败返回-1 

    cmd: 要采取的动作

                IPC_STAT   把shmid__ds结构中的数据设置为共亨内存的当前关联值
                IPC_SET     如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds

                                    结构中给出的值
                IPC_RMID   删除共享内存段

    buf: 是一个指针,它指向包含共享内存模式和访问权限的结构。
     

    代码实现

    进程 a向共享内存中写入数据,进程 b 从共享内存中读取数据并显示 shma.c 的代码:

    a文件输入,b文件输出

    a.c文件

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<string.h>
    5. #include<sys/shm.h>
    6. int main()
    7. {
    8. //创建共享内存
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
    10. if(shmid==-1)
    11. {
    12. printf("shmget err\n");//创建失败
    13. exit(1);
    14. }
    15. //映射,将共享内存映射到进程的地址空间
    16. //映射成功后就可以通过指针s访问此空间
    17. char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间标志位不指定为0
    18. if(s==(char *)-1)
    19. {
    20. printf("shmat err\n");//映射失败
    21. exit(1);
    22. }
    23. //向共享内存写入数据
    24. strcpy(s,"hello");
    25. shmdt(s);//断开映射
    26. exit(0);
    27. }

    b.c文件

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<string.h>
    5. #include<sys/shm.h>
    6. int main()
    7. {
    8. //创建共享内存,存在就获取共享内存
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
    10. if(shmid==-1)
    11. {
    12. printf("shmget err\n");//创建失败
    13. exit(1);
    14. }
    15. //映射,将共享内存映射到进程的地址空间
    16. //映射成功后就可以通过指针s访问此空间
    17. char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标
    18. 志位不指定为0
    19. if(s==(char *)-1)
    20. {
    21. printf("shmat err\n");//映射失败
    22. exit(1);
    23. }
    24. //用test文件读取共享内存空间中的值
    25. printf("read:%s\n",s);
    26. shmdt(s);//断开映射
    27. exit(0);
    28. }

    共享内存创建成功后,会一直存在,即使程序运行结束,也还会存在,除非删除此共享内存或者重启

     a.c文件持续写入,b.c文件持续读取

    a.c文件

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<string.h>
    5. #include<sys/shm.h>
    6. int main()
    7. {
    8. //创建共享内存
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
    10. if(shmid==-1)
    11. {
    12. printf("shmget err\n");//创建失败
    13. exit(1);
    14. }
    15. //映射,将共享内存映射到进程的地址空间
    16. //映射成功后就可以通过指针s访问此空间
    17. char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间标志位不指定为0
    18. if(s==(char *)-1)
    19. {
    20. printf("shmat err\n");//映射失败
    21. exit(1);
    22. }
    23. while(1)
    24. {
    25. printf("input\n");
    26. char buff[128]={0};//将输入的的数据放在buff中
    27. fgets(buff,128,stdin);
    28. strcpy(s,buff);//先将数据写入共享内存是为了也可以让读取数据时以end为结束标志
    29. if(strncmp(buff,"end",3)==0)
    30. {
    31. break;//以end为输入结束的标志
    32. }
    33. }
    34. shmdt(s);//断开映射
    35. exit(0);
    36. }

    b.c文件

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<string.h>
    5. #include<sys/shm.h>
    6. int main()
    7. {
    8. //创建共享内存,存在就获取共享内存
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
    10. if(shmid==-1)
    11. {
    12. printf("shmget err\n");//创建失败
    13. exit(1);
    14. }
    15. //映射,将共享内存映射到进程的地址空间
    16. //映射成功后就可以通过指针s访问此空间
    17. char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标
    18. 志位不指定为0
    19. if(s==(char *)-1)
    20. {
    21. printf("shmat err\n");//映射失败
    22. exit(1);
    23. }
    24. while(1)
    25. {
    26. if(strncmp(s,"end",3)==0)
    27. {
    28. break;//先判断是不是结束标志,不是再输出
    29. }
    30. sleep(1);
    31. printf("resf:%s\n",s);//以此读取数据
    32. }
    33. shmdt(s);//断开映射
    34. exit(0);
    35. }

    运行结果如下:

     我们可以看到,b是可以读到共享内存的,a输入数据b可以读取,但是只要我们不输入新的数据,b就一直读取上一个数据,

    我们想要的结果是写一次,读一次,不写就不读,我们可以通过信号量解决这个问题

    使用信号量控制共享内存

    此时我们需要两个信号量,s1,s2

    sem.h

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<string.h>
    5. #include<sys/sem.h>
    6. #define SEM1 0 //第一个信号量
    7. #define SEM2 1 //第二个信号量
    8. #define NUM 2 //信号量总个数
    9. union semun
    10. {
    11. int val;//初始值
    12. };//自己定义
    13. void sem_init();//信号量初始化
    14. void sem_p(int index);//P操作,创建两个信号量,用下标区分
    15. void sem_v(int index);//V操作
    16. void sem_destroy();//销毁

    sem.c

    1. #include"sem.h"
    2. static int semid = -1;//信号量id,只在本文件有效
    3. void sem_init()//信号量初始化
    4. {
    5. semid = semget((key_t)1234,NUM,IPC_CREAT|IPC_EXCL|0600);//key值为1234,SEM个信号量,全新创建一个信号量,如果已存在就报错,存取权限0600
    6. if(semid == -1)//全新创建失败,说明已存在或者创建失败
    7. {
    8. semid=semget((key_t)1234,NUM,0600);
    9. //semid = semget((key_t)1234,SEM,0600);//当信号已存在,获取信号量
    10. if(semid == -1)
    11. {
    12. printf("semget err\n");//前面获取失败说明是创建的时候就失败了
    13. }
    14. }
    15. else//因为要创建NUM个信号量,循环初始化
    16. {
    17. union semun a;
    18. int arr[NUM]={1,0};//信号量初始值数组
    19. for(int i=0;i<NUM;i++)
    20. {
    21. a.val = arr[i];//信号量的初始值
    22. if(semctl(semid,i,SETVAL,a)==-1)//i,信号量个数。SETVAL,a,赋初值
    23. {
    24. printf("semctl init err\n");//初始化失败打印此语句
    25. }
    26. }
    27. }
    28. }
    29. void sem_p(int index)//P操作
    30. {
    31. if(index<0 || index>=NUM)
    32. {
    33. return;
    34. }
    35. struct sembuf buf;//对信号量的改变
    36. buf.sem_num=index;//信号量的下标
    37. buf.sem_op=-1;//对信号量执行的是p操作
    38. buf.sem_flg = SEM_UNDO;//标志位,如果程序异常可以帮助进行v操作
    39. if(semop(semid,&buf,1)==-1)
    40. {
    41. printf("semop p err\n");
    42. }
    43. }
    44. void sem_v(int index)//V操作
    45. {
    46. if(index<0 || index>=NUM)
    47. {
    48. return;
    49. }
    50. struct sembuf buf;//对信号量的改变
    51. buf.sem_num=index;//信号量的下标
    52. buf.sem_op=1;//对信号量执行的是v操作
    53. buf.sem_flg = SEM_UNDO;
    54. if(semop(semid,&buf,1)==-1)
    55. {
    56. printf("semop v err\n");
    57. }
    58. }
    59. void sem_destroy()//销毁
    60. {
    61. if(semctl(semid,0,IPC_RMID)==-1)//semid 要删除的信号,0 占位,根据semid删除创建的所有信号。IPC_RMID 删除信号量
    62. {
    63. printf("semctl destroy err\n");
    64. }
    65. }

    a.c

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<string.h>
    5. #include<sys/shm.h>
    6. #include"sem.h"
    7. int main()
    8. {
    9. //创建共享内存
    10. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
    11. if(shmid==-1)
    12. {
    13. printf("shmget err\n");//创建失败
    14. exit(1);
    15. }
    16. //映射,将共享内存映射到进程的地址空间
    17. //映射成功后就可以通过指针s访问此空间
    18. char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标>
    19. 志位不指定为0
    20. if(s==(char *)-1)
    21. {
    22. printf("shmat err\n");//映射失败
    23. exit(1);
    24. }
    25. sem_init();//信号量初始化
    26. while(1)
    27. {
    28. printf("input\n");
    29. char buff[128]={0};//将输入的的数据放在buff中
    30. fgets(buff,128,stdin);
    31. sem_p(SEM1);//对a文件先P操作
    32. strcpy(s,buff);//先将数据写入共享内存是为了也可以让读取数据时以end为结束标志
    33. sem_v(SEM2);//对b文件进行v操作,不能将v操作放在下面的判读之后,因为如果最后输出end结束后,b文件不能进行v操作获得权限就无法打印最后
    34. 一个
    35. if(strncmp(buff,"end",3)==0)
    36. {
    37. break;//以end为输入结束的标志
    38. }
    39. }
    40. shmdt(s);//断开映射
    41. exit(0);
    42. }

    b.c文件

    1. #include<stdio.h>
    2. #include<stdlib.h>
    3. #include<unistd.h>
    4. #include<string.h>
    5. #include<sys/shm.h>
    6. #include"sem.h"
    7. int main()
    8. {
    9. //创建共享内存,存在就获取共享内存
    10. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);//key值为1234,共享内存大小为128,IPC_CREAT 存在则打开,不存在创建,权限0600
    11. if(shmid==-1)
    12. {
    13. printf("shmget err\n");//创建失败
    14. exit(1);
    15. }
    16. //映射,将共享内存映射到进程的地址空间
    17. //映射成功后就可以通过指针s访问此空间
    18. char*s=(char*)shmat(shmid,NULL,0);//shmat本身返回值void*类型,强转为char*,shmid 共享内存id,给NULL。系统自动选择映射的虚拟地址空间 标>
    19. 志位不指定为0
    20. if(s==(char *)-1)
    21. {
    22. printf("shmat err\n");//映射失败
    23. exit(1);
    24. }
    25. sem_init();//初始化信号量
    26. while(1)
    27. {
    28. sem_p(SEM2);
    29. if(strncmp(s,"end",3)==0)
    30. {
    31. break;//先判断是不是结束标志,不是再输出
    32. }
    33. printf("resf:%s\n",s);//以此读取数据
    34. sem_v(SEM1);
    35. }
    36. shmdt(s);//断开映射
    37. sem_destroy();//销毁信号量
    38. exit(0);
    39. }

    运行程序

    运行结果

  • 相关阅读:
    ChatGPT工作提效之使用python开发对接百度地图开放平台API的实战方案(批量路线规划、批量获取POI、突破数量有限制、批量地理编码)
    MySQL 如何避免克隆失败后再次初始化
    python提高运算速度的方法:内存缓存+磁盘缓存
    PDF转换成PPT后格式混乱,可能这个没做好
    第一个 vue-cli 项目
    基于K-prototype算法聚类
    webrtc gcc算法(1)
    03-安装docker及使用docker安装其他软件(手动挂载数据卷)
    pyhton将socket接收数据的字节改变并做处理
    信钰证券:长江电力180亿市值,招商证券、摩根大通等浮盈超一成
  • 原文地址:https://blog.csdn.net/swint_er/article/details/125360242