system V共享内存

1.共享内存
共享内存是最快的IPC形式(inter-process communication),一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不在涉及到内核,即进程不再通过执行进入内核的系统调用来传递彼此的数据
在这里插入图片描述两个进程的PCB各自维护着一个进程地址空间。当两个进程要进行通信时:
①.操作系统在内存中开辟一个内存块。
②.通过两个进程的页表,将内存中的内存块映射到两个进程的进程地址空间中。
③.此时两个进程便建立了连接。
④.进行通信时,两个进程只需要访问自己的进程地址空间即可,操作系统会通过页表访问内存中的内存块。
共享内存就是让不同的进程,看到同一块内存块。

   挂接:**将内存中创建好的内存块映射到进程的地址空间中。**
   去关联:不想通信时,取消进程和内存的映射关系。

2.共享内存函数
shmget函数
功能:用来创建共享内存
int shmget(key_t key,size_t size,int shmflg)
key:这个共享内存段名字
size:这个共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式是一样的
返回值:
创建成功返回共享内存的标识符,注意不是key值。
创建失败,返回-1。

①.关于key
管理共享内存并不是在管理内存块本身,而是在管理共享内存对应的结构体:
key值就是共享内存的标识,让想要通信的进程双方看到同一块公共资源。
系统中既然存在很多个共享内存,操作系统势必要将它们管理起来,管理也是使用先描述再组织的方式。
struct shm
{
key_t key;
size_t size;
//…
}
每创建一个共享内存,就会创建一个结构体对象,并且赋一个不同的key值。
所以这个key值就代表着一块唯一的共享内存。

如何让key值保持唯一?
函数ftok()
这就需要用到另一个函数ftok(),来生成一个独一无二的key值。

key_t ftok(const charpathname,int proj_id)
pathname:文件路径名,可以随意写,一般我们都写成当前路径"."。
proj_id:项目ID,同样可以自定义,但是不能为0。
返回值:独一无二的key值。
该函数会根据我们传的路径名和项目id值生成一个key值,具体实现是通过一些算法实现的,我们不需要在意,只需要得到key值就行。
所以,在开辟共享内存之前,必须先使用ftok函数来生成一个独一无二的key值,这样才能保证我们内存块的标识是唯一的。
当两个进程通过key值和共享内存挂接起来后,就可以进行通信了。
结论:key值就是用来标识共享内存的唯一性的。
②.size
size是用来指定开辟的共享内存是多大的,以字节为单位。
一般指定的大小是4KB的整数倍。也可以是任意值。
操作系统在开辟共享内存的时候是以4KB为单位的。每次开辟的共享内存,最小也是4KB的。
假设我们指定4097字节大小的共享内存,但是在内存中实际开辟的共享内存是2
4KB的。
但是在使用的时候只能使用4097字节的空间,剩下的空间用户无法使用,操作系统也不会用,就浪费掉了。
③.shmflg
常用的shmflg
IPC_CREAT:创建共享内存,如果不存在,创建新的,如果存在,获取相关信息。
IPC_EXCL:无法单独使用,必须与其他标志组合使用。
IPC_CREAT|IPC_EXCL:创建共享内存,如果不存在,则创建,如果存在,错误返回
④.返回值
在这里插入图片描述
在这里插入图片描述
shmid也是连续的小整数,它和文件描述符fd一样,也是让用户使用的。
用户层使用shmid而不是key值是为了让用户层和系统层解耦。
结论:shmid是供用户使用的共享内存标识符。
shmget系统调用就是用来让通信双方获取同一块共享内存的。
key:不要在应用层使用,只用来在内核中标识shm的唯一性 类似于fd
shmid:应用这个

shmctl函数
在这里插入图片描述
ipcs -m 是一个UNIX/Linux系统下的命令,用于显示共享内存段的信息。具体来说,它会列出系统中当前存在的共享内存段,并显示它们的标识符、键值、权限、大小等信息。
使用 ipcs -m 命令可以方便地查看系统中共享内存段的状态,包括它们的使用情况、占用的内存大小等。这对于诊断共享内存相关的问题或监视系统资源使用情况非常有用。

此时创建的5个共享内存就显示出来了。
此时,进程早已经结束了,但是共享内存还是存在,没有随进程的结束而消失。
共享内存的生命周期随内核,不随进程。

    指令:ipcrm -m shimd
    功能:删除指定shimd标识的共享内存。

此时所有的共享内存就被删除了,但是需要我们手动的一个个去删除。
由于指令也是shell上运行的进程,也是属于用户层,所以操作共享内存时,使用的时shmid,而不是key值。
用命令行的形式未免也太麻烦了,所以有系统调用shmctl也可以用来删除共享内存,而且是自动的。
考虑使用shmctl函数
功能:用于控制共享内存
int shmctl(int shmid,int cmd,struct shmid_ds*buf)
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作
IPC_STAT
在这里插入图片描述

上图所示就是共享内存的数据结构struct_shmid_ds,也就是用来描述共享内存的数据结构,这只是其中的一部分
该结构体的第一个成员变量是struct ipc_prem shm_prem,具体内容如红色箭头所指。可以看到,key值就在这里,共享内存就是通过它来标识唯一性的
当cmd是IPC_STAT时,我们就可以获取到共享内存的属性。
IPC_STAT
在这里插入图片描述
IPC_RMID 删除共享内存段
IPC_SET
在进程有足够权限的前提下,把共享内存的当前值设置为shmid_ds数据结构中给出的值

buf:指向一个保存着共享内存模式的模式状态和访问权限的数据结构

shmat函数
功能:将共享内存段挂接到进程地址空间中
voidshmat(int shmid,const voidshmaddr,int shmflg)
shmid:创建共享内存后返回的标识符
shmaddr:指定共享内存映射到进程地址空间中的地址,一般设置成NULL,让系统自动来设置
shmflg:它的两个可能取值是SHM_RND和SHM_RONLY
返回值:成功返回一个指针,指向共享内存的第一个节,失败返回-1

key_t key=ftok(NAME_PATH,PROJ_ID);    //创建共享内存 
int shmid=shmget(key,MEM_SIZE,IPC_CREAT|IPC_EXCL);
char*start=(char*)shmat(shmid,nullptr,0);  //挂接共享内存
 

shmdt函数
功能:将共享内存段与当前进程脱离
int shmdt(const void*shmaddr)
shmaddr:由shmat所返回的指针
要去关联的共享内存映射在进程地址空间中的起始地址。

shmget-创建共享内存
shmat-将共享内存段挂接到进程地址空间中
shmdt-将共享内存段与当前进程脱离
shmctl-用于控制共享内存

#pragma once

#include <iostream>
#include <cstdlib>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string pathname = "/home/whb/109/109/lesson30";
const int proj_id = 0x11223344;

const std::string filename = "fifo";

// 共享内存的大小,强烈建议设置成为n*4096
const int size = 4096; // 4096*2

key_t GetKey()
{
    key_t key = ftok(pathname.c_str(), proj_id);
    if(key < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(1);
    }

    return key;
}

std::string ToHex(int id)
{
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "0x%x", id);
    return buffer;
}

int CreateShmHelper(key_t key, int flag)
{
    int shmid = shmget(key, size, flag);
    if(shmid < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        exit(2);
    }

    return shmid;
}

int CreateShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT|IPC_EXCL|0644);
}

int GetShm(key_t key)
{
    return CreateShmHelper(key, IPC_CREAT/*0也可以*/);
}

bool MakeFifo()
{
    int n = mkfifo(filename.c_str(), 0666);
    if(n < 0)
    {
        std::cerr << "errno: " << errno << ", errstring: " << strerror(errno) << std::endl;
        return false;
    }

    std::cout << "mkfifo success... read" << std::endl;
    return true;
}

client.cc
int main()
{
    key_t key = GetKey();
    int shmid = GetShm(key);
    char *s = (char*)shmat(shmid, nullptr, 0);
    std::cout << "attach shm done" << std::endl;
    int fd = open(filename.c_str(), O_WRONLY);
    
    // sleep(10);
    // TODO
    // 共享内存的通信方式,不会提供同步机制, 共享内存是直接裸露给所有的使用者的,一定要注意共享内存的使用安全问题
    // 
    char c = 'a';
    for(; c <= 'z'; c++)
    {
        s[c-'a'] = c;
        std::cout << "write : " << c << " done" << std::endl;
        sleep(1);

        // 通知对方
        int code = 1;
        write(fd, &code, sizeof(4));
    }

    shmdt(s);
    std::cout << "detach shm done" << std::endl;
    close(fd);
    return 0;
}
serve.cc
int main(){
	bool r=MakeFifo();
	if(!r) return 1;
	key_t key=Getkey();
	int shmid=CreateShm(key);
	char*s=(char*)shmat(shmid,nullptr,0);
	int fd=open(filename.c_str(),O_RDONLY);
	while(true){
		int code=0;
		ssize_t n=read(fd,&code,sizeof(code));
		if(n>0){
			std::cout<<n<<std::endl;
			sleep(1); 
		}
		else if(s==0){
			break;
		}
	} 
	shmdt(s);
	shmctl(shmid,IPC_RMID,nullptr);
}

3.共享内存的特征:
**共享内存的生命周期随内核
共享内存是所有进程间通信速度最快的的方式
共享内存可以提供较大的空间
共享内存的通信方式,不会提供同步机制,共享内存是直接裸露给所有的使用者的,一定要注意内存的使用安全问题
**

  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值