Linux【进程间通信】之共享内存篇

Linux【进程间通信】之共享内存篇

  • system V共享内存
  • 如何查看IPC资源
  • 认识必须的接口
  • 三、结合代码深入理解
  • shmid_ds结构分析
  • 共享内存的优缺点

特别提醒:本篇文章在linux_64下演示
Linux【进程间通信】之共享内存篇_第1张图片

system V共享内存

原理图
Linux【进程间通信】之共享内存篇_第2张图片
进程间通信需要让不同进程看到同一块内存资源。用户使用操作系统提供的接口在物理内存中申请一块资源,通过进程的页表将这段物理空间映射至进程地址空间,进程将这段虚拟地址的起始地址返回给用户

共享内存原理:

  • 共享内存和malloc有点像,区别在于malloc出来的内存只能让进程知道这块空间的地址,共享内存是通过开辟一块物理空间,分别映射至通信进程的虚拟地址空间中
  • 共享内存是一种通信方式,所有想通信的进程都可以用,所以操作系统中可能会同时存在很多个共享内存

共享内存也是通过先描述再组织方式;共享内存=物理内存块+共享内存的相关属性,共享内存的属性中会存在一个字段表示key

共享内存的生命周期随OS内核,所以需要在使用完毕后取消进程与内存的映射关系并释放内存

如何查看IPC资源

ipcs -m/q/s   查看共享内存
ipcrm -m  [shmid](共享内存id)  
删除共享内存:删除指令在应用层,要用到上层的shmid

认识必须的接口

可通过man 2/3查看接口具体信息
形成标识符key

key_t ftok(const char pathname, int proj_id ) 
获取key,参数为 路径名 | 八位非零有效数字

申请共享内存:

int shmget(key_t key, size_t size, int shmflg);    
key:来源于ftok(),能够唯一标识一块内存,设置进共享内存属性中,保证该共享内存在内核中的唯一性 size:shm(共享内存大小)  
shmflg:标记位 IPC_CREAT不存在shm就创建,存在就获取 
			IPC_EXCL|IPC_CREAT 不存在创建,存在出错返回,保证新创建的是新的shm
成功时,将返回一个有效的共享内存标识符

shm与进程关联

void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr:要把共享空间映射到虚拟地址哪里,一般是nullptr
shmflg:读写权限,一般是0
返回值:成功->共享空间的起始地址   失败-1

解除关联:shmdt(const void *shmaddr)
参数shmaddr:shmat()返回值

控制共享内存

**int shmctl(int shmid, int cmd, struct shmid_ds *buf); 
应用层使用,要用上层的共享内存id 
cmd:各种宏,IPC_PMID表示立即删除共享内存    
buf:是一个结构体类型指针,暂时设置成nullptr即可

三、结合代码深入理解

共享内存级进程通信
1.创建共享内存
2.关联共享内存
3.开始通信
4.通信结束,取消关联,删除共享内存

工具实现

//comm.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#define PATHNAME "."//共享内存路径
#define PROJ_ID 0x66 //八位非零有效数字
//系统分配共享内存是以4KB为单位的,如果是4097,内核会向上取整
#define MAX_SIZE 4096

key_t getKey()
{
    //能够唯一标识一块内存,设置进共享内存属性中,保证该共享内存在内核中的唯一性
    key_t k= ftok(PATHNAME,PROJ_ID);
    if(k<0)
    {
        //cin,cout,cerr对应stdin,stdout,stderr,0,1,2
        //标准错误输出流,输出当前发生的错误信息
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(-1);
    }
    return k;
}

//IPC_CREAT不存在shm就创建,存在就获取
//IPC_EXCL|IPC_CREAT 不存在创建,存在出错返回,保证新创建的是新的shm
int getShmHelper(key_t k,int flags)
{
    //shmid和k  类似于于锁和钥匙的关系,
    //相当于现实世界中学号和身份证号,都是指定某一个人,为了解耦,用两个标识
    //应用层shmid,系统调用用K,即便系统调用改变,也不影响上层使用
    //shmid要把k设置进共享内存属性中,用来在内核中标识唯一性
    int shmid=shmget(k,MAX_SIZE,flags);
    if(shmid<0)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(2);
    }
    return shmid;
}
int getShm(key_t k)//获取shm
{
    return getShmHelper(k,IPC_CREAT);
}
int CreateShm(key_t k)//创建shm
{
    //0600共享内存权限
    return getShmHelper(k,IPC_CREAT|IPC_EXCL|0600);
}

//shm与进程关联
void* attachShm(int shmid)
{
    void * mem = shmat(shmid,nullptr,0);//linux下指针占8个字节
    if((long long)mem==-1L)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
        exit(3);
    }
    return mem;
}
//解除关联
void detachShm(void* start)
{
    if(shmdt(start)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}
//删除共享空间
void delShm(int shmid)
{
    //IPC_PMID立即删除共享内存  
    if(shmctl(shmid,IPC_RMID,nullptr)==-1)
    {
        std::cerr<<errno<<":"<<strerror(errno)<<std::endl;
    }
}
#endif

获取共享内存,通信的的发送方

//shm_client.cc
#include "comm.hpp"
#include 


int main()
{
    key_t k=getKey();
    printf("key: 0x%x\n",k);

    int shmid=getShm(k);
    printf("shmid: %d\n",shmid);

    //sleep(5);

    //关联shm
    char* start=(char*)attachShm(shmid);
    printf("attach success,address start:%p\n",start);


    //使用
    const char* str="我是client,我正在给你通信";
    pid_t id=getpid();
    int cnt=1;
    while (true)
    {
        sleep(3);
        //不需要建立缓冲区,直接往shm里面拷贝数据
        snprintf(start,MAX_SIZE,"%s[my_pid:%d][消息编号:%d]",str,id,cnt++);
        // snprintf(buffer, sizeof(buffer), "%s[pid:%d][消息编号:%d]", message, id, cnt++);
        // memcpy(start, buffer, strlen(buffer)+1);
    }
    
    //取消关联
    detachShm(start);
    return 0;

}

负责共享内存的创建以及删除,通信的接收方

//shm_server.cc
#include "comm.hpp"
#include 
//由于此文件负责创建shm,一定要先运行
int main()
{
    key_t k=getKey();
    printf("key: 0x%x\n",k);

    
    int shmid=CreateShm(k);
    printf("shmid: %d\n",shmid);
    // sleep(5);
    //关联shm
    char* start=(char*)attachShm(shmid);
    printf("attach success,address start:%p\n",start);

    //使用
    while (true)
    {
        printf("client say:%s\n",start);
        // struct shmid_ds ds;
        // shmctl(shmid,IPC_STAT,&ds);
        // printf("获取属性:size: %d,pid: %d,myself: %d,key:0x%x",\
        // ds.shm_segsz,ds.shm_cpid,getpid(),ds.shm_perm.__key);
        sleep(1);
    }
    
    //取消关联
    detachShm(start);

    sleep(10);

    //删除共享内存
    delShm(shmid);
    return 0;
}

Makefile文件

.PHONY:all
all:shm_client shm_server

shm_client:shm_client.cc
	g++ -o $@ $^ -std=c++11
shm_server:shm_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f shm_client shm_server

shm_server.cc运行页面
Linux【进程间通信】之共享内存篇_第3张图片

shmid_ds结构分析

或许对以下代码存在疑惑,让我们用man查一下shmctl
Linux【进程间通信】之共享内存篇_第4张图片
shmid_ds是存放着共享内存的属性的结构体,每一个字段都对应一种属性
Linux【进程间通信】之共享内存篇_第5张图片
其中最关键的是ipc_prem里的__key字段,当使用shmget的时候,key会被设置进共享内存的属性中,用来标识该共享内存在内核中的唯一性
Linux【进程间通信】之共享内存篇_第6张图片

共享内存的优缺点

1)是所有通信方式最快的,能大大减少数据的拷贝次数【不考虑输入输出的情况:管道需要4次 ,IPC只需要两次】

Linux【进程间通信】之共享内存篇_第7张图片
2)共享内存生命周期随OS,不是随着进程----->进程退出他并没有消失

3)共享内存不对数据做任何保护,没有同步和互斥操作,同一个数据可能被读多次。客户端和服务端没做保护,如果想做保护要用到信号量,对共享内存进行保护,写完通过读端进行读取。

.

你可能感兴趣的:(Linux系统编程,开发语言,centos,linux,后端)