0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内存映射的原理

嵌入式开发AIoT 来源:嵌入式开发AIoT 2023-01-15 09:55 次阅读

一、物理地址空间

  1. 物理地址是处理器在系统总线上看到的地址。使用RISC的处理器通常只实现一个物理地址空间,外围设备和物理内存使用统一的物理地址空间。有些处理器架构把分配给外围设备的物理地址区域称为设备内存。
  2. 处理器通过外围设备控制器寄存器访问外围设备,寄存器分为控制器,状态寄存器和数据寄存器三大类。外围设备的寄存器通常被连续地编址,处理器对外围设备寄存编址方式分为:i/o映射方式(i/o-mapped),内存映射方式(memory-mapped)。
  3. 应用程序只能通过虚拟地址访问外设寄存器,内核提供API函数来把外设寄存器的物理地址映射到虚拟地址空间。
  4. ARM64(物理地址宽度最大支持48位)架构分为两种内存类型:
  • 正常内存(Noramal Memory):包括物理内存和只读存储器(ROM);
  • 设备内存(Device Memory):指分配给外围设备寄存器的物理地址区域;
  • 设备内存共享属性总是外部共享,缓存属性总是不可缓存(必须绕过处理器的缓存)
  • 二、内存映射原理

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

  1. 文件映射:文件支持的内存映射,把文件的一个区间映射到进程的虚拟地址空间,数据源是存储设备上的文件。
  2. 匿名映射:没有文件支持的内存映射,把物理内存映射到进程的虚拟地址空间,没有数据源。

【原理】:创建内存映射时,在进程的用户虚拟地址空间中分配一个虚拟内存区域。内核采用延迟分配物理内存的策略,在进程第一次访问虚拟页的时候,产生缺页异常。==如果是文件映射,那么分配物理页,把文件指定区间的数据读到物理页中,然后在页表中把虚拟页映射到物理页。如果是匿名映射,就分配物理页,然后在页表中把虚拟页映射到物理页。==(1)两个进程可以使用共享的文件映射实现共享内存。匿名映射通常是私有映射,共享的匿名映射只可能出现父进程和子进程之间。在进程的虚拟地址空间中,代码段和数据段是私有的文件映射,未初始化数据段、堆栈是私有的匿名映射。(2)修改过的脏页面不会立即更新到文件中,可以调用msync来强制同步写入文件。

flowchartLR
task_struct-->mm_struct-->vm_area_struct

三、虚拟内存源码分析

3.1 相关数据结构

structvm_area_struct{
/*ThefirstcachelinehastheinfoforVMAtreewalking.*/

//这两个成员分别用来保存该虚拟内存空间的首地址和末地址后第一个字节的地址
unsignedlongvm_start;/*Ourstartaddresswithinvm_mm.*/
unsignedlongvm_end;/*Thefirstbyteafterourendaddresswithinvm_mm.*/

/*linkedlistofVMareaspertask,sortedbyaddress*/
structvm_area_struct*vm_next,*vm_prev;

//如果采用链表组织化,会影响它搜索速度问题,解决此问题采用红黑树(每个进程结构体mm_struct中都
//创建一颗红黑树,将VMA作为一个节点加入红黑树给中,这样可以提升搜索速度)
structrb_nodevm_rb;

/*
*LargestfreememorygapinbytestotheleftofthisVMA.
*EitherbetweenthisVMAandvma->vm_prev,orbetweenoneofthe
*VMAsbelowusintheVMArbtreeandits->vm_prev.Thishelps
*get_unmapped_areafindafreeareaoftherightsize.
*/
unsignedlongrb_subtree_gap;

/*Secondcachelinestartshere.*/

structmm_struct*vm_mm;/*Theaddressspacewebelongto.*/
pgprot_tvm_page_prot;/*AccesspermissionsofthisVMA.*/
unsignedlongvm_flags;/*Flags,seemm.h.*/

/*
*Forareaswithanaddressspaceandbackingstore,
*linkageintotheaddress_space->i_mmapintervaltree.
*/
struct{
structrb_noderb;
unsignedlongrb_subtree_last;
}shared;

/*
*Afile'sMAP_PRIVATEvmacanbeinbothi_mmaptreeandanon_vma
*list,afteraCOWofoneofthefilepages.AMAP_SHAREDvma
*canonlybeinthei_mmaptree.AnanonymousMAP_PRIVATE,stack
*orbrkvma(withNULLfile)canonlybeinananon_vmalist.
*/
structlist_headanon_vma_chain;/*Serializedbymmap_sem&
*page_table_lock*/
structanon_vma*anon_vma;/*Serializedbypage_table_lock*/

/*Functionpointerstodealwiththisstruct.*/
conststructvm_operations_struct*vm_ops;

/*Informationaboutourbackingstore:*/
unsignedlongvm_pgoff;/*Offset(withinvm_file)inPAGE_SIZEunits,*not*PAGE_CACHE_SIZE*/
structfile*vm_file;//文件,如果是私有的匿名映射,该成员为空指针
void*vm_private_data;/*指向内存的私有数据*/

#ifndefCONFIG_MMU
structvm_region*vm_region;/*NOMMUmappingregion*/
#endif
#ifdefCONFIG_NUMA
structmempolicy*vm_policy;/*NUMApolicyfortheVMA*/
#endif
structvm_userfaultfd_ctxvm_userfaultfd_ctx;
};

3.2 虚拟内存操作集合

structvm_operations_struct{
void(*open)(structvm_area_struct*area);//在创建虚拟内存区域时调用open方法
void(*close)(structvm_area_struct*area);//在删除虚拟内存区域时调用close方法
int(*mremap)(structvm_area_struct*area);//使用系统调用mremap移动虚拟内存区域时调用
int(*fault)(structvm_area_struct*vma,structvm_fault*vmf);//访问文件映射的虚拟页时,如果没有映射到物理页,生成
//缺页异常,异常处理程序调用fault方法来把文件的数据读到文件页缓存当中
int(*pmd_fault)(structvm_area_struct*,unsignedlongaddress,
pmd_t*,unsignedintflags);//与fault类似,区别是该方法针对使用透明巨型页的文件映射

/*读文件映射的虚拟页时,如果没有映射到物理页,生成缺页异常,异常处理程序除了读入正在访问的文件页
还会预读后续文件页,调用map_pages方法在文件的页缓存中分配物理页*/
void(*map_pages)(structvm_area_struct*vma,structvm_fault*vmf);

/*notificationthatapreviouslyread-onlypageisabouttobecome
*writable,ifanerrorisreturneditwillcauseaSIGBUS*/

/*第一次写私有的文件映射时,生成页错误异常,异常处理程序执行写时复制,调用page_mkwrite方法以
通知文件系统页即将变成可写,以便文件系统检查是否允许写,或者等待页进入合适的状态*/
int(*page_mkwrite)(structvm_area_struct*vma,structvm_fault*vmf);

/*sameaspage_mkwritewhenusingVM_PFNMAP|VM_MIXEDMAP*/
int(*pfn_mkwrite)(structvm_area_struct*vma,structvm_fault*vmf);

/*calledbyaccess_process_vmwhenget_user_pages()fails,typically
*forusebyspecialVMAsthatcanswitchbetweenmemoryandhardware
*/
int(*access)(structvm_area_struct*vma,unsignedlongaddr,
void*buf,intlen,intwrite);

/*Calledbythe/proc/PID/mapscodetoaskthevmawhetherit
*hasaspecialname.Returningnon-NULLwillalsocausethis
*vmatobedumpedunconditionally.*/
constchar*(*name)(structvm_area_struct*vma);

#ifdefCONFIG_NUMA
/*
*set_policy()opmustaddareferencetoanynon-NULL@newmempolicy
*toholdthepolicyuponreturn.CallershouldpassNULL@newto
*removeapolicyandfallbacktosurroundingcontext--i.e.donot
*installaMPOL_DEFAULTpolicy,northetaskorsystemdefault
*mempolicy.
*/
int(*set_policy)(structvm_area_struct*vma,structmempolicy*new);

/*
*get_policy()opmustaddreference[mpol_get()]toanypolicyat
*(vma,addr)markedasMPOL_SHARED.Thesharedpolicyinfrastructure
*inmm/mempolicy.cwilldothisautomatically.
*get_policy()mustNOTaddarefifthepolicyat(vma,addr)isnot
*markedasMPOL_SHARED.vmapoliciesareprotectedbythemmap_sem.
*Ifno[shared/vma]mempolicyexistsattheaddr,get_policy()op
*mustreturnNULL--i.e.,donot"fallback"totaskorsystemdefault
*policy.
*/
structmempolicy*(*get_policy)(structvm_area_struct*vma,
unsignedlongaddr);
#endif
/*
*Calledbyvm_normal_page()forspecialPTEstofindthe
*pagefor@addr.Thisisusefulifthedefaultbehavior
*(usingpte_page())wouldnotfindthecorrectpage.
*/
structpage*(*find_special_page)(structvm_area_struct*vma,
unsignedlongaddr);
};

四、系统调用

  • 应用程序通常使用C标准库提供的函数malloc()申请内存。glibc库的内存分配器ptmalloc使用brk或mmap向内核以页为单位申请虚拟内存,然后把页划分成小内存块分配给应用程序。默认的阈值是128kb,如果应用程序申请的内存长度小于阈值,ptmalloc分配器使用brk向内核申请虚拟内存,否则ptmalloc分配器使用mmap向内核申请虚拟内存。
  • 应用程序可以直接使用mmap向内核申请虚拟内存。
【回顾mmap内存映射原理三个阶段】:
  1. 进程启动映射过程,并且在虚拟地址空间中为映射创建虚拟映射区域;
  2. 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系;
  3. 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝。
内存管理子系统提供以下常用系统调用函数:
  1. mmap() ---->创建内存映射

#include void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

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

#include int munmap(void *addr, size_t len);


代码实践

#include
#include
#include
#include
#include
#include
#include

typedefstruct
{
/*data*/
charname[6];
intage;
}people;

intmain(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
",strerror(errno));
return-1;
}

temp='A';
close(fd);

for(i=0;i< 10;i++)
{
temp=temp+1;
(*(p_map+i)).name[1]='';
memcpy((*(p_map+i)).name,&temp,1);
(*(p_map+i)).age=30+i;
}

printf("Initialize.
");

sleep(15);

munmap(p_map,sizeof(people)*10);

printf("UMAOK.
");
return0;
}

#include
#include
#include
#include
#include
#include
#include

typedefstruct
{
/*data*/
charname[6];
intage;
}people;

intmain(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
",strerror(errno));
return-1;
}

for(i=0;i< 10;i++)
{
printf("name:%sage:%d
",(*(p_map+i)).name,(*(p_map+i)).age);
}

munmap(p_map,sizeof(people)*10);

return0;
}

运行结果

f5d13690-942f-11ed-bfe3-dac502259ad0.png

审核编辑:陈陈


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10992

    浏览量

    206744
  • 内存映射
    +关注

    关注

    0

    文章

    14

    浏览量

    7372

原文标题:Linux内核 | 内存映射

文章出处:【微信号:嵌入式开发AIoT,微信公众号:嵌入式开发AIoT】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    从史前文明到女娲补天:Linux内存逆向映射(reverse mapping)技术的前世今生

    关于Linux内存管理逆向映射技术的历史和现在的分析,投稿标题《逆向映射的演进》,后经过小编与郭大侠商议改为《Linux
    的头像 发表于 09-06 15:45 1w次阅读
    从史前文明到女娲补天:<b class='flag-5'>Linux</b><b class='flag-5'>内存</b>逆向<b class='flag-5'>映射</b>(reverse mapping)技术的前世今生

    Linux内核之内存映射原理分析

    Linux 内核采用延迟分配物理内存的策略,在进程第一次访问虚拟页的时候,产生缺页异常。如果是文件映射,那么分配物理页,把文件指定区间的数据读到物理页中,然后在页表中把虚拟页映射到物理
    发表于 07-21 17:06 2185次阅读

    Linux内存映射与页表详解

    我们通常所说的内存容量,指的是物理内存,只有内核才可以直接访问物理内存,进程并不可以。
    发表于 08-18 12:30 972次阅读

    拆解mmap内存映射的本质!

    mmap 内存映射里所谓的内存其实指的是虚拟内存,在调用 mmap 进行匿名映射的时候(比如进行堆内存
    的头像 发表于 01-24 14:30 350次阅读
    拆解mmap<b class='flag-5'>内存</b><b class='flag-5'>映射</b>的本质!

    Linux内核地址映射模型与Linux内核高端内存详解

    的数据可能不在内存中。 Linux内核地址映射模型 x86 CPU采用了段页式地址映射模型。进程代码中的地址为逻辑地址,经过段页式地址映射
    发表于 05-08 10:33 3329次阅读
    <b class='flag-5'>Linux</b>内核地址<b class='flag-5'>映射</b>模型与<b class='flag-5'>Linux</b>内核高端<b class='flag-5'>内存</b>详解

    [4.5.1]--4.5动手实践-Linux内存映射基础(上)

    Linux
    jf_75936199
    发布于 :2023年02月25日 01:56:27

    [4.6.1]--4.6动手实践-Linux内存映射实现(中)_clip001

    Linux
    jf_75936199
    发布于 :2023年02月25日 01:57:12

    [4.6.1]--4.6动手实践-Linux内存映射实现(中)_clip002

    Linux
    jf_75936199
    发布于 :2023年02月25日 02:04:19

    [4.7.1]--4.7动手实践-Linux内存映射测试(下)

    Linux
    jf_75936199
    发布于 :2023年02月25日 02:05:04

    Linux的mmap文件内存映射机制

    Linux的mmap文件内存映射机制在讲述文件映射的概念时, 不可避免的要牵涉到虚存(SVR 4的VM). 实际上, 文件映射是虚存的中心概
    发表于 03-08 09:54

    RTOS和Linux中的内存映射及移植方法

    映射到相应得用户空间去。同样重要的是,在I/O调用密集的嵌入式程序中怎么样把RTOS的硬件接口代码移植到更加规范的Linux设备驱动程序中去。 本文把概述几种常用的经常出现于现有嵌入式应用中的内存
    发表于 07-03 07:43

    Linux内存系统---走进Linux 内存

    Linux内存系统---走进Linux 内存 1、内存是什么?1)内存又称主存,是 CPU 能直
    发表于 08-26 08:05

    Linux的mmap文件内存映射机制

    的. Linux提供了内存映射函数mmap, 它把文件内容映射到一段内存上(准确说是虚拟内存上)
    发表于 04-02 14:35 332次阅读

    浅析linux内存映射原理

    内存映射,简而言之就是将用户空间的一段内存区域映射到内核空间,映射成功后,用户对这段内存区域的修
    发表于 08-24 09:35 1478次阅读
    浅析<b class='flag-5'>linux</b><b class='flag-5'>内存</b><b class='flag-5'>映射</b>原理

    Linux 内存管理总结

    一、Linux内存管理概述 Linux内存管理是指对系统内存的分配、释放、映射、管理、交换、压缩
    的头像 发表于 11-10 14:58 244次阅读
    <b class='flag-5'>Linux</b> <b class='flag-5'>内存</b>管理总结