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

页表的创建

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。

你可能感兴趣的:(Liunx内核)