最近学了点内存管理基本概念,仅以此文做个记录。
术语
PGD, PUD, PMD, PTE
PGD: Page Global Directory
PUD: Page Upper Directory
PMD: Page Middle-level Directory
PTE: Page Table Entry
这么看其实非常枯燥,请看下文中pgd_index, pud_index这小节。
变量/宏
__START_KERNEL_map,内核虚拟地址的起始地址
定义很简单,但这就是内核虚拟地址的启示位子。
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)
好,我们来看看为什么。
先看链接脚本: arch/x86/kernel/vmlinux.lds.S
#define __START_KERNEL_map _AC(0xffffffff80000000, UL)
#define __START_KERNEL (__START_KERNEL_map + __PHYSICAL_START)
SECTIONS
{
. = __START_KERNEL;
phys_startup_64 = ABSOLUTE(startup_64 - LOAD_OFFSET);
/* Text and read-only data */
.text : AT(ADDR(.text) - LOAD_OFFSET) {
_text = .;
这里只看x86_64的。可以看到 .text, _text的虚拟地址就是__START_KERNEL,如果__PHYSICAL_START默认为0x1000000, 那么 这个 地址 就是
__START_KERNEL_map + 0x1000000
= 0xffffffff81000000。
ok,那怎么证明呢? 有几个证据。
证明一, readelf
第一个就是看elf的program header。
readelf -l vmlinux
Elf file type is EXEC (Executable file)
Entry point 0x1000000
There are 5 program headers, starting at offset 64
Program Headers:
Type Offset VirtAddr PhysAddr
FileSiz MemSiz Flags Align
LOAD 0x0000000000200000 0xffffffff81000000 0x0000000001000000
0x0000000000da0000 0x0000000000da0000 R E 200000
LOAD 0x0000000001000000 0xffffffff81e00000 0x0000000001e00000
0x0000000000143000 0x0000000000143000 RW 200000
LOAD 0x0000000001200000 0x0000000000000000 0x0000000001f43000
0x0000000000019018 0x0000000000019018 RW 200000
LOAD 0x000000000135d000 0xffffffff81f5d000 0x0000000001f5d000
0x000000000016c000 0x00000000002ef000 RWE 200000
NOTE 0x0000000000a38e08 0xffffffff81838e08 0x0000000001838e08
0x0000000000000204 0x0000000000000204 4
看第一个program header的虚拟地址是0xffffffff81000000, 是不是就是它了呢。
证明二,打印符号 _text
第二个证明,干脆在内核中打印_text这个符号的地址呗。
恩,请自行打印,就不在这里写了。
证明三, 计算 level2_kernel_pgt的index
第三个证明, 查看页表。
在head_64.S设置的页表 和 尝试打印页表 中, 我们可以看到 level2_kernel_pgt 是 从 pgd[511] 和 pud[510] 指过来的。 好了, 那我们分析一下 __START_KERNEL_map。
__START_KERNEL_map
= 0x ffff ffff 8000 0000
= 0x ffff ffff 8 000 0000
= 0x ffff (1111 1111 1111 1111 1000)b 000 0000
= 0x ffff (1111 11111)b (111 1111 10)b (00)b 000 0000
= 0x ffff 511 510 (00)b 000 0000
写得有点丑,大家将就看一下。低48bit中的高9位是pgd的index,接下来的9位是pud的index。上面把这两个域截出来了,正好是 511 和 510。完美~
__PAGE_OFFSET,整个虚拟地址的起始地址
恩,这个是我猜的,暂时还不确认哈
/*
* Set __PAGE_OFFSET to the most negative possible address +
* PGDIR_SIZE*16 (pgd slot 272). The gap is to allow a space for a
* hypervisor to fit. Choosing 16 slots here is arbitrary, but it's
* what Xen requires.
*/
#define __PAGE_OFFSET _AC(0xffff880000000000, UL)
这高级玩意还真有点难懂,理解要是不对,欢迎大家拍砖。
0x ffff 0000 0000 0000 A
0x 0000 8000 0000 0000 B
+ 0x 0000 0800 0000 0000 C
= 0x ffff 8800 0000 0000 __PAGE_OFFSET
A 是 现在页表支持最大的内存空间。因为现在我看,一个页表最多也就能表示48位的地址空间了。(init_level4_pgt就是这种页表。)
B 是 most nagetive possible address。 这个想了一会儿发现还真的是的。这个使用补码表示,最高位留作符号位。最高位取1, 其余为0时,就是这么多位能表示的最小的负数。
C 是 注释中说的那个16个PGDIR_SIZE。 因为 (C >> 39) = 16。
看到这里有点没有想明白,为啥这个地址还是个有符号数呢?
小函数
pgd_index(), pud_index(), pmd_index()
这几个函数或者宏是用来遍历页表的
/*
* the pgd page can be thought of an array like this: pgd_t[PTRS_PER_PGD]
*
* this macro returns the index of the entry in the pgd page which would
* control the given virtual address
*/
#define pgd_index(address) (((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD - 1))
/* to find an entry in a page-table-directory. */
static inline unsigned long pud_index(unsigned long a