探索,进取,坚持

kernel-mm内存页

pagemap页映射:

/proc/pid/pagemap 该文件允许用户空间程序找出每个虚拟页映射到物理帧。每个虚拟页面对应一个64位的值。包含以下数据(fs/proc/task_mmu.c,pagemap_read方法读取) * Bits 0-54 page frame number (PFN) if present
Bits 0-4 swap type if swapped
Bits 5-54 swap offset if swapped
Bit 55 pte is soft-dirty (see Documentation/admin-guide/mm/soft-dirty.rst)
Bit 56 page exclusively mapped (since 4.2)
Bits 57-60 zero
Bit 61 page is file-page or shared-anon (since 3.5)
Bit 62 page swapped
Bit 63 page present

其中
使用/proc/pid/maps可以高效的确定映射的内存区域、跳过未映射的区域。
/proc/kpagecount:这个文件包含64位计数 , 表示每一页被映射的次数,按照PFN值固定索引。
/proc/kpageflags:此文件包含为64位的标志集 ,表示该页的属性,按照PFN索引。

使用下边测试程序来读页指针:

#!/usr/bin/python

import sys
import os
import binascii
import struct

def read_entry(path, offset, size=8):
  with open(path, 'r') as f:
    f.seek(offset, 0)
    return struct.unpack('Q', f.read(size))[0]

# Read /proc/$PID/pagemap
def get_pagemap_entry(pid, addr):
  maps_path = "/proc/{0}/pagemap".format(pid)
  if not os.path.isfile(maps_path):
    print "Process {0} doesn't exist.".format(pid)
    return

  page_size = os.sysconf("SC_PAGE_SIZE")
  pagemap_entry_size = 8
  offset  = (addr / page_size) * pagemap_entry_size

  return read_entry(maps_path, offset)

def get_pfn(entry):
  return entry & 0x7FFFFFFFFFFFFF

def is_present(entry):
  return ((entry & (1 << 63)) != 0)

def is_file_page(entry):
  return ((entry & (1 << 61)) != 0)
##########################################################

# Read /proc/kpagecount
def get_pagecount(pfn):
  file_path = "/proc/kpagecount"
  offset = pfn * 8
  return read_entry(file_path, offset)

##########################################################

# Read /proc/kpageflags
def get_page_flags(pfn):
  file_path = "/proc/kpageflags"
  offset = pfn * 8
  return read_entry(file_path, offset)


if __name__ == "__main__":
  pid = sys.argv[1]
  if sys.argv[2].startswith("0x"):
    addr = long(sys.argv[2], base=16)
  else:
    addr = long(sys.argv[2])

  entry = get_pagemap_entry(pid, addr)
  pfn = get_pfn(entry)
  print "PFN: {}".format(hex(pfn))
  print "Is Present? : {}".format(is_present(entry))
  print "Is file-page: {}".format(is_file_page(entry))
  print "Page count: {}".format(get_pagecount(pfn))
  print "Page flags: {}".format(hex(get_page_flags(pfn)))

线性地址转换(MMU)4-levle页表

63  62                  52 51                                                    32
 --------------------------------------------------------------------------------
| N |                     |                                                     |
|   |     Available       |     Address of the paging structure on lower level  |
| X |                     |                                                     |
 --------------------------------------------------------------------------------
31                                              12 11  9 8 7 6 5   4   3 2 1     0
 --------------------------------------------------------------------------------
|                                                |     | M |I| | P | P |U|W|    |
| Address of the paging structure on lower level | AVL | B |G|A| C | W | | |  P |
|                                                |     | Z |N| | D | T |S|R|    |
 --------------------------------------------------------------------------------

Where:

4级页表存在19年以前的发行版中,新的发行版已经是默认支持5级页表了。5级页表支持64TB以上的ram内存。

关闭内核5级页表使用no5lvl或者检查是否开启5级页表用lscpu | grep -i la57 还有就是CONFIG_X86_5LEVEL配置可以直接看到 5级页表本质上在PUD的前一级加了一个P4D,然后在位数上从48位拓冲到57位。

Translation Lookaside Buffer (TLB)

TLB中保存的是线性地址和对应的物理地址 一般分为指令缓存和数据缓存。修改CR3寄存器可使得TLB刷新。

windows系统的内存分页

32位系统下的xp系统分no-pxe 和pxe 分别分页是10-10-12分页和2-9-9-12分页. 差别就是一个是32位,一个是36位,多了4位的寻址。能支持64GB寻址。