【Linux】一篇文章搞定 进程间通信 之 共享内存

1. 共享内存原理及图解

共享内存通过页表映射到进程的进程虚拟地址空间共享区中,进程通过操控共享区的内容,间接操作共享内存中的内容

不同的进程将同一块共享内存映射到自己的共享区中,通过操作各自的共享区,以达到进程间数据交换

在这里插入图片描述

2. 共享内存相关函数及命令

2.1 创建共享内存函数

shmget函数

int shmget(key_t key, size_t size, int shmflg);

功能:开辟一段共享内存,用共享内存标识符key去标识这块内存,并返回共享内存的操作句柄

头文件:

  • sys/ipc.h
  • sys/shm.h

参数:

  • key : 共享内存标识符,相当于共享内存的身份证,唯一标识共享内存区域
  • size : 需要开辟的共享内存大小,单位是字节
  • shmflg
含义
IPC_CREAT 若共享内存不存在,则创建
IPC_EXCL 与IPC_CREAT一同使用,为了确保共享内存已经存在时,会报告错误

返回值:创建成功返回 共享内存操作句柄 ,创建失败返回 -1

共享内存标识符:用于唯一标识某块共享内存的,进程可以通过共享内存标识符找到这块共享内存

共享内存操作句柄:用于操作这块内存的

代码演示shmget函数

#include <stdio.h>     
#include <unistd.h>    
#include <sys/ipc.h>   
#include <sys/shm.h>   
                       
#define  KEY 0x89898989
                       
int main()             
{
    
                          
  int shmid = shmget(KEY,1024,IPC_CREAT);
                       
  if(shmid < 0)        
  {
    
                        
    perror("shmget");  
    return -1;         
  }                    
                       
  printf("shmid : %d\n",shmid);
                                         
  return 0;
}                      
[gongruiyang@localhost Testshm]$ ./shm 
shmid : 425985

2.2 查看共享内存命令

ipcs命令用于查看所有进程间通信(IPC)信息,-m选项是所有信息中过滤出共享内存段的进程间通信信息

ipcs -m

命令演示

[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
key        shmid      owner      权限        字节     nattch   status
0x00000000 262144     gongruiyan 600        524288     2       dest 
0x89898989 425985     gongruiyan 0          1024       0       dest      
0x00000000 393218     gongruiyan 600        524288     2       dest 
  • key : 共享内存标识符,若为
  • shmid : 共享内存操作句柄
  • owner : 所属用户,谁创建的
  • 权限 : 对该共享内存的操作权限
  • 字节 : 共享内存大小
  • nattch : 附加的进程数量
  • status : 共享内存段的状态,dest表示销毁

上面的程序创建了一个进程标识符为0x89898989的共享内存段,虽然程序已经执行结束,但是共享内存段仍然存在,说明共享内存的生命周期的结束不是跟随程序的,而是跟随内核的

2.3 删除共享内存命令

ipcrm用于删除进程间通信资源,-m代表删除的是共享内存段

ipcrm -m 共享内存操作句柄

命令演示

[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
key        shmid      owner      权限        字节     nattch   status
0x00000000 262144     gongruiyan 600        524288     2       dest 
0x89898989 425985     gongruiyan 0          1024       0       dest      
0x00000000 393218     gongruiyan 600        524288     2       dest 
[gongruiyang@localhost TestPipeCS]$ ipcrm -m 425985
[gongruiyang@localhost TestPipeCS]$ ipcs -m
------------ 共享内存段 --------------
键          shmid       拥有者     权限         字节     nattch      状态      
0x00000000 262144     gongruiyan 600        524288     2          dest      
0x00000000 393218     gongruiyan 600        524288     2          dest

2.4 创建共享内存时赋予读写权限

由上面程序创建出来的共享内存的权限为0可以看出,我们对该共享内存无任何权限,我们可以在创建共享内存的时候对共享内存属性进行修改,增加 权限

int shmid = shmget(KEY,1024,IPC_CREAT | 0664);

2.5 将自定义的共享内存映射到进程虚拟地址的共享区中

void *shmat(int shmid, const void *shmaddr, int shmflg);

功能:将指定的共享内存段映射到进程虚拟地址的共享区中的某一块中

头文件:

  • sys/types.h
  • sys/shm.h

参数:

  • shmid : 共享内存操作句柄
  • shmaddr : 映射到共享区的哪一块地址,需要映射到哪一块地址就填哪块地址;如果填入NULL就代表想让操作系统替我们选择一个合适的地址,
  • shmflg :
含义
SHM_RDONLY 规定当前进程对该共享内存有 只读权限
0 规定当前进程对该共享内存有 读写权限

返回值:返回 映射到的共享区 的地址

程序演示

#include <stdio.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

#define  KEY 0x89898989

int main()
{
    
    
  int shmid = shmget(KEY,1024,IPC_CREAT | 0664);

  if(shmid < 0)
  {
    
    
    perror("shmget");
    return -1;
  }

  void* lp = shmat(shmid,NULL,0); //让系统分配共享区,该进程对共享内存的权限为 可读可写
  printf("%p\n",lp);                                            

  return 0;
}
[gongruiyang@localhost Testshm]$ ./shm
0xffffffffffffffff

2.6 单进程对共享内存进行的读写操作

程序思路:

  1. 创建一块共享内存标识符为KEY的共享内存
  2. 将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
  3. 本进程向共享内存中写入数据
  4. 本进程从共享内存中读取数据

程序演示:单进程对共享内存进行读写操作

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

#define  KEY 0x89898989

int main()
{
    
    
  //1.创建一块共享内存标识符为KEY的共享内存
  int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
  if(shmid < 0)
  {
    
    
    perror("shmget");
    return -1;
  }

  //2.将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
  void* lp = shmat(shmid,NULL,0);

  //3.本进程向共享内存中写入数据
  const char* w_buf = "Hello World!\n";
  strcpy((char*)lp,w_buf);

  //4.本进程从共享内存中读取数据
  printf("%s\n",lp);

  return 0;                                                           
}

2.7 多进程读写分离操作共享内存

程序演示:一个进程写入数据 一个进程读取数据

读进程

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

#define KEY 0x89898989

int main()
{
    
    
    int shmid = shmget(KEY, 1024, IPC_CREAT | 0664);
    if(shmid < 0)
    {
    
    
        perror("shmget");
        return -1;
    }

    void* lp = shmat(shmid, NULL, 0);
    printf("%s\n", (char*)lp);

    while(1)
    {
    
    
        sleep(1);
    }
    return 0;
}

写进程

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

#define KEY 0x89898989

int main()
{
    
    
    int shmid = shmget(KEY, 1024, IPC_CREAT | 0664);
    if(shmid < 0)
    {
    
    
        perror("shmget");
        return -1;
    }

    const char* w_buf = "Hello World!\n";
    void* lp = shmat(shmid, NULL, 0);
    strcpy((char*)lp, w_buf);

    while(1)
    {
    
    
        sleep(1);
    }
    return 0;
}

进程读取共享内存中数据时,并不会将共享内存中的数据拿走,数据依然存在共享内存之中

2.8 将共享内存与进程进行分离

int shmdt(const void *shmaddr);

功能:将进程中附加到共享区的某个共享内存段进行分离开

头文件:

  • sys/types.h
  • sys/shm.h

参数:

  • shmaddr : shmat返回值

返回值:0 代表分离成功 ; -1 代表分离失败

程序演示

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

#define  KEY 0x89898989

int main()
{
    
    
  //1.创建一块共享内存标识符为KEY的共享内存
  int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
  if(shmid < 0)
  {
    
    
    perror("shmget");
    return -1;
  }

  //2.将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
  void* lp = shmat(shmid,NULL,0);


  while(1)
  {
    
    
    sleep(1);
  }
  return 0;
}
[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
键          shmid          拥有者  权限        字节       nattch     状态      
0x00000000 262144     gongruiyan 600        524288     2          dest    
0x89898989 524289     gongruiyan 664        1024       1                       
0x00000000 393218     gongruiyan 600        524288     2          dest

由此可以看出:附加在 共享内存标识符为0x89898989 上的进程数量nattch为1

进程分离后的程序及现象

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

#define  KEY 0x89898989

int main()
{
    
    
  //1.创建一块共享内存标识符为KEY的共享内存
  int shmid = shmget(KEY,1024,IPC_CREAT | 0664);
  if(shmid < 0)
  {
    
    
    perror("shmget");
    return -1;
  }

  //2.将创建出来的共享内存附加到本进程中,让系统分配共享区,权限为读写
  void* lp = shmat(shmid,NULL,0);
	
  //将本进程与共享内存分离  
  int ret_shmdt = shmdt(lp);
  if(ret_shmdt < 0)
    perror("shmdt");
  else
    printf("shmdt success!\n");                                        

  while(1)
  {
    
    
    sleep(1);
  }
  return 0;
}

[gongruiyang@localhost Testshm]$ ipcs -m
------------ 共享内存段 --------------
键        shmid        拥有者     权限        字节        nattch      状态      
0x00000000 262144     gongruiyan 600        524288     2          dest      
0x89898989 557057     gongruiyan 664        1024       0                       
0x00000000 393218     gongruiyan 600        524288     2          dest

2.9 操作共享内存属性

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

功能:操作共享内存的属性

头文件:

  • sys/types.h
  • sys/shm.h

参数:

  • shhmid : 共享内存操作句柄
  • cmd : 告诉shmctl函数需要做什么操作,操作可以填入以下的宏
含义
IPC_STAT 获取共享内存属性,将指定的共享内存属性填入buf中
IPC_SET 设置共享内存数,将指定的共享内存属性按照buf设置
IPC_RMID 删除共享内存,buf直接传递NULL
  • buf : 共享内存结构体

shmid_ds结构体源码

struct shmid_ds {
    
    
    struct ipc_perm shm_perm;    /* Ownership and permissions */
    size_t          shm_segsz;   /* Size of segment (bytes) */
    time_t          shm_atime;   /* Last attach time */
    time_t          shm_dtime;   /* Last detach time */
    time_t          shm_ctime;   /* Last change time */
    pid_t           shm_cpid;    /* PID of creator */
    pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
    shmatt_t        shm_nattch;  /* No. of current attaches */
    ...
};
struct ipc_perm {
    
    
    key_t          __key;    /* Key supplied to shmget(2) */
    uid_t          uid;      /* Effective UID of owner */
    gid_t          gid;      /* Effective GID of owner */
    uid_t          cuid;     /* Effective UID of creator */
    gid_t          cgid;     /* Effective GID of creator */
    unsigned short mode;     /* Permissions + SHM_DEST and SHM_LOCKED flags */
    unsigned short __seq;    /* Sequence number */
};

程序演示:获取共享内存属性,并输出共享内存大小

#include <stdio.h>  
#include <unistd.h>  
#include <sys/ipc.h>  
#include <sys/shm.h>  
  
#define  KEY 0x89898989  
  
int main()  
{
    
      
  int shmid = shmget(KEY,1024,IPC_CREAT | 0664);  
  if(shmid < 0)  
  {
    
      
    perror("shmget");  
    return -1;  
  }  
                                                  
  struct shmid_ds buf;
  shmctl(shmid, IPC_STAT, &buf);
  // 获取 共享内存段 大小
  printf("shm size : %ld字节\n",buf.shm_segsz);
                                     
  return 0;  
}            
[gongruiyang@localhost Testshm]$ ./shm 
shm size : 1024字节

2.10删除共享内存总结

  • 当使用ipcrm命令或者shmctl函数删除共享内存之后,共享内存段就被释放了
  • 共享内存标识符被设置为0x00000000,表示任何进程都无法通过之前的标识符来寻找该共享内存
  • 共享内存的状态会被设置成dest(destroy),表示被销毁
  • 如果这块被释放的共享内存块依然还有进程映射,即nattch不为0,则描述共享内存的结构体就不会被释放,直到所有依附于该共享内存的进程全部分离后,即nattch为0时,描述该共享内存的结构体才会被释放

猜你喜欢

转载自blog.csdn.net/weixin_45437022/article/details/112105356