03_008内存映射原理_虚拟内存区域vm_area_struct详解,和mmap系统钓调用完全分析

news2024/11/15 5:00:55

前言

上一个记录中的 虚拟地址里的虚拟内存区域没有说的很完全
这次补充一下 同时记录一些 物理地址空间 内存映射原理 最后直接通过进程使用函数完成虚拟空间到物理空间的映射

物理地址空间

物理地址是处理器在系统总线上看到的地址。使用RISC的处理器通常只实现一个物理地址空间,外围设备和物理内存使用统一 的物理地址空间。有些处理器架构把分配给外围设备的物理地址区域称为设备内存。
处理器通过外围设备控制器的寄存器访问外围设备,寄存器分为控制寄存器,状态寄存器和数据寄存器三大类。
外围设备的寄存器通常被连续地编址,处理器对外围设备寄存编址方式分为两种: I/O映射方式(I/O-mapped) ,内存映射方式(memory-mapped).
举个例子
Io映射x86上 给io专门分配了空间 访问要啥in out指令之类的
内存映射是使用risc 实现一个物理地址空间 外围设备和物理内存使用统一的物理地址空间
处理器可以像访问一个内存单元这样一样访问外围设备 不需要提供专门的io指令
进程这些程序只能通过虚拟地址访问外设 所以内核需要提供一些接口把这些外围设备的物理地址映射到虚拟地址空间中 ----> ioremaap
感觉是这个样子
在这里插入图片描述
ARM64架构分为两种内存类型:
正常内存(Normal Memory) :包括物理内存和只读存在器(ROM) ;
设备内存(Device Memory) :指分配给外围设备寄存器的物理地址区域;
当把这两种内存都映射到虚拟空间的时候 需要用 vm_area_struct 来描述
应用程序只能通过虚拟地址访问外设寄存器,内核提供API函数来把外设寄存器的物理地址映射
到虚拟地址空间。

内存映射原理

下面这一段简单描述
创建内存映射时,在进程的用户虚拟地址空间中分配一个虚拟内存区域 vm_area_struct 。
内核采用延迟分配物理内存的策略,在进程第一-次访问虚拟页的时候, 产生缺页异常。如果是文件映射,那么分配物理页,把文件指定区间的数据读到物理页中,然后在页表中把虚拟页映射到物理页。
如果是匿名映射,就分配物理页,然后在页表中把虚拟页映射到物理页。(就像映射iic的控制器地址)

下面这一段具体一点描述 有点绕 ,哪天被绕晕了没事 下面还有实例
进程启动后在虚拟地址空间 给映射创建映射区 先在用户空间调用mmp 在这个进程的虚拟地址空间中找一段空闲满足要求的连续虚拟地址 作为内存虚拟映射区 对这个区域初始化 插入这个进程的虚拟区域链表vm_area_struct
比如进程在读写这块虚拟地址 去查询这个页表 发现这一段内存并不在物理页上 因为虽然建立了映射关系 但是还没有把这个文件从磁盘中移到内存当中 所以这次就发生了缺页异常中断 内存就请求磁盘调度到这个页面

调页过程: 先在交换缓存空间 switch chache当中进行查找 如果没有就通过not_page()这个函数把这个缺页从磁盘调入内存 之后进程就对它进行读写操作 如果在读写操作中改变页面内容了 一段时间后系统会自动回写一些脏页面到磁盘中
后面继续讲解 比如修改后的脏页面 不会立刻更新到文件当中 我们可以调用ms_sync()强制进行回写

vm_area_struct结构体分析

对一个vm_area区域进行描述 有成员标志位用来表示该虚拟区域是否能可读可写 是否支持共享
有file_operation成员 用来表示对这个区域的操作函数

struct vm_ area_ struct {

//这两个成员分别用来保存该虚拟内存空间的首地址和末地址后第--个字节的地址。
unsigned long vm_ start;
unsigned long vm_ end;

struct vm_ area_ struct *vm_ next, *vm_ prev;
//^分别VMA链表的前后成员连接操作
//如果采用链表组织化,会影响到它搜索速度问题,解决此问题采用红黑树(每个进程结构体mm_struct中都
//创建一棵红黑树,将VMA作为一个节点加入到红黑树中,这样可以提升搜索速度)
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
x VMAs below us in the VMA rbtree and its ->vm_ prev. This helps
x 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;
//指向内存描述符,即虚拟内存区域所属的用户虚拟地址空间
pgprot_ t Vm_ page_ prot; 
保护位,即访问权限
/*标志
#define VM_ READ
0x00000001
#define VM_ WRITE
0x00000002
#define VM_ EXEC
0x00000004
#define VM_ _SHARED 00000008 */
unsigned long Vm_ flags;
/*上面的宏为了支持查询一个文件区间被映射到哪些虚拟内存区域,把--个文件映射到的所有虚拟内存区域加入该文件地地址空间结构
address_ space的成员i_ _mmap指 向的区域树*/
struct {
    struct rb_ node rb;
    unsigned long rb_ subtree_ last;
    } shared;

/*把虚拟内存区域关联的所有anon__vma实例串联起来,一个虚拟内存区域会关联到父进程的anon__vma实例和自己的anon__vma实例*/
struct list_ head anon_ vma_ chain;
/*指向一个anon__vma实例,结构anon__vma用来组织匿名页被映射到的所有的虚拟地址空间*/
struct anon_ vma *anon vma; /* Serialized by page_ table_ lock 
/*
虚拟内存操作集合  fileoperation下面的各种操作函数
struct vm_ operations_ struct{
void (*open) (struct vm_ area_ struct *area); // 在创建虚拟内存区域时调用open方法
void (*close) (struct vm_ area_ struct *area); //在删除虛拟内存区域时调用close方法
int (*mremap) (struct vm_ area_ struct*area); //使用系统调用mremap移动虚拟内存区域时调用mremap方法
int (*fault) (struct vm_ fault *vmf); //访问文件映射的虛拟页时,如果没有映射到物理页,生成缺页异常,
异常处理程序调用fault就去来把文件的数据读到文件页缓存当中
. //与fault类似, 区别是huge_ fault方法针对使用透明巨型页的文件映射
int (*huge_ fault) (struct vm_ fault *vmf, enum page_ entry_ size pe_ size) ;
读文件映射的虚拟页时,如果没有映射到物理页,生成缺页异常,异常处理程序除了读入正在访问的文件页,
还会预读后续的文件页,调用map_ pages方法在文件的页缓存中分配物理页
void (*map_ pages) (struct vm_ fault *vmf,
pgoff_ _t start_ pgoff, pgoff_ t end_ pgoff);
//第一次写私有的文件映射时,生成页错误异常,异常处理程序执行写时复制,
调用page_ mkwrite 方法以通知文件系统页即将变成可写,以便文件系统检查是否允许写,或者等待页进入合适的状态。
int (*page_ mkwrite) (struct vm_ fault *vmf) ; .
*/
const struct Vm_ operations_ struct *vm_ ops;
/* Information about our backing store: */
unsigned long vm_ pgoff;
//文件偏移,单位是页
struct file * vm_ file;
//文件,如果是私有的匿名映射,该成员是空指针。
void * Vm_ private_ data;
//指向内存区的私有数据
}

系统调用实战

先分清c库提供的mmap()函数 是在应用层使用的
内核层是linux内核提供的mmap()函数 不要搞混了

函数介绍 c库mmap

1、mmap0----创建内存映射
#include <sys/mman.h>
void *mmap(void *addr,size_ t length, int prot, int flags, int fd, off t offset);
为什么要使用mmap映射呢 直接读写文件不香吗???
1.系统调用mmap():进程创建匿名的内存映射,把内存的物理页映射到进程的虚拟地址空间。
2.进程把文件映射到进程的虚拟地址空间,可以像访问内存一样访问文件,不需要调用系统调用read()/write()访问文件,
从而避免用户模式和内核模式之间的切换,提高读写文件速度。
3.两个进程针对同一个文件创建共享的内存映射,实现共享内存。

代码

进程1 映射磁盘的文件 从物理空间到虚拟空间 给里面写入一堆结构体people
过15秒再读一下这个文件

#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;
    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");

}
进程2 也映射相同的物理地址到进程2的虚拟地址空间 修改这个空间的值 然后ummmap 再退出

```c
#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);   

}

此时能发现进程1过了15s后 内容被修改了

代码具体原理分析

上面不就改了文件内容吗 但是背后的原理其实为 mmap做了什么好事情
mmap内存映射的实现过程主要分为三个阶段:

(一):进程启动映射过程并在虚拟地址空间当中为映射创建映射区域

1.进程在用户空间调用mmap也就是上面那个函数。

2.在当前进程的地址空间当中寻找一段连续的空虚的虚拟地址

3.给这块虚拟地址分配一个vm_area_struct的结构并对其各个区域进行初始化

4.将新键的虚拟结构插入到虚拟地址空间(mm_struc)的链表或者红黑树当中

(二):实现物理内存地址和虚拟地址的映射关系

1.为映射分配了新的虚拟地址空间之后通过待映射的文件描述符指针,在文件描述符表当中找到对应的文件描述符链接到内核已经打开的文件描述符集当中的struct_file,这个struct_file维护着这个被打开的文件的各项信息

2.通过这个文件的结构体链接到file_operations,调用内核的mmap其函数原型为int mmap(struct filefilp,struct vm_area_structvma),请注意不是用户态的mmap

3.内核mmap函数通过虚拟文件系统当中的inode定位到文件的物理地址

4.通过reamp_pfn_range函数建立页表即实现了文件地址和虚拟地址的映射关系。

(三)

1.进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。

2.缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。

3.调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。

4.之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程





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

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

相关文章

递归函数(详解+实战)

目录 递归函数介绍 递归函数的作用 案例&#xff1a;实现10以内阶乘 递归思想 递归的编写 斐波那契数列(实战) 循环实现 递归实现 递归函数介绍 递归函数是指在函数的定义中调用函数本身的过程。递归函数可以用于解决那些可以通过将大问题拆分为更小的相似子问题来解决的…

窗口看门狗 WWDG

窗口看门狗介绍 Q: 什么是窗口看门狗&#xff1f; A: 窗口看门狗用于监测单片机程序运行时效是否精准&#xff0c;主要检测软件异常(独立看门狗检测的是硬件异常)&#xff0c;一般用于需要精准检测&#xff08;独立看门狗不太精准&#xff09;程序运行时间的场合。 窗口看门狗…

MySQL数据库操作篇3(聚合函数分组查询)

通过MySQL提供的聚合函数&#xff0c;可以很方便的进行一些计算来辅助查询&#xff0c;所谓聚合函数就是将表中的数据统计后进行的某种处理 分组查询可以理解成将一张表按照某个属性分成多张表&#xff0c;属性值相同的在一张表里 比如说学生表&#xff0c;按照性别这个属性分组…

java+大数据实战 短链项目

一.前言 1.1课程大致包含技术 首先选这套课的目的是 包含了我所学的大部分技术 比如springboot ssm redis kafka flink clickhouse 等 1.2 外界客观原因 就业环境一般 目前来看暂时还没但是有后续潜在的毕业 或者 离职 1.3技术追求 个人的技术追求暂时是在技术总监 技术…

vue3功能实现

在vue2中&#xff0c;要实现一些方法&#xff08;增删改查&#xff09;一般都是写在一起的。如下图所示&#xff1a; 但是在vue3中&#xff0c;实现一个方法需要用到很多文件。 方法定义方法如下&#xff1a; export function classSign(phone: string) {return sign_reques…

TypeScript基础篇 - 类型的思考方式

目录 重新认识&#xff1a;什么是类型&#xff1f; 类型是人类的思考方式 类型是错误检查的利器 总结&#xff1a;为什么我们用TS&#xff1f; 一张ai生成图~ 重新认识&#xff1a;什么是类型&#xff1f; 汽车可以跑怎么理解&#xff1f; car.run() 【面向对象】car imp…

【雕爷学编程】Arduino动手做(06)---KY-038声音传感器模块3

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

微服务架构Ribbon与OpenFeign的使用 【快速入门】

一、实现负载均衡&#x1f349; 1.什么是负载均衡&#x1f95d; 通俗的讲&#xff0c; 负载均衡就是将负载&#xff08;工作任务&#xff0c;访问请求&#xff09;进行分摊到多个操作单元&#xff08;服务器,组件&#xff09;上进行执行。 根据负载均衡发生位置的不同,一般分…

Terraform(一)

IaC 自动化配置与编排神器 - Terraform 1. 相关概念1.1 什么是 IaCIaC 的两种实现方式IaC 的好处IaC 优点IaC 工具为什么 IaC 对 DevOps 很重要&#xff1f; 1.2 什么是 Terraform1.3 Terraform 的主要特性1.4 Terraform 架构1.5 Terraform 核心概念1.6 Terraform 生命周期1.7 …

高等数学❤️第一章~第二节~极限❤️无穷小量与无穷大量~无穷小量概念及其性质与比较详解

【精讲】高等数学中的无穷小量概念及其性质与比较 博主&#xff1a;命运之光的主页 专栏&#xff1a;高等数学 目录 【精讲】高等数学中的无穷小量概念及其性质与比较 导言 一、无穷小量的概念 二、无穷小量的性质 三、无穷小量的比较 四、无穷小量的应用 必需记忆知识点…

【C++刷题集】-- day4

目录 选择题​​​​​​​ 单选 编程题 计算糖果⭐ 【题目解析】 【解题思路】 进制转换⭐​​​​​​​ 【题目解析】 【解题思路】 选择题 单选 1、 有以下程序 #include<iostream> #include<cstdio> using namespace std; int main() {int m 0123, n 123…

(文章复现)售电市场环境下电力用户选择售电公司行为研究(附matlab代码)

参考文献&#xff1a; [1]孙云涛,宋依群,姚良忠等.售电市场环境下电力用户选择售电公司行为研究[J].电网技术,2018,42(04):1124-1131. 1.基本原理 1 .1演化博弈 与古典博弈理论相比较&#xff0c;演化博弈假设参与人是有限理性的&#xff0c;参与人会根据自己和他人的经验选…

【前端】网页开发精讲与实战 CSS Day 4

&#x1f680;Write In Front&#x1f680; &#x1f4dd;个人主页&#xff1a;令夏二十三 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;前端 &#x1f4ac;总结&#xff1a;希望你看完之后&#xff0c;能对你有…

Vue+Ts+Echart使用以及后台接口对接逻辑【实战】

官网传送门 一.echarts介绍 是一个js插件 性能好可流畅远行PC和移动设备 兼容主流浏览器 提供很多图标,用户且可自行修改。 2.使用npm安装 npm install echarts二.echarts基本使用、自定义图例、选择7天日期查询图表数据内容 获取本地时间以及当前时间前几天后几天 /*** 获…

安装Visual Studio Installer Projects 2022插件

VS主界面--扩展--管理扩展--搜索VS插件“Visual Studio Installer Projects 2022”并安装。

JavaWeb(5)——HTML、CSS、JS 快速入门

一、JavaScript 对象 二、JavaScript BOM对象 和 DOM对象 关于BOM主要对 Window 和 location 进行说明&#xff1a; 三、JavaScript 事件监听 事件绑定 常见事件 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">…

ORA-01122 ORA-01208 故障处理---惜分飞

数据库突然故障ORA-01122 ORA-01208,导致实例crash Tue Jul 11 09:06:43 2023 Thread 1 cannot allocate new log, sequence 254989 Private strand flush not complete Current log# 3 seq# 254988 mem# 0: E:\APP\ADMINISTRATOR\ORADATA\xff\REDO03.LOG Thread 1 advanced …

23款奔驰E300加装原厂香氛负离子系统,清香宜人,久闻不腻

奔驰原厂香氛合理性可通过车内空气调节组件营造芳香四溢的怡人氛围。通过更换手套箱内香氛喷雾发生器所用的香水瓶&#xff0c;可轻松选择其他香氛。香氛的浓度和持续时间可调。淡雅的香氛缓缓喷出&#xff0c;并且在关闭后能够立刻散去。车内气味不会永久改变&#xff0c;香氛…

javascript(js)组成及使用

组成&#xff1a; js是由核心ECMAScript、文档对象模型DOM、浏览器对象模型BOM三部分组成。 ECMAScript&#xff1a;定义了语言的语法、类型、语句、关键字、保留字、操作符、对象。有普通模式和严格模式的区分。文档对象模型(DOM)&#xff1a;由XML经过扩展用于HTML的应用程序…