Linux进程通信(五)之 System V共享内存

system V共享内存

共享内存区是最快的IPC形式。

一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,

换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。

共享内存原理示意图

image-20250414145427607

image-20250415212029254

共享内存数据结构

struct shmid_ds 
{
 struct ipc_perm shm_perm; /* operation perms */
 int shm_segsz; /* size of segment (bytes) */
 __kernel_time_t shm_atime; /* last attach time */
 __kernel_time_t shm_dtime; /* last detach time */
 __kernel_time_t shm_ctime; /* last change time */
 __kernel_ipc_pid_t shm_cpid; /* pid of creator */
 __kernel_ipc_pid_t shm_lpid; /* pid of last operator */
 unsigned short shm_nattch; /* no. of current attaches */
 unsigned short shm_unused; /* compatibility */
 void *shm_unused2; /* ditto - used by DIPC */
 void *shm_unused3; /* unused */
};

image-20250416091910392

共享内存函数

shmget函数

image-20250415213347072

功能:用来创建共享内存(只需要创建一次)
原型
 int shmget(key_t key, size_t size, int shmflg);
参数
 key:这个共享内存段名字(保证不同的进程看到同一份共享内存)
 size:共享内存大小(单位字节)
 shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
 	IPC_CREAT(单独使用):如果申请的共享内存不存在,就创建,存在,就获取并返回
 	IPC_CREAT|IPC_EXCL:如果申请的共享内存不存在,就创建,存在,就出错并返回
 	(确保,如果申请成功了一个共享内存,那么这个共享内存一定是新的!)
 	IPC_EXCL:不单独使用!
返回值(共享内存标识符):成功返回一个非负整数,即该共享内存段的标识码;失败返回-1

谈谈key:

  1. key是一个数字,这个数字是几不重要,

​ 关键在于它在内核中必须具有唯一性,能够让不同进程进行唯一性标识。

  1. 第一个进程可以通过key创建共享内存,第二个之后的进程,只要拿着同一个key

​ 就可以和第一个进程看到同一个共享内存了!

  1. 对于一个已经创建好的共享内存,key在共享内存的描述对象中!

  2. 第一次创建的时候,必须有一个key了。怎么有key?

    image-20250415215844123

    ftok 是一套算法,pathnameproj_id 进行了数值的计算!

    pathnameproj_id 由用户自由指定!

    如果与操作系统内的 key 冲突了,就要修改pathnameproj_id

    key 为什么不让操作系统直接生成?因为操作系统不清楚哪个进程之间要通信!

    用户才清楚哪些进程要通信,所以由用户约定,用同一个pathnameproj_id 生成用一个key

  3. key - 类似 - 路径 都具有唯一性。

keyshmid都具有唯一性。

key 操作系统内标定唯一性。

shmid 只在进程内,用来表示资源的唯一性。


将进程挂接到共享内存

shmat函数

image-20250416102655924

image-20250416102721217

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

说明:

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

去关联

shmdt函数

image-20250416103610591

image-20250416103636412

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

shmctl函数

image-20250416111158797

image-20250416111628880

image-20250416111657266

功能:用于控制共享内存
原型
 int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
 shmid:由shmget返回的共享内存标识码
 cmd:将要采取的动作(有三个可取值)
 buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1

image-20250414145448611

实例代码

测试代码

comm.hpp

#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include<string>
#include<cstring>
#include<cstdlib>
#include"log.hpp"
Log log;
using namespace std;
const int size=4096;

//冲突了改值就行
const string pathname="/home/lll";
int proj_id=0x1234;

key_t Getkey()
{
    key_t k=ftok(pathname.c_str(),proj_id);
    if(k<0)
    {
        log(Fatal,"ftok error : %s",strerror(errno));
        exit(1);
    }
    log(Info,"ftok sucess,key is %d",k);
    return k;
}

int GetshmMen()
{
    int k=Getkey();
    int shmid=shmget(k,size,IPC_CREAT|IPC_EXCL);
    if(shmid<0)
    {
        log(Fatal,"create share memory error: %s",strerror(errno));
        exit(2);
    }
    log(Info,"create share memory success , shmid : %d",shmid);

    return shmid;
}

processa.cc

#include"comm.hpp"
#include"log.hpp"

int main()
{
    int shmid=GetshmMen();

    sleep(20);
    
    log(Debug,"processa quit..");
    return 0;
}

结果演示

查看共享内存的命令:ipcs -m

image-20250416090337702

共享内存的生命周期是随内核的!

用户不主动关闭,共享内存会一直存在。

除非内核重启(用户释放)

用户没有主动关闭算内存泄漏!

关闭释放共享内存的命令:ipcrm -m 该共享内存的shmid (不是用 key )

用户层(应用层)统一用 shmidkey 只给操作系统使用

image-20250416091121824


最终代码:

makefile

.PHONY:all
all:processa processb

processa:processa.cc
	g++ -o $@ $^ -std=c++11
processb:processb.cc
	g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
	rm -rf processa processb

comm.hpp

#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include<string>
#include<cstring>
#include<cstdlib>
#include"log.hpp"
Log log;
using namespace std;

//共享内存的大小一般建议是4096的整数倍
//设置为4097 实际上操作系统分配了4096*2的大小给共享内存
//虽然操作系统分配了4096*2 但是你设置了4097也只能用4097
const int size=4096;

//冲突了改值就行
const string pathname="/home/lll";
int proj_id=0x1234;

key_t Getkey()
{
    key_t k=ftok(pathname.c_str(),proj_id);
    if(k<0)
    {
        log(Fatal,"ftok error : %s",strerror(errno));
        exit(1);
    }
    log(Info,"ftok sucess,key is 0x%x",k);
    return k;
}

int GetshmMenHelper(int flag)
{
    int k=Getkey();
    int shmid=shmget(k,size,flag);
    if(shmid<0)
    {
        log(Fatal,"create share memory error: %s",strerror(errno));
        exit(2);
    }
    log(Info,"create share memory success , shmid : %d",shmid);

    return shmid;
}

int Createshm()
{
    return GetshmMenHelper(IPC_CREAT|IPC_EXCL|0666);
}

int Getshm()
{
    return GetshmMenHelper(IPC_CREAT);
}

processa.cc

#include"comm.hpp"
#include"log.hpp"

int main()
{
    //创建共享内存
    int shmid=Createshm();
    // log(Debug,"create share memory done");

    // sleep(5);
    //挂接
    char*shmaddr=(char*)shmat(shmid,nullptr,0);
    // log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);

    //ipc code
    //一旦有人把数据写到共享内存中,其实我们立马就可以看到了!!!
    //不需要经过系统调用,直接就能看到数据了!
    while(1)
    {
        cout<<"client say: "<<shmaddr<<endl;//直接访问共享内存
        sleep(1);
    }

    // sleep(5);

    //去关联
    shmdt(shmaddr);
    // log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);

    //释放共享内存
    shmctl(shmid,IPC_RMID,nullptr);
    // log(Debug,"destory share memory done,shmid: %d",shmid);

    
    // log(Debug,"processa quit..");
    return 0;
}

processb.cc

#include"comm.hpp"

int main()
{
    //一旦有了共享内存,挂接到自己的地址空间中,你直接把它当成你的内存空间来用即可!
    //不需要调用系统调用
    //获取shmid
    int shmid=Getshm();
    // log(Debug,"get share memory done");

    //挂接
    char*shmaddr=(char*)shmat(shmid,nullptr,0);
    // log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);

    //ipc code
    while(1)
    {
        cout<<"please enter:"<<endl;
        fgets(shmaddr,size,stdin);
    }

    //去关联
    shmdt(shmaddr);
    // log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);

    return 0;
}

运行结果:

image-20250416153634303

bytes nattch 有时间可以研究一下

注意:共享内存没有进行同步与互斥!

共享内存的特性+扩展代码

  1. 共享内存没有同步互斥之类的保护机制!

  2. 共享内存是所有进程间通信中,速度最快的! -> 拷贝少!

    管道至少拷贝两次!共享内存直接使用,不用拷贝!

  3. 共享内存内部的数据由用户自己维护!

看看共享内存的属性

image-20250416154649872

processa.cc

#include"comm.hpp"
#include"log.hpp"

int main()
{
    //创建共享内存
    int shmid=Createshm();
    // log(Debug,"create share memory done");

    // sleep(5);
    //挂接
    char*shmaddr=(char*)shmat(shmid,nullptr,0);
    // log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);

    //ipc code
    //一旦有人把数据写到共享内存中,其实我们立马就可以看到了!!!
    //不需要经过系统调用,直接就能看到数据了!
    struct shmid_ds shmds;
    while(1)
    {
        cout<<"client say: "<<shmaddr<<endl;//直接访问共享内存
        sleep(1);

        shmctl(shmid,IPC_STAT,&shmds);

        cout<<"shm size: "<<shmds.shm_segsz<<endl;
        cout<<"shm nattch: "<<shmds.shm_nattch<<endl;
        printf("shm key: 0x%x\n",shmds.shm_perm.__key);
        cout<<"shm mode: "<<shmds.shm_perm.mode<<endl;
        cout<<"--------------------------------"<<endl;
    }

    // sleep(5);

    //去关联
    shmdt(shmaddr);
    // log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);

    //释放共享内存
    shmctl(shmid,IPC_RMID,nullptr);
    // log(Debug,"destory share memory done,shmid: %d",shmid);

    
    // log(Debug,"processa quit..");
    return 0;
}

image-20250416155552707

同步机制(结合命名管道)

comm.hpp

#pragma once

#include <iostream>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include<string>
#include<cstring>
#include<cstdlib>
#include<sys/stat.h>
#include"log.hpp"

Log log;
using namespace std;

//共享内存的大小一般建议是4096的整数倍
//设置为4097 实际上操作系统分配了4096*2的大小给共享内存
//虽然操作系统分配了4096*2 但是你设置了4097也只能用4097
const int size=4096;

//冲突了改值就行
const string pathname="/home/lll";
int proj_id=0x1234;

#define FIFO_FILE "./myfifo"
#define MODE 0664

enum
{
    FIFO_CREATE_ERR=1,
    FIFO_OPEN_ERR,
    FIFO_DELETE_ERR
};

class Init
{
public:
    Init()
    {
         //创建管道
        int n=mkfifo(FIFO_FILE,MODE);
        if(n==-1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
    }
    ~Init()
    {
        //删除管道
        int m=unlink(FIFO_FILE);
        if(m==-1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};

key_t Getkey()
{
    key_t k=ftok(pathname.c_str(),proj_id);
    if(k<0)
    {
        log(Fatal,"ftok error : %s",strerror(errno));
        exit(1);
    }
    log(Info,"ftok sucess,key is 0x%x",k);
    return k;
}

int GetshmMenHelper(int flag)
{
    int k=Getkey();
    int shmid=shmget(k,size,flag);
    if(shmid<0)
    {
        log(Fatal,"create share memory error: %s",strerror(errno));
        exit(2);
    }
    log(Info,"create share memory success , shmid : %d",shmid);

    return shmid;
}

int Createshm()
{
    return GetshmMenHelper(IPC_CREAT|IPC_EXCL|0666);
}

int Getshm()
{
    return GetshmMenHelper(IPC_CREAT);
}

processa.cc

#include"comm.hpp"
#include"log.hpp"

int main()
{
    Init init;
    //创建共享内存
    int shmid=Createshm();
    // log(Debug,"create share memory done");

    // sleep(5);
    //挂接
    char*shmaddr=(char*)shmat(shmid,nullptr,0);
    // log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);

    int fd=open(FIFO_FILE,O_RDONLY);
    if(fd<0)
    {
        log(Fatal,"error string: %s , error code: %d\n",strerror(errno),errno);
        exit(FIFO_OPEN_ERR);
    }

    //ipc code
    //一旦有人把数据写到共享内存中,其实我们立马就可以看到了!!!
    //不需要经过系统调用,直接就能看到数据了!
    struct shmid_ds shmds;
    while(1)
    {
        char c;
        ssize_t s=read(fd,&c,1);
        if(s==0)break;
        else if(s<0)break;


        cout<<"client say: "<<shmaddr<<endl;//直接访问共享内存
        sleep(1);

        shmctl(shmid,IPC_STAT,&shmds);

        cout<<"shm size: "<<shmds.shm_segsz<<endl;
        cout<<"shm nattch: "<<shmds.shm_nattch<<endl;
        printf("shm key: 0x%x\n",shmds.shm_perm.__key);
        cout<<"shm mode: "<<shmds.shm_perm.mode<<endl;
        cout<<"--------------------------------"<<endl;
    }

    // sleep(5);

    //去关联
    shmdt(shmaddr);
    // log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);

    //释放共享内存
    shmctl(shmid,IPC_RMID,nullptr);
    // log(Debug,"destory share memory done,shmid: %d",shmid);
    close(fd);
    
    // log(Debug,"processa quit..");
    return 0;
}

processb.cc

#include"comm.hpp"

int main()
{
    //一旦有了共享内存,挂接到自己的地址空间中,你直接把它当成你的内存空间来用即可!
    //不需要调用系统调用
    //获取shmid
    int shmid=Getshm();
    // log(Debug,"get share memory done");

    //挂接
    char*shmaddr=(char*)shmat(shmid,nullptr,0);
    // log(Debug,"attach share memory done,shmaddr:0x%x",shmaddr);

    int fd=open(FIFO_FILE,O_WRONLY);
    if(fd<0)
    {
        log(Fatal,"error string: %s , error code: %d\n",strerror(errno),errno);
        exit(FIFO_OPEN_ERR);
    }
    //ipc code
    while(1)
    {
        cout<<"please enter:"<<endl;
        fgets(shmaddr,size,stdin);

        write(fd,"c",1);//通知对方
    }

    // sleep(5);

    //去关联
    shmdt(shmaddr);
    // log(Debug,"detach share memory done,shmaddr:0x%x",shmaddr);
    close(fd);
    return 0;
}

效果:

image-20250416161500035

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值