Linux:IPC之共享内存

什么是共享内存?

  • 共享内存即指在Linux下,两个或多个进程所共享的一块在内存映射段(共享区) 上开辟好的物理内存空间,将这块物理内存空间映射到这些进程的虚拟地址空间中(通过页表)进行访问,当一个进程写入数据时,其他进程就可以读取这些数据,这就实现了进程间的通信,并且因为是直接读取共享内存中的数据,较其他方式省去了用户态与内核态的数据拷贝过程,所以这种方式的速度是最快的

  • 在Linux中,我们可以通过 “ipcs -m” 指令来查看当前系统下的共享内存状态
    Linux:IPC之共享内存

  • Linux内核为每个共享内存维护着一个结构shmid_ds,里面存储着各种相关信息;这个结构体定义在<sys/shm.h>中

使用共享内存

  • 使用共享内存需要进行以下五个步骤:
操作步骤 操作指令
创建共享内存 shmget
将共享内存映射连接至虚拟地址空间 shmat
进程对共享空间进行操作 - -
解除映射关系 shmdt
删除内存 shmctl
  • 注意,在进程没有主动释放共享内存时就退出的话,共享内存会一直存在,但由于其key的唯一标识性,下次进程启动后还会调用这块共享内存,所以在key值不变的情况下不必担心资源泄露

shmget:创建共享内存

  • 原型:int shemget(key_t key ,size_t size,int shemflg);
  • key:共享内存标识符,用来区别和制定共享内存
    1. 自己用difine定义无符号十六进制数字
    2. key_t key = ftok(".",PROJ_ID);
  • size:共享内存大小
  • shemflg:权限
    1. IPC_CREAT: 不存在创建;可与IPC_CREATE一起使用表示存在报错,不存在再创建
    2. mode_flags(二进制位指定权限)
  • 返回值:成功:标识符–操作句柄;失败:-1

shmat:将共享内存映射连接到虚拟地址空间

  • 原型:void* shmat(int shmid,const void* shmaddr,int shmflg);
  • shmid:创建共享内存返回的操作句柄;
  • shmaddr:映射首地址
    1. 如果为NULL,则连接到由内核选择的第一个可用地址上;推荐使用
    2. 如果不为NULL且未指定SHM_RND,则连接到shmaddr制定的地址上;
    3. 如果不为NULL且指定了SHM_RND,则连接到(shmaddr-(shmaddr mod SHMLBA))所表示的地址上。
  • shmflg:若被指定为SHM_RDONLY则以只读方式连接;否则输入0以读写方式连接;
  • 返回值:成功:映射首地址 ;失败:(void*) -1;

shmdt:解除映射关系

  • 原型:shmdt(const void *shmaddr)
  • shmaddr:之前得到的映射首地址

shmctl: 内存控制

  • 这一步我们主要实现的主要功能是:全部映射解除后再删除,在这期间拒绝后续其他进程的新的映射连接
  • 原型:int shmctl(int shmid,int cmd,struct shmid_ds *buf);
  • shmid:操作句柄
  • cmd:操作命令
    1. IPC_RMID 删除共享内存
    2. IPC_STAT 取到此内存区的shmid_ds结构,使其在shmid的指定区域上运行。
    3. IPC_SET 按照buf指向的结构中的值来设置该共享内存区的shmid_ds中的:shm_perm.uid/shm_perm.gid/shm_perm.mod
    4. SHM_LOCK 对共享内存存储区加锁实现互斥(Linux/Solaris独有且使用者为root进程)
    5. SHM_UNLOCK 解锁共享内存存储区(Linux/Solaris独有且使用者为root进程)
  • buf:获取/设置共享内存信息,不用存储的话写NULL;

实例

  • 假设我们现在需要让一个进程接收来自另一个进程的一串文本,按照上面的五步即可完成:
    MemWrite.c
#include<stdio.h>
#include<unistd.h>
#include<error.h>
#include<sys/shm.h>

#define my_key 0x199805

int MemShare(const char* data){
  int handle = shmget(my_key,1024,IPC_CREAT|0664);
  if(handle<0){
    perror("shmget error");
    return -1;
  }
  void* addr = shmat(handle,NULL,0);
  if(addr==(void*)-1){
    perror("shmat error");
    return -1;
  }
  int i = 1;
  int j = 10;
  while(j--){
    sprintf((char*)addr,"ShareMem test txt+%d\n",i++);
    sleep(1);
  }
  shmdt(addr);
  shmctl(handle,IPC_RMID,NULL);
  return 0;
}

int main(int argc,char* argv[]){
  char data[] = {0};
  //scanf("%s",data);
  MemShare(data);
  return 0;
}

MemRead.c

#include<stdio.h>
#include<unistd.h>
#include<error.h>
#include<sys/shm.h>
#include<string.h>

#define my_key 0x199805

int MemShareRead(){
  int handle = shmget(my_key,1024,IPC_CREAT|0664);
  if(handle<0){
    perror("shmget error");
    return -1;
  }
  char* addr = (char*)shmat(handle,NULL,SHM_RDONLY);
  if(addr==(void*)-1){
    perror("shmat error");
    return -1;
  }
  char*start = "";
  char history[1024] = "";
  while(1){
    if((!strcmp(addr,history))&&strcmp(start,history))//add与上一次输入一直相等,且不为初始值时就说明写段已经关闭了,此时退出;
      break;    
    printf("%s",addr);
    strcpy(history,addr);
    sleep(1);
  }
  shmdt(addr);
  shmctl(handle,IPC_RMID,NULL);
  return 0;
}

int main(int argc,char* argv[]){
  MemShareRead();
  return 0;
}

当两个进程运行起来,我们看到Read端读到了来自Write端的文本
Linux:IPC之共享内存

特点

  • 优点:因为不像其他通信方式需要进行数据拷贝,所以效率极高;
  • 缺点:由于每个进程都具有读写权限,使得共享内存中进程间同步就显得尤为重要,但它本身并不提供同步功能,这就需要使用信号量等手段实现同步;