linux 进程 页表 mmap,Linux内存之页表

Linux的相关页表的框架在 vmalloc分析中,__vmalloc_area_node调用流程与分配页表项两节中已经有所阐述。本文再做一些总体上的把握。文中会借用其中的一些图。

所谓的页表结构如下。针对现在的64位操作系统,Linux已经可以支持4级页表操作,即pgd,pud,pmd和pte。但目前一般嵌入式的应用并不会使能该功能,所以在代码中涉及到pud和pmd的指针,实际都是指向pgd表项的。

7b1e48e1e9cd03ef97cd9def730eab60.png

简单介绍下这幅图

1. 所有Linux都是4G的地址空间,每个表项代表2MB空间,共2K表项,占2个page

2. 所有的内存在表中都是有序的,即物理地址在这个空间中也是有低到高的

2. 每个pgd表项指向一个page,4KB,分成4个子表如上图。

3. 下面的两个子表2KB,包含512个表项,正好2MB空间,视为一个pte,其中每个表项指向一个page

pgd表保存在进程的mm_struct中

struct mm_struct {

……

pgd_t * pgd;

……

}

pte_alloc_map / pte_alloc_map_lock / pte_alloc_kernel这几个函数为pgd的每个表项分配他所指向的那一页数据,其实就是pte。

因为内存会不断的分配,不可能每次分配一个page都会新建这样的一张表格,所以Linux为每个pgd表项(代码中为了兼容,还是将之称为pmd)加入了一些标志位,并在每次分配之前,用宏pmd_present(*(pmd))来检验此pte是否已经分配了,如果已经分配就不会再分配了。这个标志位是怎么分配的呢?vmalloc分析也有所分析。即函数pmd_populate_kernel,其实还有其他函数,例如pmd_populate。他们调用这的最终函数都同是__pmd_populate。即

pmdp[0] = __pmd(pmdval) ;// = pmdval

pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t)); // =  pmdval + 256 * sizeof(pte_t)

13668ea19aaa5d38a0df173410aff869.png

pmd_populate是在为用户空间的地址建立页表,而pmd_populate_kernel是在为内核地址建立页表。所以他们之间的差别便水到渠成了:

1. 两者为pmd填充的内容不一样

pmd_populate_kernel:__pa(pte_ptr) | _PAGE_KERNEL_TABLE

pmd_populate:page_to_pfn(ptep) << PAGE_SHIFT | _PAGE_USER_TABLE

可见,一个是物理地址+kernel标志,另一个是内核逻辑地址+user标志

2. 两者传入的参数也不同

pmd_populate_kernel传入的是内核逻辑地址

pmd_populate传入的则是page结构指针

不管怎么样,其实他们的实质是一样的,pmd的内容都是这一个page表格所在的具体位置(物理地址,或者物理地址偏移PAGE_SHIFT的内核逻辑地址)+一个标志内核进程还是用户进程 的标志位。

至此,pte(或者pmd)的内容就讲清楚了,那有了这页表格后,就顺利成章的该往里面填pte表项了。首先用pte_offset_map / pte_offset_kernel找到表格中对应的表项,然后用函数set_pte_at真正的做填充。set_pte_at最终会调用到一个汇编函数set_pte_ext。如果不考虑高端内存的话,pte_offset_map和pte_offset_kernel是一样的,都

= pmd_page_vaddr(*(dir)) + __pte_index(addr)

前半部分是pmd的逻辑地址,后半部分是本page在pmd中的偏移量。

=========================分割线===================================

说了这么多,页表的建立就是这么些事情了。下面再举个小小的例子,是mmap实现的一种方式remap_pfn_range。mmap要做的事情是把一个文件(包括设备文件,如果是设备文件,则要驱动来支持)映射到一款内存地址上,那remap_pfn_range就是要为一段物理page建立页表项。先看他们的原型:

mmap (caddr_t addr, size_t len, int prot, int flags, int fd, off_t offset)

int remap_pfn_range(struct vm_area_struct *vma,unsigned long virt_addr, unsigned long pfn,

unsigned long size, pgprot_t prot);

上面的mmap是Linux的API,可以针对普通文件和设备文件,所以比较有普遍性,下面是驱动中为了实现mmap经常会用到的remap_pfn_range函数。理一下他们的参数之间的对应关系:

mmapremap_pfn_range

addr          <=>               vma,  virt_addr

fd,offset,len  <=>               pfn,size

调用过程就是:

1. 应用程序调用mmap来映射一个文件fd中offset~offset+len这一部分。

2. 内核按照传入的参数来,分配一个vma_area_struct,这个结构包含了所有要分配的地址映射的信息,包括物理地址(fd, offset,len)和虚拟地址空间(addr),所以remap_pfn_range的参数virt_addr,pfn,size,都在vma中有所描述了。至于为什么要这样做,我猜是为了提供灵活性,毕竟vma可以超过我们需要映射的地址空间,比如内核在分配vma时发现有两个vma可以合并,则传入的vma就大于我们需要的空间,这样就可以用virt_addr,pfn,size来说明我们需要的映射。

remap_pfn_range最核心的部分就是为virt_addr~virt_addr+PAGE_ALIGN(size)的区间建立页表,代码如下

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,

unsigned long pfn, unsigned long size, pgprot_t prot)

{

……

do {next = pgd_addr_end(addr, end);

err = remap_pud_range(mm, pgd, addr, next,

pfn + (addr >> PAGE_SHIFT), prot);

if (err)

break;

} while (pgd++, addr = next, addr != end);

……

}

remap_pud_range -> remap_pmd_range -> remap_pte_range

前面两个是虚的,最后一个就是建立页表了

static int remap_pte_range(struct mm_struct *mm, pmd_t *pmd,unsigned long addr, unsigned long end,

unsigned long pfn, pgprot_t prot){

……

pte = pte_alloc_map_lock(mm, pmd, addr, &ptl);

……

do {

set_pte_at(mm, addr, pte, pte_mkspecial(pfn_pte(pfn, prot)));

pfn++;

} while (pte++, addr += PAGE_SIZE, addr != end);

……

}传入的参数1. pmd就是我们之前提到的pgd表项指向的那1个page2. addr~end是要建立页表的虚拟地址范围3. pfn是这块虚拟地址范围首地址的物理地址帧号4. prot是一些标志位

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值