进程间通信之共享内存

本文深入讲解了进程间通信中的共享内存机制,包括共享内存的基本概念、相关函数如shmget、shmat、shmdt和shmctl的使用方法,以及如何在有亲缘关系和无亲缘关系的进程间实现数据通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

进程间通信之共享内存

一、共享内存的介绍

共享内存可以从字面上去理解,就把一片逻辑内存共享出来,让不同的进程去访问它,修改它。共享内存是在两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常安排为同一段物理内存。进程可以将同一段共享内存连接到它们自己的地址空间中,所有进程都可以访问共享内存中的地址,就好像它们是由用C语言函数malloc分配的内存一样。而如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
但有一点特别要注意:共享内存并未提供同步机制。也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取。所以我们通常需要用其他的机制来同步对共享内存的访问,比如信号量。

它的原理就是:在Linux中,每个进程都有属于自己的进程控制块(PCB)和地址空间(Addr Space),并且都有一个与之对应的页表,负责将进程的虚拟地址与物理地址进行映射,通过内存管理单元(MMU)进行管理。两个不同的虚拟地址通过页表映射到物理空间的同一区域,它们所指向的这块区域即共享内存。
1.查看系统中的共享存储段
ipcs -m
2.删除系统中的共享存储段
ipcrm -m [shmid]

二、函数介绍

shmget函数

功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

有一个特殊的键值IPC_PRIVATE, 它用于创建一个只属于创建进程的共享内存,通常不会用到。
代码:

  1 #include<unistd.h>
  2 #include<stdlib.h>
  3 #include<stdio.h>
  4 #include<sys/shm.h>
  5 #include <sys/ipc.h>
  6 
  7 int main()
  8 {
  9     int shmid;
 10     shmid = shmget(IPC_PRIVATE,128,0777);
 11     if(shmid < 0)
 12     {
 13         printf("create error\n");
 14         return -1;
 15     }
 16     printf("create share memory ok,shmid = %d\n",shmid);
 17     system("ipcs -m");
 18     return 0;                                                               
 19 }

结果:

wz@wz-machine:~/linux/shm$ ./shm
create share memory ok,shmid = 1540108

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态      
0x00000000 1179648    wz         600        524288     2          目标       
0x00000000 393217     wz         600        524288     2          目标       
0x00000000 425986     wz         600        524288     2          目标       
0x00000000 622595     wz         600        524288     2          目标       
0x00000000 720900     wz         600        524288     2          目标       
0x00000000 753669     wz         600        16777216   2                       
0x00000000 1015814    wz         600        524288     2          目标       
0x00000000 884743     wz         600        33554432   2          目标       
0x00000000 1277960    wz         600        524288     2          目标       
0x00000000 1310729    wz         600        1048576    2          目标       
0x00000000 1474570    wz         777        128        0                       
0x00000000 1507339    wz         777        128        0                       
0x00000000 1540108    wz         777        128        0           

shmat函数

第一次创建共享内存段时,它不能被任何进程访问。要想启动对该内存的访问,必须将其连接到一个进程的地址空间。
这个工作由shmat函数完成:
函数原型:void *shmat(int shm_id, const void *shm_addr, int shmflg);
参数:
shm_id : 由shmget返回的共享内存标识。
shm_addr: 指定共享内存连接到当前进程中的地址位置。它通常是一个空指针, 表示让系统来选择共享内存出现的地址。
shmflg : 是一组标志。它的两个可能取值是SHM_RND, 和shm_addr联合使用, 用来控制共享内存连接的地址。SHM_RDONLY, 它使连接的内存只读

返回值:
如果调用成功, 返回一个指向共享内存第一个字节的指针;
如果失败,返回-1.

共享内存的读写权限由它的属主(共享内存的创建者),
它的访问权限由当前进程的属主决定。
共享内存的访问权限类似于文件的访问权限。

 1 #include<unistd.h>                                                          
  2 #include<stdlib.h>
  3 #include<stdio.h>
  4 #include<sys/shm.h>
  5 #include <sys/ipc.h>
  6 
  7 int main()
  8 {
  9     int shmid;
 10     char *p;
 11     shmid = shmget(IPC_PRIVATE,128,0777);
 12     if(shmid < 0)
 13     {
 14         printf("create error\n");
 15         return -1;
 16     }
 17     printf("create share memory ok,shmid = %d\n",shmid);
 18     system("ipcs -m");
 19     p = (char*)shmat(shmid,NULL,0);
 20     if(p == NULL)
 21     {
 22         printf("create shmat error\n");
 23         return -1;
 24     }
 25     //write
 26     fgets(p,128,stdin);
 27 
 28     //read
 29     printf("share memory:%s\n",p);
 30     printf("second share memory:%s\n",p);                                   
 31     return 0;

结果:

00000000 425986     wz         600        524288     2          目标       
0x00000000 622595     wz         600        524288     2          目标       
0x00000000 720900     wz         600        524288     2          目标       
0x00000000 753669     wz         600        16777216   2                       
0x00000000 1015814    wz         600        524288     2          目标       
0x00000000 884743     wz         600        33554432   2          目标       
0x00000000 1277960    wz         600        524288     2          目标       
0x00000000 1310729    wz         600        1048576    2          目标       
0x00000000 1474570    wz         777        128        0                       
0x00000000 1507339    wz         777        128        0                       
0x00000000 1540108    wz         777        128        0                       
0x00000000 1638413    wz         600        524288     2          目标       
0x00000000 1703950    wz         600        2097152    2          目标       
0x00000000 1736719    wz         777        128        0                       

hello
share memory:hello

second share memory:hello

由此可知,shmat函数相当于把内存中的共享内存映射到用户空间,可以直间进程读写操作,而且读完一次之后,该内存不会释放。

shmdt(去关联)

将共享内存从当前进程中分离。
int shmdt(const void *shm_addr);
shm_addr: shmat返回的地址指针。

成功时,返回0,
失败时,返回-1.

NOTE:
共享内存分离并未删除它,
只是使得该共享内存对当前进程不再可用。

shmctl函数

删除共享内存对象
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
shmid_ds结构至少包含以下成员:

struct shmid_ds {
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;

参数:
shm_id : 是shmget返回的共享内存标识符。
command: 是要采取的动作,
它可以取3个值:

IPC_STAT 把shmid_ds结构中的数据设置为共享内存的当前关联值
IPC_SET 如果进程有足够的权限,
就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID 删除共享内存段
buf : 是一个指针,
包含共享内存模式和访问权限的结构。

返回值:
成功时,返回0,
失败时,返回-1.

 1 #include<unistd.h>                                                          
  2 #include<stdlib.h>
  3 #include<stdio.h>
  4 #include<sys/shm.h>
  5 #include <sys/ipc.h>
  6 
  7 int main()
  8 {
  9     int shmid;
 10     char *p;
 11     shmid = shmget(IPC_PRIVATE,128,0777);
 12     if(shmid < 0)
 13     {
 14         printf("create error\n");
 15         return -1;
 16     }
 17     printf("create share memory ok,shmid = %d\n",shmid);
 18     system("ipcs -m");
 19     p = (char*)shmat(shmid,NULL,0);
 20     if(p == NULL)
 21     {
 22         printf("create shmat error\n");
 23         return -1;
 24     }
 25     //write
 26     fgets(p,128,stdin);
 27 
 28     //read
 29     printf("share memory:%s\n",p);
 30     printf("second share memory:%s\n",p);
 31     shmdt(p);
 32     shmctl(shmid,IPC_RMID,NULL);
 33     system("ipcs -m");
 34     return 0;
 35 }                           

结果:

wz@wz-machine:~/linux/shm$ ./shmctl 
create share memory ok,shmid = 1900560

------------ 共享内存段 --------------
键        shmid      拥有者  权限     字节     连接数  状态      
0x00000000 1179648    wz         600        524288     2          目标       
0x00000000 393217     wz         600        524288     2          目标       
0x00000000 425986     wz         600        524288     2          目标       
0x00000000 622595     wz         600        524288     2          目标       
0x00000000 720900     wz         600        524288     2          目标       
0x00000000 753669     wz         600        16777216   2                       
0x00000000 1015814    wz         600        524288     2          目标       
0x00000000 884743     wz         600        33554432   2          目标       
0x00000000 1277960    wz         600        524288     2          目标       
0x00000000 1310729    wz         600        1048576    2          目标       
0x00000000 1474570    wz         777        128        0                       
0x00000000 1507339    wz         777        128        0                       
0x00000000 1540108    wz         777        128        0                       
0x00000000 1638413    wz         600        524288     2          目标       
0x00000000 1703950    wz         600        2097152    2          目标       
0x00000000 1736719    wz         777        128        0                

由此可见,内存被删除了,因为创建的shmid没有了。
接下来就是有亲缘关系直间的通信:

 1 #include<unistd.h>                                                                                      
  2 #include<signal.h>
  3 #include<stdlib.h>
  4 #include<stdio.h>
  5 #include<sys/shm.h>
  6 #include <sys/ipc.h>
  7 void myfun(int signum)
  8 {
  9     return ;
 10 }
 11 int main()
 12 {
 13     int shmid;
 14     pid_t pid;
 15     char *p;
 16     shmid = shmget(IPC_PRIVATE,128,IPC_CREAT|0777);
 17     if(shmid < 0)
 18     {
 19         printf("create error\n");
 20         return -1;
 21     }
 22     printf("create share memory ok,shmid = %d\n",shmid);
 23     //system("ipcs -m");
 24     pid = fork();
 25     if(pid > 0)
 26     {   
 27         signal(SIGUSR2,myfun);
 28         p = (char*)shmat(shmid,NULL,0);
 29         if(p == NULL)
 30         {
 31             printf("create shmat error\n");
 32             return -1;
 33         }
 34         while(1)
 35         {
 36         //write
 37             printf("parent process start write:\n");
 38             fgets(p,128,stdin);
 39             kill(pid,SIGUSR1);//child process read
 40             pause();//wait child read
41 
 42         }
 43     }
 44     //read
 45     if(pid == 0)
 46     {
 47         p = (char*)shmat(shmid,NULL,0);
 48         if(p == NULL)
 49         {
 50             printf("child chmat error\n");
 51             return -1;
 52         }
 53         signal(SIGUSR1,myfun);
 54         while(1)
 55         {
 56             pause();//wait parent process write
 57             printf("share memory:%s\n",p);
 58             //printf("second share memory:%s\n",p);
 59             kill(getppid(),SIGUSR2);
 60         }
 61     }
 62         shmdt(p);
 63         shmctl(shmid,IPC_RMID,NULL);
 64         system("ipcs -m");
 65     return 0;
 66 }                                

结果:

wz@wz-machine:~/linux/shm$ ./shmctl 
create share memory ok,shmid = 1376266
parent process start write:
sd
share memory:sd

parent process start write:
sd
share memory:sd

parent process start write:
sd
share memory:sd

接下来就是无亲缘关系直间的通信,我们先实现单向的通信,双向通信按照单向通信再建立一个共享内存就可以了。
ser.c

 1 #include<unistd.h>                                                                                      
  2 #include<signal.h>
  3 #include<stdlib.h>
  4 #include<stdio.h>
  5 #include<sys/shm.h>
  6 #include <sys/ipc.h>
  7 struct mybuf
  8 {
  9     int pid;
 10     char buf[100];
 11 };
 12 void myfun(int signum)
 13 {
 14     return ;
 15 }
 16 int main()
 17 {
 18     int shmid;
 19     pid_t pid;
 20     int key;
 21     struct mybuf *p;
 22     key = ftok("./a.c",'a');
 23     if(key < 0)
 24     {
 25         printf("create key fail\n");
 26         return -1;
 27     }
 28     shmid = shmget(key,128,IPC_CREAT|0777);
 29     if(shmid < 0)
 30     {
 31         printf("create error\n");
 32         return -1;
 33     }
 34     printf("create share memory ok,shmid = %d\n",shmid);
 35     //system("ipcs -m");
 36         signal(SIGUSR2,myfun);
 37         p = (struct mybuf*)shmat(shmid,NULL,0);
 38         if(p == NULL)
 39         {
 40             printf("create shmat error\n");
 41             return -1;
 42         }
 43         //get client pid
 44         p->pid = getpid();//write server pid to share memory
 45         pause();//wait client read server pid
 46         pid =p->pid;        
  47         while(1)
 48         {
 49         //write
 50         printf("parent process start write:\n");
 51         fgets(p->buf,128,stdin);
 52         kill(pid,SIGUSR1);//child process read
 53         pause();//wait child read
 54 
 55         }
 56         shmdt(p);
 57         shmctl(shmid,IPC_RMID,NULL);
 58         system("ipcs -m");
 59     return 0;
 60 }                                                     

cli.c

 1 #include<unistd.h>                                                                                      
  2 #include<signal.h>
  3 #include<stdlib.h>
  4 #include<stdio.h>
  5 #include<sys/shm.h>
  6 #include <sys/ipc.h>
  7 struct mybuf
  8 {
  9     int pid;
 10     char buf[100];
 11 };
 12 void myfun(int signum)
 13 {
 14     return ;
 15 }
 16 int main()
 17 {
 18     int shmid;
 19     pid_t pid;
 20     int key;
 21     struct mybuf *p;
 22     key = ftok("./a.c",'a');
 23     if(key < 0)
 24     {
 25         printf("create key fail\n");
 26         return -1;
 27     }
 28     shmid = shmget(key,128,IPC_CREAT|0777);
 29     if(shmid < 0)
 30     {
 31         printf("create error\n");
 32         return -1;
 33     }
 34     printf("create share memory ok,shmid = %d\n",shmid);
 35     //system("ipcs -m");
 36         signal(SIGUSR1,myfun);
 37         p = (struct mybuf*)shmat(shmid,NULL,0);
 38         if(p == NULL)
 39         {
 40             printf("create shmat error\n");
 41             return -1;
 42         }
 43         //get server  pid
 44         //read share memory
 45         pid = p->pid;
 46         //write client pid to share memory    
  47         p->pid = getpid();
 48         //kill signal
 49         kill(pid,SIGUSR2);
 50         p->pid = getpid();//write server pid to share memory
 51         while(1)
 52         {
 53             pause();//wait server write data to share memory
 54             printf("client process receve data from share memory :%s",p->buf);//read
 55             kill(pid,SIGUSR2);//server write
 56         
 57         }
 58         shmdt(p);
 59         shmctl(shmid,IPC_RMID,NULL);
 60         system("ipcs -m");
 61     return 0;
 62 }                                            

接下来,我说一下这个通信的过程

1.由于客户端和服务端没有血缘关系,所以没办法直接通信,所以获取同一个pid来进行通信,利用信号来进行同步。
2.用一个图来说吧
这里写图片描述

不足之处请多多指教!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值