概述
【Linux内核】内存映射原理
物理地址空间
-
物理地址是处理器在总线上能看到的地址,使用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 address
within 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);
}
系统调用
应用程序通常使用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] = '