本文将结合x86-bare-metal-examples与SDM,展示Intel IA32架构下paging的具体用法。

背景

MMU使用页表,将虚拟地址转为物理地址。


Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
BEGIN
CLEAR
STAGE2
PROTECTED_MODE
SETUP_PAGING_4M

/* Setup a test canary value. */
movl $0x1234, 0x1000

/* Print the canary to make sure it is really there. */
VGA_PRINT_HEX_4 0x1000

/* Make page 0 point to page frame 1(i.e. virtual address 0 points to physical address 4KB)
* by setting bit 12 of the Page Table Entry structure.
*
* At SETUP_PAGING_4M, page_table has been setup to
* point page frame 0(i.e. page 0 point to page frame 0).
* Bit 12 is the lowest bit of the "Address of 4KB page frame" field,
* By setting it, can relocate page 0 point to page frame 1.
*/
orw $0x1000, page_table

PAGING_ON

/* THIS is what we've been working for!!!
* Even though we mov to 0, the paging circuit reads that as physical address 0x1000,
* so the canary value 0x1234 should be modified to 0x5678.
**/
movl $0x5678, 0

/* Turn paging back off to prevent it from messing with us. */
PAGING_OFF

/* Print the (hopefully) modified value 0x5678. */
VGA_PRINT_HEX_4 0x1000

第5行代码调用宏SETUP_PAGING_4M来建立4MB 内存区域的页表,后面会详细分析该宏。

第8行代码往物理地址4KB处写入0x1234。

第21行代码让page 0指向page frame 1(即:virtual address 0 points to physical address 4KB)。

第23行代码调用宏PAGING_ON来开启分页,后面会详细分析该宏。

第29行代码是往虚拟地址0写入0x5678,此时,物理地址4KB处的内容被更改为0x5678。

第32行代码调用宏PAGING_OFF来关闭分页。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/* Setup the first Page Directory entry, which gives us a 4MB(2^10 * 2^12) memory region.
* The memory region starts at 0, and the virtual address and physical address are identical.
*
* The currently executing code is inside that range, or else we'd jump somewhere and die.
*/
.equ page_directory, __end_align_4k
.equ page_table, __end_align_4k + 0x1000
.macro SETUP_PAGING_4M
LOCAL page_setup_start page_setup_end
PUSH_EADX

/* Page Directory setup. */
mov $page_table, %eax
/* Clear the low 12 bits of the first Page Directory entry. */
and $0xF000, %ax
/* Set the P, R/W, U/S, and A bits of the first Page Directory entry. */
orb $0b00100111, %al
/* Setup the first Page Directory entry. */
mov %eax, page_directory

/* Page table setup. */
mov $0, %eax
mov $page_table, %ebx
page_setup_start:
cmp $0x400, %eax
je page_setup_end
/* Top 20 address bits. */
mov %eax, %edx
shl $12, %edx
/* For flag bits 0-7. We only set bit 0 and bit 1:
* - bit 0: Page present
* - bit 1: Page is writable.
* Might work without this as the permission also depends on CR0.WP.
*/
mov $0b00000011, %dl
/* Zero flag bits 8-11 */
and $0xF0, %dh
/* Setup the PTE(Page Table Entry). */
mov %edx, (%ebx)
inc %eax
add $4, %ebx
jmp page_setup_start
page_setup_end:
POP_EDAX
.endm

page_directory是Page Directory的物理地址。

page_table是Page Directory第一个entry指向的Page Table的物理地址。

该宏只置Page Directory第一个entry的present位为1。同时,设置Page Table,使得page 0指向page frame 0, page 1指向page frame 1…,page 1023指向page frame 1023。这样,就配置好了4MB(2^10 * 2^12)大小的内存区域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* * Turn paging on.
* The cr3 register does have a format, it is not simply the address of the page directory:
*
* Many tutorials simply ignore bits 3 and 4, and do a direct address mov to `cr3`.
*
* This sets the 20 top address bits to their correct value, and puts trash in bits 3 and 4,
* but it generally works.
*/
.macro PAGING_ON
/* Tell the CPU where the page directory is. */
mov $page_directory, %eax
mov %eax, %cr3

/* Turn paging on. */
mov %cr0, %eax
or $0x80000000, %eax
mov %eax, %cr0
.endm

/* Turn paging off. */
.macro PAGING_OFF
mov %cr0, %eax
and $0x7FFFFFFF, %eax
mov %eax, %cr0
.endm