欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

调用malloc时发生了什么(3) - 缺页中断

程序员文章站 2022-06-04 11:02:20
...

页表的创建

kmalloc内存使用了umapped内存,直接对地址偏移即可寻址物理内存,这里不考虑。
考虑用户态内存和vmalloc,都用到了虚拟内存,即需要通过页表查询的方式查询都物理内存。

例如 用户态通过brk申请了一块内存,后续访问这块内存的0x00007F88F16A4690这块地址会发生什么?
首先,X64内核是4级页表,根据X64对线性地址的划分,可以计算出0x00007F88F16A4690这地址的pgd索引是 255, pud索引是 35, pmd索引是 395, pte索引是 164。
因为x64体系结构,pgd索引为 47~39 bit,pud索引为 38~30 bit,pmd索引为 29~21 bit,pte索引为 20~12 bit。

如果这块内存的地址没有存在MMU/TLB,则会触发缺页中断,常见的缺页中断就是该地址第一次访问,先来看下缺页中弄断时,页表是如何创建的:

首先,从current->mm->pgd取出全局页表的首地址,current->mm->pgdpgd_t类型数据结构,其实就是8字节的unsigned long地址,所以根据 pgd索引可以获得pgd = current->mm->pgd + 255,
接着,取出的pgd中,即获取*pgd中的值,也是一个unsigned long的值。

*pgd为NULL的话,创建一个page1,page1的页框号,就保存在 pgd中,而pud的值,就是该page的对应的虚拟地址加上358字节,换句话说,*pgd具体存着pud的页框号,通过该页框号,能够找到page,然后通过
内存偏移能够找到当前地址对应的pud。
同理,*pud 为null的话,也会创建一个page2,pud 保存page2的页框号,pmd就是page2的虚拟地址加上 3958。
最后,*pmd为null的话,创建一个page3,pmd存入page3的页框号,pte的地址就是page3的虚拟地址加上 1648。
用图来描述,就是下面这样子:

mm->pgd         
 ----
| 0  |
 ----
  .
  .
  .           *pgd
 ----   pgd   ----
| 255|  ->   | 0  |
 ----         ----
  .            .
  .            .              *pud
  .           ----    pud     ----
 ----        | 35 |   ->     | 0  |
| 511|        ----            ----
 ----          .               .
               .               .                *pmd
              ----            ----    pmd      ----
             | 511|          | 395|   ->      | 0  |  
              ----            ----             ----
                               .                .
                               .                .
                              ----             ----    pte(8字节)
                             | 511|           | 164|   <-
                              ----             ----
                                                .
                                                .
                                               ----
                                              | 511|
                                               ----  

至此寻址到了内存地址对应的pte,但是虚拟地址对应的物理地址还未创建,上面描述的page1,page2,page3都是为了构建多级页表创建内存而已。
物理内存的创建是在函数do_anonymous_page中完成。do_anonymous_page函数中,对于读操作,分配一个zero page,它是一个固定的页,启动就分配好的页,因为走到 do_anonymous_page
这步时,必然是因为虚拟内存对应的屋里内存还未创建,所以此时用户态的read类型的操作,返回0即可,其实返回任意数据即可。且将这个pte标记没有设置成可写。这样意味着
后续的写操作,会申请新的page,这个page才是虚拟地址对应的物理内存。(在do_wp_page中处理)。

回到上面的图,我们来看一下,一个进程最坏情况下,一个进程页表最大会多大?看上图,一个pmd 4k,一个pud有512个pmd,一个pgd有512个pmd,总共512个pgd,
所以总共会消耗内存:4k512512*512 = 549755813888 bytes = 512G。还有一种更简单的算法,就是64bit有效地址范围是48字节,所以总共内存2^48,而又被4k大小的page分割,所以
会被划分成 248/212 个 page,而一个page需要8字节的pte来表示所以(248/212)*2^3 = 2^39 = 549755813888 = 512G。

pte

这个pte就是被会缓存在TLB中,可见上面极端情况下,一个进程会有 549755813888/8 个pte表,如果进程申请的内存够大,访问够随机,基本次次tlb miss。

pagesie

上面描述的pagesize是4k,如果pagesize大一点会怎么样。我们举个极端例子,内存是512GB,pagesize是256GB,显然,一个进程只需要2 pte就能构建虚拟地址和物理地址映射关系。
而pte的数量急剧减少,被TLB淘汰的概率几乎没有。但是劣势也非常明显,如果进程开辟1字节的内存,就需要申请一个256G的page。

相关标签: 内存管理