【Linux内核】内存映射原理

【Linux内核】内存映射原理

物理地址空间

  1. 物理地址是处理器在总线上能看到的地址,使用RISC(Reduced Instruction Set Computing精简指令集)的处理器通常只实现一个物理地址空间,外围设备和物理内存使用统一的物理空间,

    有些架构的处理器把分配给外围设备的物理地址称为设备内存

    处理器通过外围设备控制器里面的寄存器来访问外围设备,寄存器分为控制寄存器,状态寄存器和数据寄存器

    外围设备的寄存器通常被常备连续的编址,处理器对外围设备寄存器的编址方式分为两种:I/O映射方式,内存映射方式

    • IO映射方式:x86处理器专门为外围设备单独提供一个空间,通过单独的指令(如in out)来访问这个空间的地址
    • 内存映射方式:外围设备和物理内存使用同一个物理空间,处理器以相同方式访问物理内存和外围设备,那么应用如何访问外围设备呢? 操作系统提供单独的函数将外围设备对应的地址映射到应用的虚拟内存中,从而使应用能访问设备

    ARM64架构分为两种内存类型:

    • 正常内存(Normal Memory) :包括物理内存和只读存在器(ROM) ;

      对于正常内存来讲可以设置共享属性,包括缓冲区的共享属性,共享属性分为不可共享,内部共享和外部共享.不可共享:制备处理器一个核心使用,内部共享:可以被多个核使用,外部共享:可以被DMI(DMI是指Direct Media InterfaceI(直接媒体接口))等共享1

    • 设备内存(Device Memory)∶指分配给外围设备寄存器的物理地址区域

      设备内存的共享属性总是外部共享

内存映射原理

​ 创建内存映射时,在进程的用户虚拟地址空间中分配一个虚拟内存区域,内核采用一个延迟物理内存分配策略,在进程第一次访问虚拟页的时候产生缺页异常2

​ 如果是文件映射,那么分配物理页把文件指定的区域数据读到物理页当中,然后把应用的虚拟页表映射到物理页,

​ 如果是匿名映射,就分配物理页,让后把虚拟页映射到物理页

内存映射即在进程的虚拟地址空间创建一个映射,分为两种:

  • 文件映射:文件支持内存映射,把文件的一段区域映射进程的虚拟空间,文件源式存储设备上的文件

    两个进程可以使用共享的文件映射实现共享内存

  • 匿名映射3:没有文件支持的映射,把物理内存映射到进程里的虚拟空间,无数据源

    匿名映射通常是私有映射,只可能出现在父进程和子进程之间

在进程的虚拟地址空间中,代码段和数据段是私有文件映射

未初始化的数据段,堆栈是私有的匿名映射

线程启动映射过程,并且在虚拟地址空间中为内存映射创建一个映射区,先在用户空间调用mmap函数,并且在内存虚拟空间中找到一段连续空闲的符合要求的虚拟地址,对这个区域初始化,并插入进程虚拟空间地址的链表,让后再内核中系统调用,实现文件的物理地址和进程虚拟地址之间一一对应的关系,进程开始查询文件内容,这是虽然进程虚拟内存与文件物理地址已经建立映射关系,但由于文件相关区域还没有加载到物理内存中,这是就会引发缺页中断,请求将磁盘内容调入内存当中,先在进程缓存空间swapcathe4中进行查找,如果没有找到,就通过nopage()把缺页从磁盘调入内存,如果你对内存进行写入改变内存内容,一段时间后,系统就会将改变的脏页写入硬盘,也可以使用函数强制及时同步.

数据结构

struct vm_area_struct {/* The first cache line has the info for VMA tree walking. */unsigned long vm_start;		/* Our start address within vm_mm. */unsigned long vm_end;		/* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next, *vm_prev;//虚拟内存连接
//红黑树实现struct rb_node vm_rb;/** Largest free memory gap in bytes to the left of this VMA.* Either between this VMA and vma->vm_prev, or between one of the* VMAs below us in the VMA rbtree and its ->vm_prev. This helps* get_unmapped_area find a free area of the right size.*/unsigned long rb_subtree_gap;/* Second cache line starts here. */struct mm_struct *vm_mm;//指向内存描述符,虚拟内存区域所属的用户虚拟空间	/* The address space we belong to. */pgprot_t vm_page_prot;	//权限呗	/* Access permissions of this VMA. */unsigned long vm_flags;		/* Flags, see mm.h. */
}
struct vm_operations_struct {//虚拟内存操作集合void (*open)(struct vm_area_struct * area);//创建虚拟内存区域void (*close)(struct vm_area_struct * area);//关闭虚拟内存区域int (*mremap)(struct vm_area_struct * area);//移动虚拟内存区域是调用int (*fault)(struct vm_area_struct *vma, struct vm_fault *vmf);//缺页异常处理,将文件加载到内存中int (*pmd_fault)(struct vm_area_struct *, unsigned long address,pmd_t *, unsigned int flags);void (*map_pages)(struct vm_area_struct *vma, struct vm_fault *vmf);/* notification that a previously read-only page is about to become* writable, if an error is returned it will cause a SIGBUS */int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);/* same as page_mkwrite when using VM_PFNMAP|VM_MIXEDMAP */int (*pfn_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
}

image-20210909224459409

系统调用

​ 应用程序通常使用C标准库提供的函数malloc()申请内存,glibc库的内存分配器ptmalloc使用brk或mmap向内核以页为单位申请虚拟内存然后把页划分为小块共应用程序使用

​ 应用程序可以使用mmap向内核申请虚拟内存

1、mmap()----创建内存映射

#include <sys/mman.h> void *mmap(void *addr,size_t length,int prot,int flags,int fd,off_t offset);

系统调用mmap():进程创建匿名的内存映射,把内存的物理页映射到进程的虚拟地址空间。进程把文件映射到进程的虚拟地址空间,可以像访问内存一样访问文件,不需要调用系统调用read()/write()访问文件,从而避免用户模式和内核模式之间的切换,提高读写文件速度。 两个进程针对同一个文件创建共享的内存映射,实现共享内存。

2、munmap()----删除内存映射

#include <sys/mman.h> int munmap(void *addr, size_t len); 

3、mprotect()----设置虚拟内存区域的访问权限

#include <sys/mman.h> int mprotect(void *addr, size_t len, int prot);

1、进程启动映射过程,并且在虚拟地址空间中为映射创建虚拟映射区域;
2、调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟的一一映射关系;
3、进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝。

上代码:

#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>typedef struct
{/* data */char name[4];int age;
} people;void main(int argc, char **argv)
{int fd, i;people *p_map;char temp;fd = open(argv[1], O_CREAT | O_RDWR | O_TRUNC, 00777);lseek(fd, sizeof(people) * 5 - 1, SEEK_SET);write(fd, "", 1);p_map = (people *)mmap(NULL, sizeof(people) * 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (p_map == (void *)-1){fprintf(stderr, "mmap : %s \n", strerror(errno));return;}close(fd);temp = 'A';for (i = 0; i < 10; i++){temp = temp + 1;(*(p_map + i)).name[1] = '\0';memcpy((*(p_map + i)).name, &temp, 1);(*(p_map + i)).age = 30 + i;}printf("Initialize.\n");sleep(15);munmap(p_map, sizeof(people) * 10);printf("UMA OK.\n");
}
#include <sys/mman.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>typedef struct 
{/* data */char name[4];int age;
}people;void main(int argc,char**argv)
{int fd,i;people *p_map;fd=open(argv[1],O_CREAT|O_RDWR,00777);p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);if(p_map==(void*)-1){fprintf(stderr,"mmap : %s \n",strerror(errno));return ;}for(i=0;i<10;i++){printf("name:%s age:%d\n",(*(p_map+i)).name,(*(p_map+i)).age);}munmap(p_map,sizeof(people)*10);   }

**我所有的学习笔记都在Github仓库里:https://github.com/fanxing-6/CPP-learning-notes **

,如果访问Github有问题也可以访问Gitee:CPP-learning-notes: C++学习笔记 (gitee.com)

本人能力有限,笔记难免有疏漏,如果有错误,欢迎各位关注公众号与我交流,一定会及时回复

请添加图片描述


  1. DMI是Intel(英特尔)公司开发用于连接主板南北桥的总线,取代了以前的Hub-Link总线。DMI采用点对点的连接方式,时钟频率为100MHz,由于它是基于PCI-Express总线,因此具有PCI-E总线的优势。DMI实现了上行与下行各1GB/s的数据传输率,总带宽达到2GB/s,这个高速接口集成了高级优先服务,允许并发通讯和真正的同步传输能力。它的基本功能对于软件是完全透明的,因此早期的软件也可以正常操作。 ↩︎

  2. 缺页异常,页缺失Page fault,指的是硬错误、硬中断、分页错误、寻页缺失、缺页中断、页故障等)指的是当软件试图访问已映射在虚拟地址空间中,但是目前并未被加载在物理内存中的一个分页时,由中央处理器的内存管理单元所发出的中断。通常情况下,用于处理此中断的程序是操作系统的一部分。如果操作系统判断此次访问是有效的,那么操作系统会尝试将相关的分页从硬盘上的虚拟内存文件中调入内存。而如果访问是不被允许的,那么操作系统通常会结束相关的进程。虽然其名为“页缺失”错误,但实际上这并不一定是一种错误。而且这一机制对于利用虚拟内存来增加程序可用内存空间的操作系统(比如Microsoft Windows和各种类 Unix 系统)中都是常见且有必要的。 ↩︎

  3. mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反映用户空间,从而可以实现不同进程间的文件共享。进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);来实现内存映射。从原型可知,存在一个参数为fd,根据fd,存在一种情况叫匿名映射,所谓匿名映射,表示不存在fd这么个真实的文件。实现匿名映射的方式主要有以下两种: 1、BSD 提供匿名映射的办法是fd =-1,同时 flag 指定为MAP_SHARE|MAP_ANON。 ptr = mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON,-1,0);2、SVR4 提供匿名映射的办法是 open /dev/zero设备文件,把返回的文件描述符,作为mmap的fd参数。 fd = open("/dev/zero",O_RDWR); /dev/zero 是一个特殊的文件,当你读它的时候,它会提供无限的空字符(NULL, ASCII NUL, 0x00) 一个作用是用它作为源,产生一个特定大小的空白文件。匿名内存映射适用于具有亲属关系的进程之间;由于父子进程之间的这种特殊的父子关系,在父进程中先调用mmap(),然后调用fork(),那么,在调用fork() 之后,子进程继承了父进程的所有资源,当然也包括匿名映射后的地址空间和mmap()返回的地址,这样父子进程就可以通过映射区域进行通信了;这里不是一般的继承关系,一般来说,子进程单独维护从父进程继承下来的一些变量,而mmap()返回的地址却是由父子进程共同维护的;对于具有亲属关系的进程之间实现共享内存的最好方式应该是采用匿名映射的方式。此时,不必指定具体的条件,只要设置相应的标志即可。 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/408099.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

javascript学习系列(1):数组中的map方法

最好的种树是十年前,其次是现在。歌谣 每天一个前端小知识 提醒你改好好学习了 知乎博主 csdn博主 b站博主 放弃很容易但是坚持一定很酷 我是歌谣 喜欢就一键三连咯 你得点赞是对歌谣最大的鼓励 1前言 在我们的日常开发中 不免会有很多需要处理数据的方法 本节主要说一说m…

【Linux内核】物理内存组织结构

【Linux内核】物理内存组织结构 系统调用mmap 物理内存组织结构 体系结构 目前多处理器系统有两种体系结构&#xff1a; 1&#xff09;非一致内存访问&#xff08;Non-Unit Memory Access&#xff0c;NUMA&#xff09;&#xff1a;指内存被划分成多个 内存节点的多处理器系…

脱离 Rails 看 Ruby

在开始这篇文章之前&#xff0c;我需要澄清一些事情。首先&#xff0c;这不是一篇关于 Ruby on Rails 的文章。如果您希望了解 Rails&#xff0c;每周&#xff08;甚至每小时&#xff09;都有相关的文章和 blog 出现&#xff0c;它们都对这个令人兴奋的框架的众多特性大加推崇&…

如何方便的让你的集合引发改变事件

在我们开发自定义控件的过程中,我们常常会给控件添加集合属性。比如定制Grid控件就会有Column集合。当集合属性发生变化时&#xff0c;比如添加新元素&#xff0c;删除新元素&#xff0c;我们要通知控件去重绘以反映新的变化。我们可以创建一个集合类&#xff0c;在类里添加一个…

[汇编语言]-第八章 div指令,伪指令dd,dup

1- div除法指令 (1) 除数: 有8位和16位两种,在一个寄存器或内存单元中. (2) 被除数: 默认放在AX和DX或AX中 除数为8位, 被除数为16位, 默认在AX中存放. 除数为16位, 被除数为32位, 在DX或AX中存放. AX存放低16位,DX存放高16位. (3) 结果 除数为8位, 则AL存储除法操作的商, AH存…

lambda表达式浅析【C++学习笔记】

lambda表达式浅析【C学习笔记】 基本用法: auto f [/*捕获列表*/](/*参数*/)->int /*后置返回值类型*/{/** 函数体*/};捕获列表: [] : 不捕获任何变量 [变量名] : 表示值捕获,不可修改 [] :按值捕获所有变量,不可修改 [&] : 按引用捕获可以修改 [this] : 在类中捕…

【Cocos2d-x for WP8 学习整理】(2)Cocos2d-Html5 游戏 《Fruit Attack》 WP8移植版 开源...

【Cocos2d-x for WP8 学习整理】&#xff08;2&#xff09;Cocos2d-Html5 游戏 《Fruit Attack》 WP8移植版 开源 原文:【Cocos2d-x for WP8 学习整理】&#xff08;2&#xff09;Cocos2d-Html5 游戏 《Fruit Attack》 WP8移植版 开源这一阵花了些时间&#xff0c;把 cocos2d-h…

碰撞,处理碰撞,发射 Learn Unreal Engine (with C++)

本文使用打砖块游戏举例 碰撞,处理碰撞 碰撞就相当于一个Actor进入另一个Box中,用这个思路就可以处理碰撞了 OnComponentBeginOverlap 当某些内容开始重叠此组件时调用的事件&#xff0c;例如玩家进入触发器。 **委托 事件 **1 AddDynamic( UserObject, FuncName ) 用于…

传送,条件加速 Learn Unreal Engine (with C++)

本文以吃豆人游戏为例UE4项目: 自制UE4 小游戏 (gitee.com) 传送 pawn进入box触发OnActorBeginOverlap获取目标位置,下一帧将pawn坐标更改为目标位置 首先需要重叠函数与开始重叠事件绑定 OnActorBeginOverlap.AddDynamic(this, &ATeleporterActor::OnOverlapBegin);头文件…

获取摄像机,摄像机切换Learn Unreal Engine (with C++)

摄像机应该是使用最普遍的组件了 获取摄像机,摄像机切换 新建C类(以CameraActor为父类) 将摄像机在地图中放置 头文件声明 virtual void BeginPlay() override;UPROPERTY(EditAnywhere, BlueprintReadWrite)UBoxComponent* OverlapVolume; // 盒体组件,用于检测人物碰撞UPR…

android报错及解决1--Bitmap加载时,报bitmap size exceeds VM budget

报错描述&#xff1a; 用Bitmap加载图片资源时&#xff0c;报错java.lang.OutOfMemoryError: bitmap size exceeds VM budget 原因分析&#xff1a; android系统限制&#xff0c;只给图片分配8M内存&#xff0c;超过就蹦。图片虽然几十K&#xff0c;可能是压缩格式&#xff0c;…

主角的创建与选择 Learn Unreal Engine (with C++)

主角创建有两种方式,本教程以SpaceshipBattle fanxingin/UE4项目 - 码云 - 开源中国 (gitee.com) 1. 新建游戏模式方式 新建一个蓝图类,选择游戏模式基础 在蓝图类的细节中将默认pawn类选择主角的蓝图类 在项目设置->地图和模式->默认模式->默认游戏模式 默认游…

控制`Actor`朝向,运动 Learn Unreal Engine (with C++)

控制Actor的朝向,以及Actor的运动 SpaceshipBattle fanxingin/UE4项目 - 码云 - 开源中国 (gitee.com) 控制Actor朝向鼠标 设置鼠标在游戏中可见 获取玩家控制器鼠标可见设置为true PC Cast<APlayerController>(GetController()); PC->bShowMouseCursor true;获取…

.Net开发人员应该下载的十种必备工具(三)

NDoc 编写代码文档资料几乎总是一项令人畏惧的任务。我所说的不是早期设计文档&#xff0c;甚至也不是更为详细的设计文档&#xff1b;我说的是记录类上的各个方法和属性。NDoc 工具能够使用反射来分析程序集&#xff0c;并使用从 C# XML 注释生成的 XML 自动为代码生成文档资料…

Actor范围内随机生成 Learn Unreal Engine (with C++)

Actor范围内随机生成 Learn Unreal Engine (with C) SpaceshipBattle fanxingin/UE4项目 - 码云 - 开源中国 (gitee.com) Actor范围内随机生成 新建box组件 SpawnArea CreateDefaultSubobject<UBoxComponent>(TEXT("SpawnArea"));RootComponent SpawnArea…

.Net开发人员应该下载的十种必备工具(二)

NUnit NUnit 是为 .NET 框架生成的开放源代码单元测试框架。NUnit 使您可以用您喜欢的语言编写测试&#xff0c;从而测试应用程序的特定功能。当您首次编写代码时&#xff0c;单元测试是一种测试代码功能的很好方法&#xff0c;它还提供了一种对应用程序进行回归测试的方法。NU…

子弹创建及发射 Learn Unreal Engine (with C++)

子弹创建及发射 Learn Unreal Engine (with C) SpaceshipBattle fanxingin/UE4项目 - 码云 - 开源中国 (gitee.com) 子弹的创建 声明: UPROPERTY(EditAnywhere, Category "Fire")TSubclassOf<ABullet> Bullet;实现: //在空组件处生产子弹GetWorld()->…