进程间通信之共享内存初步

基本概念

共享内存
  共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
  
这里写图片描述

用管道或者消息队列传递数据
  内核为每个IPC对象维护一个数据结构
这里写图片描述

用共享内存传递数据

这里写图片描述

相关API

shmget函数

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

  • 示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/wait.h>

#define ERR_EXIT(m) \
  do \
  { \
    perror(m); \
    exit(-1); \
  }while(0)

typedef struct _T
{
  char name[64];
  int age;
}Teacher;

//没有IPC_CREAT
int main01()
{
  key_t key;
  int shm_id;
  key = ftok("./",'b');


  //不存在会报错
  shm_id = shmget(key,sizeof(Teacher),0666); 
  if(shm_id == -1)
  {
    if(errno == ENOENT)//如果没有使用IPC_CREAT,打开共享内存,共享内存文件不存在
    {
      printf("no such shm\n");
    }

    perror("shmget");
  }

  return 0;
}

//使用IPC_CREAT
int main02()
{
  key_t key;
  int shm_id;
  key = ftok("./",'b');

  //存在使用旧共享内存
  //不存在就创建新的共享内存
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); 
  if(shm_id == -1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }

    perror("shmget");
  }

  return 0;
}

//IPC_CREAT和IPC_EXCL在一起
int main03()
{
  key_t key;
  int shm_id;
  key = ftok("./",'b');

  //存在会报错
  //不存在就创建新的共享内存
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT|IPC_EXCL); 
  if(shm_id == -1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }
    if(errno == EEXIST)//已经存在就报错
    {
      printf("have created shm\n");
    }
    ERR_EXIT("shmget");
  }

  return 0;
}

shmat函数

  • 功能:将共享内存段连接到进程地址空间
  • 原型
    void *shmat(int shmid, const void *shmaddr, int shmflg);
  • 参数
    shmid: 共享内存标识
    shmaddr:指定连接的地址
    shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
  • 返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1

  • shmaddr和shmflg的组合关系对共享内存地址的影响

shmaddr和shmflg的组合关系共享内存最终地址
shmaddr为NULL,不管shmflg核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY表示连接操作用来只读共享内存

* 示例代码

//shmat函数的使用
int main04()
{
  key_t key;
  int shm_id;
  void * p = NULL;
  Teacher t1;

  bzero(&t1,sizeof(t1));
  strcpy(t1.name,"abcdef");
  t1.name[strlen(t1.name)] = '\0';
  t1.age = 32;
  key = ftok("./",'b');

  //存在会报错
  //不存在就创建新的共享内存
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); 
  if(shm_id == (void*)-1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }
    if(errno == EEXIST)//已经存在就报错
    {
      printf("have created shm\n");
    }
    ERR_EXIT("shmget");
  }

  //shmat让共享内存可以被进程使用--在进程的地址空间就可以使用该块内存--每个进程shmat以后得到的地址不一样--属于自己的共享内存地址
  p = shmat(shm_id,NULL,0);//让本进程链接到共享内存--得到一个地址--共享内存的首地址--p
  if(p ==(void*)-11)
  {
    ERR_EXIT("shmat");
  }

  memcpy(p,&t1,sizeof(t1));

  printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);

  return 0;
}

shmdt函数

  • 功能:将共享内存段与当前进程脱离
  • 原型
    int shmdt(const void *shmaddr);
  • 参数
    shmaddr: 由shmat所返回的指针
  • 返回值:成功返回0;失败返回-1

注意:将共享内存段与当前进程脱离不等于删除共享内存段

shmctl函数

  • 功能:用于控制共享内存
  • 原型
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • 参数
    shmid:由shmget返回的共享内存标识码
    cmd:将要采取的动作(有三个可取值)
    这里写图片描述
    buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
  • 返回值:成功返回0;失败返回-1
  • 示例代码
//shmctl+shmdt删除共享内存
int main05()
{
  key_t key;
  int shm_id;
  void * p = NULL;
  Teacher t1;
  int opt = 0;

  bzero(&t1,sizeof(t1));
  strcpy(t1.name,"abcdef");
  t1.name[strlen(t1.name)] = '\0';
  t1.age = 32;
  key = ftok("./",'b');

  //存在会报错
  //不存在就创建新的共享内存
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); 
  if(shm_id == (void*)-1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }
    if(errno == EEXIST)//已经存在就报错
    {
      printf("have created shm\n");
    }
    ERR_EXIT("shmget");
  }

  //shmat让共享内存可以被进程使用--在进程的地址空间就可以使用该块内存--每个进程shmat以后得到的地址不一样--属于自己的共享内存地址
  p = shmat(shm_id,NULL,0);//让本进程链接到共享内存--得到一个地址--共享内存的首地址--p
  if(p == (void*)-1)
  {
    ERR_EXIT("shmat");
  }

  memcpy(p,&t1,sizeof(t1));

  printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);

  printf("Please input your select:\n1-->don't delete\t2-->delete it force\n");
  scanf("%d",&opt);

  if(opt == 2)
  {
    //删除之前要脱离映射unmap

    shmdt(p);

    shmctl(shm_id,IPC_RMID,NULL);
  }
  else if(opt == 1)
  {
    pause();
  }
  else
  {
    exit(0);
  }

  return 0;
}

综合实例代码

涉及到共享内存的边界操作–是否真的立即删除。详细内容看代码注释复习!!!

//如果共享内存被别的程序占用,连接数/引用计数不为0,则删除共享内存不会立马被删除
//出现一个现象---共享内存key值变成了0,引用计数减1,且此时的共享内存已经不能被其他重新shmat的进程读取了
//只能被在删除之前就已经shmat的进程读取,重启一个进程重新shmat的时候由于key已经变为0,所以新开的进程是不能再继续读取共享内存的数据的
//只有当连接共享内存的所有进程都死掉Linux内核才会删除共享内存
//是否能直接删除共享内存--关键是看在删除以后有无进程链接到该块内存--等同于shmctl+shmdt删除之前有多少进程已经执行了shmat
//多进程中shmat和shmctl操作的时序关系不同--导致删除共享内存是否能直接删除
int main()
{
  key_t key;
  int shm_id;
  void * p = NULL;
  Teacher t1;
  int opt = 0;
  pid_t pid = 0;

  bzero(&t1,sizeof(t1));
  strcpy(t1.name,"abcdef");
  t1.name[strlen(t1.name)] = '\0';
  t1.age = 32;
  key = ftok("./",'b');

  //存在会报错
  //不存在就创建新的共享内存
  shm_id = shmget(key,sizeof(Teacher),0666|IPC_CREAT); 
  if(shm_id == -1)
  {
    if(errno == ENOENT)
    {
      printf("no such shm\n");
    }
    if(errno == EEXIST)//已经存在就报错
    {
      printf("have created shm\n");
    }
    ERR_EXIT("shmget");
  }
  pid = fork();
  if( pid == -1)
  {
    ERR_EXIT("fork");
  }
  else if(pid == 0)
  {
    p = shmat(shm_id,NULL,0);//子进程链接到共享内存
    sleep(3);
    if(p == (void*)-1)
    {
      ERR_EXIT("shmat");
    }
    printf("child p:%d\n",(int)p);

    //子进程脱离并删除共享内存--此时不一定直接删除

    //如果父进程在此之前已经shmat了--共享内存的key变为0--但因为父进程并没有退出也没有删除共享内存--引用计数不为0--不删除

    //如果直到此时此刻,父进程还没有shmat--没有连接到共享内存--
    //此时子进程可以直接删除共享内存--因为再也没有多余的进程链到该块内存
    shmdt(p);
    shmctl(shm_id,IPC_RMID,NULL);

    //子进程结束
    printf("child quit\n");
    exit(0);
  }
  wait(NULL);//如果wait放在这里--程序会在父进程shmat的时候异常结束
            //父进程会等待子进程删除共享内存之后再shmat--
            //此时共享内存已经被删除,Linux内核已经回收该块内存--
            //意味着需要重新shmget一块共享内存,再shmat才可以给父进程使用
            //父进程在共享内存已经被删除的情况下直接shmat是会core dump的


  //shmat让共享内存可以被进程使用--在进程的地址空间就可以使用该块内存--属于自己进程的用户空间的内存地址
  p = shmat(shm_id,NULL,0);//让本进程链接到共享内存--得到一个地址--共享内存的首地址--p
  if(p == (void*)-1)
  {
    ERR_EXIT("shmat");
  }
  printf("parent p:%d\n",(int)p);
  memcpy(p,&t1,sizeof(t1));

  printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);
  sleep(5);
  printf("Please input your select:\n1-->read shm after child delete it\n2-->delete it force\n");
  scanf("%d",&opt);

  if(opt == 2)
  {
    //删除之前要脱离映射unmap
    shmdt(p);
    //删除共享共享内存--不一定立即删除--要看是否还有其他进程链接到该进程
    shmctl(shm_id,IPC_RMID,NULL);
  }
  else if(opt == 1)
  { 
    //这里不一定能读取成功:

    //如果父进程在子进程删除共享内存之前之前就已经shmat的,就可以读取内存里面的数据

    //如果父进程在子进程结束之后/删除共享内存之后才shmat--读取失败--实际上在shmat的时候就已经失败了--返回-1无效地址
    printf("name : %s\tage:%d\n",((Teacher*)p)->name,((Teacher*)p)->age);
  }
  else
  {
    pause();
  }

  //如果wait放在这里--程序正常运行
  //父进程会在子进程删除共享内存之前shmat--所以在子进程删除共享内存之后还可以读取
  //wait(NULL);
  return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值