一、物理地址空间

  • 正常内存(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