一文搞懂内存映射原理及使用方法

news2024/11/19 23:29:36

a. 内存映射原理

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

文件映射:文件支持的内存映射,把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件。

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

创建内存映射时,在进程的用户虚拟地址空间中分配一个虚拟内存区域。内核采用延迟分配物理内存的策略,在进程第一次访问虚拟页的时候,产生缺页异常。如果是文件映射,那么分配物理页,把文件指定区间的数据读到物理页中,然后在页表中把虚拟页映射到物理页。

如果是匿名映射,就分配物理页,然后在页表中把虚拟页映射到物理页。

内核必须提供数据结构,以建立虚拟地址空间的区域和相关数据所在位置之间的关联。例如,在映射文本文件时,映射的虚似内存区必须关联到文件系统在硬盘上存储文件内容的区域。

当然,给出的图示是简化的,因为文件数据在硬盘上的存储通常并不是连续的,而是分布到若干小的区域。内核利用address_space数据结构,提供一组方法从后备存储器读取数据。例如,从文件系统读取。因此address_space形成了一个辅助层,将映射的数据表示为连续的线性区域,提供给内存管理子系统。按需分配和填充页称之为按需调页法( demand paging)。它基于处理器和内核之间的交互,使用的各种数据结构如图。

进程试图访问用户地址空间中的一个内存地址,但使用页表无法确定物理地址(物理内存中没有关联页)。

处理器接下来触发一个缺页异常,发送到内核。

内核会检查负责缺页区域的进程地址空间数据结构,找到适当的后备存储器,或者确认该访问实际上是不正确的。

分配物理内存页,并从后备存储器读取所需数据填充。

借助于页表将物理内存页并入到用户进程的地址空间,应用程序恢复执行。

这些操作对用户进程是透明的。换句话说,进程不会注意到页是实际在物理内存中,还是需要通过按需调页加载。

学习直通车:

Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈ke.qq.com/course/4032547?flowToken=1040236

b.数据结构

虚拟内存区域分配给进程的一个虚拟地址范围,内核使用结构体vm_area_struct描述虚拟内存区域,主要核心成员如下:

我们知道struct mm_struct很重要,该结构提供了进程在内存中布局的所有必要信息。另外,它还包括下列成员,用于管理用户进程在虚拟地址空间中的所有内存区域。

/*mm_types.h*/structmm_struct{
	structvm_area_struct*mmap;		/* list of VMAs */
	structrb_rootmm_rb;
	u32vmacache_seqnum;/* per-thread vmacache */
	.....}

用户虚拟地址空间中的每个区域由开始和结束地址描述。现存的区域按起始地址以递增次序被归入链表中。扫描链表找到与特定地址关联的区域,在有大量区域时是非常低效的操作(数据密集型的应用程序就是这样)。因此vm_area_struct的各个实例还通过红黑树管理,可以显著加快扫描速度。增加新区域时,内核首先搜索红黑树,找到刚好在新区域之前的区域。因此,内核可以向树和线性链表添加新的区域,而无需扫描链表。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(

img-zknBf3AO-1638714343843)(C:\Users\wangzhen\AppData\Roaming\Typora\typora-user-images\image-20211205101738426.png)]

b.1 虚拟内存区域的数据结构

每个区域表示为vm_area_struct的一个实例,其定义(简化形式)如下:

/*
 * This struct defines a memory VMM memory area. There is one of these
 * per VM-area/task.  A VM area is any part of the process virtual memory
 * space that has a special rule for the page-fault handlers (ie a shared
 * library, the executable area etc).
 */structvm_area_struct{
	/* The first cache line has the info for VMA tree walking. *///这两个成员分别用来保存该虚拟内存空间的首地址和末地址后的第一个字节的地址
	unsignedlongvm_start;		/* Our start address within vm_mm. */
	unsignedlongvm_end;		/* The first byte after our end address
					   within vm_mm. */

	/* linked list of VM areas per task, sorted by address */
	structvm_area_struct*vm_next,*vm_prev;//各进程的虚拟内存区域链表,按地址排序

	structrb_nodevm_rb;//采用 红黑树(每个进程结构体mm_struct中都创建一颗红黑树,将VMA作为一个节点加入到红黑树中,提升搜索速度)

	/*
	 * 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.
	 */
	unsignedlongrb_subtree_gap;

	/* Second cache line starts here. *///指向内存描述符,即虚拟内存区域所属的用户虚拟地址空间
	structmm_struct*vm_mm;	/* The address space we belong to. *///保护位,即访问权限
	pgprot_tvm_page_prot;		/* Access permissions of this VMA. *//*
    #define VM_READ           0x00000001
    #define VM_WRITE		 0x00000002
    #define VM_EXEC           0x00000004
    #define VM_SHARED         0x00000008
    */
	unsignedlongvm_flags;		/* Flags, see mm.h. */

	/*
	 * For areas with an address space and backing store,
	 * linkage into the address_space->i_mmap interval tree.
	 */
	struct{
		structrb_noderb;
		unsignedlongrb_subtree_last;
	}shared;

	/*
	 * A file's MAP_PRIVATE vma can be in both i_mmap tree and anon_vma
	 * list, after a COW of one of the file pages.	A MAP_SHARED vma
	 * can only be in the i_mmap tree.  An anonymous MAP_PRIVATE, stack
	 * or brk vma (with NULL file) can only be in an anon_vma list.
	 *///把虚拟内存区域关联的所有的anon_vma实例串联起来,一个虚拟内存区域会关联到父进程的anon_vma实例和自己的anon_vma实例
	structlist_headanon_vma_chain;/* Serialized by mmap_sem &
					  * page_table_lock *///指向一个anon_vma实例,结构anon_vma用来组织匿名页被映射到的所有的虚拟地址空间
	structanon_vma*anon_vma;	/* Serialized by page_table_lock */

	/* Function pointers to deal with this struct. *//*
    <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);
	int (*page_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
	int (*pfn_mkwrite)(struct vm_area_struct *vma, struct vm_fault *vmf);
	int (*access)(struct vm_area_struct *vma, unsigned long addr, void *buf, int len, int write);
	const char *(*name)(struct vm_area_struct *vma);
	struct page *(*find_special_page)(struct vm_area_struct *vma, unsigned long addr);
    */
	conststructvm_operations_struct*vm_ops;

	/* Information about our backing store: *///文件偏移,单位是页
	unsignedlongvm_pgoff;		/* Offset (within vm_file) in PAGE_SIZE
					   units, *not* PAGE_CACHE_SIZE */
	structfile*vm_file;		/* File we map to (can be NULL). */
	void*vm_private_data;		/* was vm_pte (shared mem) 指向内存区的私有数据*/#ifndef CONFIG_MMU
	structvm_region*vm_region;	/* NOMMU mapping region */#endif
#ifdef CONFIG_NUMA
	structmempolicy*vm_policy;	/* NUMA policy for the VMA */#endif
	structvm_userfaultfd_ctxvm_userfaultfd_ctx;};

c. 系统调用和mmap内存映射

c.1 系统调用

应用程序通常使用C标准库提供的函数malloc()申请内存,glibc库的内存分配器ptmalloc使用brk或mmap向内核以页为单位申请虚拟内存,然后把页划分成小内存块分配给应用程序。默认的阈值是128kb,如果应用程序申请的内存长度小于阈值,ptmalloc分配器使用brk向内核申请虚拟内存,否则ptmalloc分配器使用mmap向内核申请虚拟内存。应用程序可以直接使用mmap向内核申请虚拟内存。

我们已经熟悉了内存映射相关的数据结构和地址空间操作,在本节中,我们将进一步讨论在建立映射时内核和应用程序之间的交互。就我们所知, C标准库提供了mmap 函数建立映射。在内核一端,提供了两个系统调用mmap和mmap2。两个函数的参数相同。

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

这两个调用都会在用户虚拟地址空间中的pos位置,建立一个长度为len的映射,其访问权限通过prot定义。 flags是一个标志集,用于设置一些参数。相关的文件通过其文件描述符fd标识。

mmap和mmap2之间的差别在于偏移量的语义( off)。在这两个调用中,它都表示映射在文件中开始的位置。对于mmap,位置的单位是字节,而mmap2使用的单位则是页( PAGE_SIZE)。因此即使文件比可用地址空间大,也可以映射文件的一部分。通常C标准库只提供一个函数,由应用程序用来创建内存映射。接下来该函数调用在内部转换为适合于体系结构的系统调用。可使用munmap系统调用删除映射。因为不需要文件偏移量,因此不需要munmap2系统调用,只需提供映射的虚拟地址。

参数start:指向欲映射的内存起始地址,通常设为 NULL,代表让系统自动选定地址,映射成功后返回该地址。

参数length:代表将文件中多大的部分映射到内存。

参数prot:映射区域的保护方式。可以分为以下几种方式的组合:

PROT_EXEC 映射区域可被执行

PROT_READ 映射区域可被读取

PROT_WRITE 映射区域可被写入

PROT_NONE 映射区域不能存取

参数flags:影响映射区域的各种特性。在调用mmap()时必须要指定MAP_SHARED 或MAP_PRIVATE。

MAP_FIXED 如果参数start所指的地址无法成功建立映射时,则放弃映射,不对地址做修正。通常不鼓励用此旗标。

MAP_SHARED对映射区域的写入数据会复制回文件内,而且允许其他映射该文件的进程共享。

MAP_PRIVATE 对映射区域的写入操作会产生一个映射文件的复制,即私人的“写入时复制”(copy on write)对此区域作的任何修改都不会写回原来的文件内容。

MAP_ANONYMOUS建立匿名映射。此时会忽略参数fd,不涉及文件,而且映射区域无法和其他进程共享。

MAP_DENYWRITE只允许对映射区域的写入操作,其他对文件直接写入的操作将会被拒绝。

MAP_LOCKED 将映射区域锁定住,这表示该区域不会被置换(swap)。

参数fd:要映射到内存中的文件描述符。如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,然后对该文件进行映射,可以同样达到匿名内存映射的效果。

参数offset:文件映射的偏移量,通常设置为0,代表从文件最前方开始对应,offset必须是分页大小的整数倍。

返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

错误代码:

EBADF 参数fd 不是有效的文件描述词

EACCES 存取权限有误。如果是MAP_PRIVATE 情况下文件必须可读,使用MAP_SHARED则要有PROT_WRITE以及该文件要能写入。

EINVAL 参数start、length 或offset有一个不合法。

EAGAIN 文件被锁住,或是有太多内存被锁住。

ENOMEM 内存不足。

我们回顾一下mmap内存映射原理的三个阶段:

进程启动映射过程,并且在虚拟地址空间为映射创建虚拟映射区域;

调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟的一一映射关系;

进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝。

munmap()----删除内存映射#include<sys/mman.h>
intmunmap(void*addr,size_tlen);mprotect()----设置虚拟内存区域的访问权限#include<sys/mman.h>
intmprotect(void*addr,size_tlen,intprot);//进程1
#include<sys/mman.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
typedefstruct{/* data */charname[4];intage;}people;voidmain(intargc,char**argv){intfd,i;people*p_map;chartemp;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++){(*(p_map+i)).name[1]='\0';memcpy((*(p_map+i)).name,&temp,1);(*(p_map+i)).age=30+i;temp=temp+1;}printf("Initialize.\n");sleep(15);munmap(p_map,sizeof(people)*10);printf("UMA OK.\n");}//进程2
#include<sys/mman.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
typedefstruct{/* data */charname[4];intage;}people;voidmain(intargc,char**argv){intfd,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);}//mprotect
#include<unistd.h>
#include<signal.h>
#include<malloc.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<sys/mman.h>
#define handle_error(msg) do{ perror(msg); exit(EXIT_FAILURE);}while(0)
staticchar*buffer;staticvoidhandler(intsig,siginfo_t*si,void*unused){printf("Get SIGSEGV at address : %p\n",si->si_addr);exit(EXIT_FAILURE);}intmain(intargc,char*argv[]){intpagesize;structsigactionsa;sa.sa_flags=SA_SIGINFO;sigemptyset(&sa.sa_mask);sa.sa_sigaction=handler;if(sigaction(SIGSEGV,&sa,NULL)==-1)handle_error("siaction");pagesize=sysconf(_SC_PAGE_SIZE);if(pagesize==-1)handle_error("sysconf");buffer=memalign(pagesize,4*pagesize);if(buffer==NULL)handle_error("memalign");printf("start of region : %p\n",buffer);if(mprotect(buffer+pagesize*2,pagesize,PROT_READ)==-1)handle_error("mprotect");for(char*p=buffer;;)*(p++)='A';printf("for completed.\n");exit(EXIT_SUCCESS);return0;}

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

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

相关文章

2. 因子(factor)、缺失数据(na)、字符串、时间序列数据

课程视频链接&#xff1a;https://www.bilibili.com/video/BV19x411X7C6?p1 本笔记参照该视频&#xff0c;笔记顺序做了些调整【个人感觉逻辑顺畅】&#xff0c;并删掉一些不重要的内容 系列笔记目录【持续更新】&#xff1a;https://blog.csdn.net/weixin_42214698/category_…

字符的编码与乱码

目录 前言 1 计算机中字符的编码分类 1.1 常见非Unicode编码 1.1.1 ASCII 1.1.2 ISO 8859-1 1.1.3 Windows-1252 1.1.4 GB2312 1.1.5 GBK 1.1.6 GB18030 1.1.7 Big5 1.1.8 编码汇总 1.2、Unicode编码 1.2.1 UTF-32 1.2.2 UTF-16 1.2.3 UTF-8 1.2.4 Unicode编码…

A1032 Sharing

Powered by:NEFU AB-IN Link 文章目录A1032 Sharing题意思路代码A1032 Sharing 题意 To store English words, one method is to use linked lists and store a word letter by letter. To save some space, we may let the words share the same sublist if they share the s…

2032.1.7 学习总结

1.string与int之间的相互转化问题 &#xff08;1&#xff09;int转string&#xff1a;使用to_string函数 引入头文件: #include<string> 转int&#xff0c;float&#xff0c;double都可以 string to_string (int val); string to_string (long val); string to_string…

Java外卖点餐系统小程序+数据库源码(带本地部署搭建文档)源码免费分享!

亲测Java在线点餐系统小程序数据库源码带本地部署搭建文档 需要源码学习可私信我获取。 小程序外卖扫码点餐为客户提供的是最方便的饮食方式,以快速、便捷的点餐业务送货上门为 -客户服务,这省去了客户很多不必要的时间和麻烦,给商家带来更多利益。同时,小程序外卖扫码点餐可…

STM32从固件库到HAL库

让坚持成为一种热爱&#xff0c;极致成为一种精神。历时10个月&#xff0c;目前我又重新回到了程序员的身份&#xff0c;2023想玩不一样的嵌入式。&#x1f680; 目录前言一、安装STM32CubeMX二、配置自己的HAL库MDK工程三、HAL库与固件库的区别1.句柄2.回调函数3.时钟配置4.HA…

Bulma - 免费开源的纯 CSS 前端 UI 框架,专注于构建移动优先的响应式 web 界面

简单易用的 CSS 框架&#xff0c;虽然只有一个 CSS 文件&#xff0c;但功能很强大&#xff0c;在国外很受开发者欢迎&#xff0c;推荐给大家。 关于 Bulma CSS 框架 Bulma 是一个简单、很容易自定义的 CSS UI 框架&#xff0c;提供了众多预定义好的样式&#xff0c;开发者可以…

宠物吸毛器控制板开发,构建理想人宠共居空间

目前养宠物的人越来越多&#xff0c;猫狗等宠物一直存在着严重的掉毛情况&#xff0c;宠物毛发可能会引起过敏等疾病反应&#xff0c;日常生活中清理工作是一大难题&#xff0c;市面上关于宠物的吸毛产品非常的少&#xff0c;为此沐渥开发了一款宠物吸毛器控制板&#xff0c;适…

奇怪,郭德纲小儿子八岁生日,现场照片发布不久却被删除了

2022年1月5日&#xff0c;对于北京德云社来说&#xff0c;是一个双喜临门好日子&#xff0c;首先德云社相声春晚开始录制&#xff0c;其次郭德纲的儿子郭汾阳&#xff0c;也要过八岁生日了。郭汾阳作为德云社的少班主&#xff0c;是郭德纲老师和董事长王惠所生&#xff0c;老来…

与ChatGPT的聊天:如何使用Python连接Neo4j并创建无向图?

0. 前言 尝试请ChatGPT帮写代码&#xff1a; 但发现ChatGPT可以给出逻辑上正确的程序&#xff0c;一旦具体深入询问&#xff0c;有可能会产生错误。 当然&#xff0c;也可能因为提问的人比较少&#x1f923; 不管怎么说&#xff0c;这篇可以当成是对ChatGPT茶余饭后的谈资&am…

数据库,计算机网络、操作系统刷题笔记28

数据库&#xff0c;计算机网络、操作系统刷题笔记28 2022找工作是学历、能力和运气的超强结合体&#xff0c;遇到寒冬&#xff0c;大厂不招人&#xff0c;可能很多算法学生都得去找开发&#xff0c;测开 测开的话&#xff0c;你就得学数据库&#xff0c;sql&#xff0c;oracle…

产品重要更新: GcExcel Java Edition 6.0.2 Crack

重要产品更新&#xff1a; JDK 6 和 7 已经走到了生命的尽头。自此&#xff0c; GcExcel Java Edition将不再支持它们&#xff0c;现在将从最新的v6 版本及更高版本开始以JDK 8为目标。GcExcel Java Edition 6.0.2 Crack by Ω578867473 GcExcel高速 Java Excel 电子表格 API …

Zookeeper简介

引言 在分布式环境下&#xff0c;如果舍弃SpringCloud&#xff0c;使用其他的分布式框架&#xff0c;那么注册心中&#xff0c;配置集中管理&#xff0c;集群管理&#xff0c;分布式锁&#xff0c;分布式任务&#xff0c;队列的管理想单独实现怎么办。 Zookeeper介绍 Zookeeper…

Flink 运行架构

1 Flink 运行时的组件 Flink 运行时架构主要包括四个不同的组件&#xff0c;它们会在运行流处理应用程序时协同工作&#xff1a;作业管理器&#xff08;JobManager&#xff09;、资源管理器&#xff08;ResourceManager&#xff09;、任务管理器&#xff08;TaskManager&#…

一篇文章让你掌握HTML(下)

目录 1. 列表标签 1.1 无序列表 1.2 有序列表 1.3 自定义列表 2. 表格标签 2.1 表格的基本标签 2.2 表格相关属性 2.3 表格标题和表头单元格标签 2.4 表格的结构标签 2.5 合并单元格 3. 表单标签 3.1 input系列标签 3.1.1 input系列标签-文本框 3.1.…

MATLAB-非线性方程(组)求解

求非线性方程或方程组解的问题也就是求函数零点的问题。对于任意函数&#xff0c;在求解范围内可能有零点&#xff0c;也可能没有;可能只有一个零点&#xff0c;也可能有多个甚至无数个零点。MATLAB没有可以求解所有函数零点的通用命令&#xff0c;下面将分别讨论一元函数和多元…

Nginx入门知识

一、什么是NginxNginx (engine x) 是一个高性能的HTTP和反向代理web服务器&#xff0c;Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;在BSD-like 协议下发行。其特点是占有内存少&#xff0c;并发能力强&am…

JavaScript 字符串

文章目录JavaScript 字符串JavaScript 字符串字符串长度特殊字符字符串可以是对象字符串属性和方法字符串属性字符串方法JavaScript 字符串 JavaScript 字符串用于存储和处理文本。 JavaScript 字符串 字符串可以存储一系列字符&#xff0c;如 “John Doe”。 字符串可以是插…

如何连接远程mysql数据库(原创)

一、连接远程数据库&#xff1a;&#xff08;前提本地也有mysql的客户端&#xff09; 1、显示密码 如&#xff1a;MySQL 连接远程数据库&#xff08;192.168.5.116&#xff09;&#xff0c;端口“3306”&#xff0c;用户名为“rfid_hh”&#xff0c;密码“123456” C:/>mysq…

20230107报警器的测试

20230107报警器的测试 2023/1/7 13:01 新款夹子户外报警器防盗报警器果园自动打电话无线距离防水报警器 拼多多的货。不到30&#xffe5;&#xff0c;也就那么回事了&#xff0c;没有太高期望。 需要在微信公众号注册。 拿掉强磁铁之后&#xff0c;GSM模块通电了&#xff01;…