ucore实验二

主要差异文件

相对于实验一,实验二主要增加和修改的文件主要改动如下:

  • boot/bootasm.S:增加了对计算机系统中物理内存布局的探测功能;
  • kern/init/entry.S:根据临时段表重新暂时建立好新的段空间,为进行分页做好准备。
  • kern/mm/default_pmm.[ch]:提供基本的基于链表方法的物理内存管理(分配单位为页,即4096字节);
  • kern/mm/pmm.[ch]:pmm.h定义物理内存管理类框架struct pmm_manager,基于此通用框架可以实现不同的物理内存管理策略和算法(default_pmm.[ch] 实现了一个基于此框架的简单物理内存管理策略); pmm.c包含了对此物理内存管理类框架的访问,以及与建立、修改、访问页表相关的各种函数实现。
  • kern/sync/sync.h:为确保内存管理修改相关数据时不被中断打断,提供两个功能,一个是保存eflag寄存器中的中断屏蔽位信息并屏蔽中断的功能,另一个是根据保存的中断屏蔽位信息来使能中断的功能;(可不用细看)
  • libs/list.h:定义了通用双向链表结构以及相关的查找、插入等基本操作,这是建立基于链表方法的物理内存管理(以及其他内核功能)的基础。其他有类似双向链表需求的内核功能模块可直接使用list.h中定义的函数。
  • libs/atomic.h:定义了对一个变量进行读写的原子操作,确保相关操作不被中断打断。(可不用细看)
  • tools/kernel.ld:ld形成执行文件的地址所用到的链接脚本。修改了ucore的起始入口和代码段的起始地址。相关细节可参看附录C。

Makefile(非answer目录)

和lab1的makefile对比发现,有部分不同,主要地方如下,其他地方影响不大。

KINCLUDE	+= kern/debug/ \
			   kern/driver/ \
			   kern/trap/ \
			   kern/mm/ \
			   kern/libs/ \         # 新增
			   kern/sync/           # 新增

KSRCDIR		+= kern/init \
			   kern/libs \
			   kern/debug \
			   kern/driver \
			   kern/trap \
			   kern/mm \
			   kern/sync            # 新增
  
# 新增kernel_nopage
# create kernel_nopage target
kernel_nopage = $(call totarget,kernel_nopage)

$(kernel_nopage): tools/kernel_nopage.ld

$(kernel_nopage): $(KOBJS)
	@echo + ld $@
	$(V)$(LD) $(LDFLAGS) -T tools/kernel_nopage.ld -o $@ $(KOBJS)
	@$(OBJDUMP) -S $@ > $(call asmfile,kernel_nopage)
	@$(OBJDUMP) -t $@ | $(SED) '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $(call symfile,kernel_nopage)

$(call create_target,kernel)


# 使用“-T tools/boot.ld”链接脚本,而不是直接在命令行写出链接参数
$(bootblock): $(call toobj,boot/bootasm.S) $(call toobj,$(bootfiles)) | $(call totarget,sign)
	@echo + ld $@
	$(V)$(LD) $(LDFLAGS) -N -T tools/boot.ld $^ -o $(call toobj,bootblock)

tools/boot.ld

OUTPUT_FORMAT("elf32-i386")
OUTPUT_ARCH(i386)

SECTIONS {
    . = 0x7C00;

    .startup : {
        *bootasm.o(.text)
    }

    .text : { *(.text) }
    .data : { *(.data .rodata) }
    
    /DISCARD/ : { *(.eh_*) }
}

tools/kernel.ld

该文件内部改动较大,重要的如下,

# 入口改变,不再是kern_init,而是kern_entry,在init/entry.S中,由kern_entry调用kern_init
ENTRY(kern_entry)

SECTIONS {
    # 加载内核的虚拟地址不再是0x100000
    /* Load the kernel at this address: "." means the current address */
    . = 0xC0100000;
    
......

    # 增加这部分内容
    . = ALIGN(0x1000);
    .data.pgdir : {
        *(.data.pgdir)
    }

......
}

编译链接过程(answer目录)

+ cc kern/init/entry.S
gcc -Ikern/init/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/init/entry.S -o obj/kern/init/entry.o
+ cc kern/init/init.c
gcc -Ikern/init/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/init/init.c -o obj/kern/init/init.o
kern/init/init.c: In function ‘kern_init’:
kern/init/init.c:28:5: warning: implicit declaration of function ‘grade_backtrace’ [-Wimplicit-function-declaration]
     grade_backtrace();
     ^
kern/init/init.c: In function ‘grade_backtrace2’:
kern/init/init.c:48:5: warning: implicit declaration of function ‘mon_backtrace’ [-Wimplicit-function-declaration]
     mon_backtrace(0, NULL, NULL);
     ^
kern/init/init.c: At top level:
kern/init/init.c:62:1: warning: conflicting types for ‘grade_backtrace’
 grade_backtrace(void) {
 ^
kern/init/init.c:28:5: note: previous implicit declaration of ‘grade_backtrace’ was here
     grade_backtrace();
     ^
kern/init/init.c:95:1: warning: ‘lab1_switch_test’ defined but not used [-Wunused-function]
 lab1_switch_test(void) {
 ^
+ cc kern/libs/stdio.c
gcc -Ikern/libs/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/libs/stdio.c -o obj/kern/libs/stdio.o
+ cc kern/libs/readline.c
gcc -Ikern/libs/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/libs/readline.c -o obj/kern/libs/readline.o
+ cc kern/debug/panic.c
gcc -Ikern/debug/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/debug/panic.c -o obj/kern/debug/panic.o
kern/debug/panic.c: In function ‘__panic’:
kern/debug/panic.c:27:5: warning: implicit declaration of function ‘print_stackframe’ [-Wimplicit-function-declaration]
     print_stackframe();
     ^
+ cc kern/debug/kdebug.c
gcc -Ikern/debug/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/debug/kdebug.c -o obj/kern/debug/kdebug.o
+ cc kern/debug/kmonitor.c
gcc -Ikern/debug/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/debug/kmonitor.c -o obj/kern/debug/kmonitor.o
+ cc kern/driver/clock.c
gcc -Ikern/driver/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/driver/clock.c -o obj/kern/driver/clock.o
+ cc kern/driver/console.c
gcc -Ikern/driver/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/driver/console.c -o obj/kern/driver/console.o
+ cc kern/driver/picirq.c
gcc -Ikern/driver/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/driver/picirq.c -o obj/kern/driver/picirq.o
+ cc kern/driver/intr.c
gcc -Ikern/driver/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/driver/intr.c -o obj/kern/driver/intr.o
+ cc kern/trap/trap.c
gcc -Ikern/trap/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/trap/trap.c -o obj/kern/trap/trap.o
+ cc kern/trap/vectors.S
gcc -Ikern/trap/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/trap/vectors.S -o obj/kern/trap/vectors.o
+ cc kern/trap/trapentry.S
gcc -Ikern/trap/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/trap/trapentry.S -o obj/kern/trap/trapentry.o
+ cc kern/mm/pmm.c
gcc -Ikern/mm/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/mm/pmm.c -o obj/kern/mm/pmm.o
kern/mm/pmm.c:265:1: warning: ‘boot_alloc_page’ defined but not used [-Wunused-function]
 boot_alloc_page(void) {
 ^
+ cc kern/mm/default_pmm.c
gcc -Ikern/mm/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Ikern/debug/ -Ikern/driver/ -Ikern/trap/ -Ikern/mm/ -Ikern/libs/ -Ikern/sync/ -c kern/mm/default_pmm.c -o obj/kern/mm/default_pmm.o
+ cc libs/string.c
gcc -Ilibs/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/string.c -o obj/libs/string.o
+ cc libs/printfmt.c
gcc -Ilibs/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/  -c libs/printfmt.c -o obj/libs/printfmt.o
+ ld bin/kernel
ld -m    elf_i386 -nostdlib -T tools/kernel.ld -o bin/kernel  obj/kern/init/entry.o obj/kern/init/init.o obj/kern/libs/stdio.o obj/kern/libs/readline.o obj/kern/debug/panic.o obj/kern/debug/kdebug.o obj/kern/debug/kmonitor.o obj/kern/driver/clock.o obj/kern/driver/console.o obj/kern/driver/picirq.o obj/kern/driver/intr.o obj/kern/trap/trap.o obj/kern/trap/vectors.o obj/kern/trap/trapentry.o obj/kern/mm/pmm.o obj/kern/mm/default_pmm.o  obj/libs/string.o obj/libs/printfmt.o
+ cc boot/bootasm.S
gcc -Iboot/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootasm.S -o obj/boot/bootasm.o
+ cc boot/bootmain.c
gcc -Iboot/ -fno-builtin -fno-PIC -Wall -ggdb -m32 -gstabs -nostdinc  -fno-stack-protector -Ilibs/ -Os -nostdinc -c boot/bootmain.c -o obj/boot/bootmain.o
+ cc tools/sign.c
gcc -Itools/ -g -Wall -O2 -c tools/sign.c -o obj/sign/tools/sign.o
gcc -g -Wall -O2 obj/sign/tools/sign.o -o bin/sign
+ ld bin/bootblock
ld -m    elf_i386 -nostdlib -N -T tools/boot.ld obj/boot/bootasm.o obj/boot/bootmain.o -o obj/bootblock.o
'obj/bootblock.out' size: 442 bytes
build 512 bytes boot sector: 'bin/bootblock' success!
dd if=/dev/zero of=bin/ucore.img count=10000
10000+0 records in
10000+0 records out
5120000 bytes (5.1 MB, 4.9 MiB) copied, 0.0182375 s, 281 MB/s
dd if=bin/bootblock of=bin/ucore.img conv=notrunc
1+0 records in
1+0 records out
512 bytes copied, 0.00021284 s, 2.4 MB/s
dd if=bin/kernel of=bin/ucore.img seek=1 conv=notrunc
240+1 records in
240+1 records out
123128 bytes (123 kB, 120 KiB) copied, 0.000512567 s, 240 MB/s

新增了+ cc kern/init/entry.S

实验执行流程概述

本次实验主要完成ucore内核对物理内存的管理工作。参考ucore总控函数kern_init的代码,可以清楚地看到在调用完成物理内存初始化的pmm_init函数之前和之后,是已有lab1实验的工作,好像没啥修改。其实不然,ucore有两个方面的扩展:

  1. bootloader的工作有增加,在bootloader中,完成了对物理内存资源的探测工作,让ucore kernel在后续执行中能够基于bootloader探测出的物理内存情况进行物理内存管理初始化工作。
  2. bootloader不像lab1那样,直接调用kern_init函数,而是先调用位于lab2/kern/init/entry.S中的kern_entry函数。kern_entry函数的主要任务是为执行kern_init建立一个良好的C语言运行环境(设置堆栈),而且临时建立了一个段映射关系,为之后建立分页机制的过程做一个准备。完成这些工作后,才调用kern_init函数。

kern_init函数在完成一些输出并对lab1实验结果的检查后,将进入物理内存管理初始化的工作,即调用pmm_init函数完成物理内存的管理,这也是我们lab2的内容。接着是执行中断和异常相关的初始化工作,即调用pic_init函数和idt_init函数等,这些工作与lab1的中断异常初始化工作的内容是相同的。

物理内存管理要做的事情包括:

  1. 探测可用的物理内存资源,明确物理内存在哪里,有多大;
  2. 以固定页面大小来划分整个物理内存空间,并准备以此为最小内存分配单位来管理整个物理内存;
  3. 管理在内核运行过程中的每页内存,设定其可用状态(free的,used的,还是reserved的),对应在原理课上讲到的连续内存分配概念和原理的具体实现;
  4. ucore kernel建立页表, 启动分页机制,让CPU的MMU把预先建立好的页表中的页表项读入到TLB,根据页表项描述的虚拟页(Page)与物理页帧(Page Frame)的对应关系完成CPU对内存的读、写和执行操作,对应我们在原理课上讲到内存映射、页表、多级页表等概念和原理的具体实现。

在代码分析上,建议根据执行流程来直接看源代码,并可采用GDB源码调试的手段来动态地分析ucore的执行过程。内存管理相关的总体控制函数是pmm_init函数,它完成的主要工作包括:

  1. 初始化物理内存页管理器框架pmm_manager;
  2. 建立空闲的page链表,这样就可以分配以页(4KB)为单位的空闲内存;
  3. 检查物理内存页分配算法;
  4. 为确保切换到分页机制后,代码能够正常执行,先建立一个临时二级页表;
  5. 建立一一映射关系的二级页表;
  6. 使能分页机制;
  7. 从新设置全局段描述符表;
  8. 取消临时二级页表;
  9. 检查页表建立是否正确;
  10. 通过自映射机制完成页表的打印输出(这部分是扩展知识)

另外,主要注意的相关代码内容包括:

  • boot/bootasm.S中探测内存部分(从probe_memory到finish_probe的代码);
  • 管理每个物理页的Page数据结构(在mm/memlayout.h中),这个数据结构也是实现连续物理内存分配算法的关键数据结构,可通过此数据结构来完成空闲块的链接和信息存储,而基于这个数据结构的管理物理页数组起始地址就是全局变量pages,具体初始化此数组的函数位于page_init函数中;
  • 用于实现连续物理内存分配算法的物理内存页管理器框架pmm_manager,这个数据结构定义了实现内存分配算法的关键函数指针,需要完成这些函数的具体实现;
  • 设定二级页表和建立页表项以完成虚实地址映射关系,这与硬件相关,且用到不少内联函数,源代码相对难懂一些。具体完成页表和页表项建立的重要函数是boot_map_segment函数,而get_pte函数是完成虚实映射关键的关键。

探测系统物理内存布局

探测可用的物理内存资源,明确物理内存在哪里,有多大。当 ucore 被启动之后,最重要的事情就是知道还有多少内存可用,一般来说,获取内存大小的方法有 BIOS 中断调用直接探测两种。

BIOS 中断调用方法

参考地址:地址1 地址2

  1. 一般只能在实模式下完成;
  2. 有三种方式获取内存布局,都基于INT 15h中断,分别为88h、e801h、e820h,但这三种方式并非在所有情况下都能工作。Linux kernel 会依次尝试这三 种方法。在本实验中,我们通过e820h中断获取内存信息。因为e820h中断必须在实模式下使用,所以我们在 bootloader 进入保护模式之前调用这个 BIOS 中断,并且把 e820h映射结构保存在物理地址0x8000处。

BIOS中断里面的“中断向量表”和操作系统起来后使用的“中断描述符表”是两个不同的东西,前者工作在实模式,后者工作在保护模式。

BIOS中断调用必须在实模式下进行,所以在bootloader进入保护模式前完成这部分工作相对比较合适。这些部分由boot/bootasm.S中从probe_memory处到finish_probe处的代码部分完成完成。通过BIOS中断获取内存可调用参数为e820h的INT 15h BIOS中断。BIOS通过系统内存映射地址描述符(Address Range Descriptor)格式来表示系统物理内存布局,其具体表示如下:

Offset  Size    Description
00h     8字节    base address               # 系统内存块基地址
08h     8字节    length in bytes            # 系统内存大小
10h     4字节    type of address range      # 内存类型

这个缓冲区结构体定义在memlayout.h中:

 80 // some constants for bios interrupt 15h AX = 0xE820
 81 #define E820MAX             20      // number of entries in E820MAP                                                                                  
 82 #define E820_ARM            1       // address range memory
 83 #define E820_ARR            2       // address range reserved
 84   
 85 struct e820map {           
 86     int nr_map; // 记录有多少个map,最大E820MAX,探测异常则为12345
 87     struct {               
 88         uint64_t addr;     
 89         uint64_t size;     
 90         uint32_t type;     
 91     } __attribute__((packed)) map[E820MAX];
 92 };

看下面的(Values for System Memory Map address type)

Values for System Memory Map address type:
01h    memory, available to OS
02h    reserved, not available (e.g. system ROM, memory-mapped device)
03h    ACPI Reclaim Memory (usable by OS after reading ACPI tables)
04h    ACPI NVS Memory (OS is required to save this memory between NVS sessions)
other  not defined yet -- treat as Reserved

INT15h BIOS中断的详细调用参数:

eax:e820h,INT 15的中断调用参数;
edx:534D4150h (即4个ASCII字符“SMAP”) ,这只是一个签名而已;
ebx:如果是第一次调用或内存区域扫描完毕,则为0。 如果不是,则存放上次调用之后的计数值;
ecx:保存地址范围描述符的内存大小,应该大于等于20字节;
es:di:指向保存地址范围描述符结构的缓冲区,BIOS把信息写入这个结构的起始地址。

此中断的返回值为:

cflags的CF位:若INT 15中断执行成功,则不置位,否则置位;
eax        :534D4150h ('SMAP') ;
es:di      :指向保存地址范围描述符的缓冲区,此时缓冲区内的数据已由BIOS填写完毕
ebx        :下一个地址范围描述符的计数地址
ecx        :返回BIOS往ES:DI处写的地址范围描述符的字节大小
ah         :失败时保存出错代码

这样,我们通过调用INT 15h BIOS中断,递增di的值(20的倍数),让BIOS帮我们查找出一个一个的内存布局entry,并放入到一个保存地址范围描述符结构的缓冲区中(在物理地址0x8000处),供后续的ucore进一步进行物理内存管理。

直接探测方法

必须在保护模式下完成。

实现物理内存探测

给中断函数传参:
eax:e820h,INT 15的中断调用参数;
edx:534D4150h (即4个ASCII字符“SMAP”) ,这只是一个签名而已;
ebx:如果是第一次调用或内存区域扫描完毕,则为0。 如果不是,则存放上次调用之后的计数值;
ecx:保存地址范围描述符的内存大小,应该大于等于20字节;
es:di:指向保存地址范围描述符结构的缓冲区,BIOS把信息写入这个结构的起始地址。


中断函数的返回:
cflags的CF位:若INT 15中断执行成功,则不置位,否则置位;
eax        :534D4150h ('SMAP') ;
es:di      :指向保存地址范围描述符的缓冲区,此时缓冲区内的数据已由BIOS填写完毕
ebx        :下一个地址范围描述符的计数地址
ecx        :返回BIOS往ES:DI处写的地址范围描述符的字节大小
ah         :失败时保存出错代码


 85 struct e820map {           
 86     int nr_map;            
 87     struct {               
 88         uint64_t addr;     
 89         uint64_t size;     
 90         uint32_t type;     
 91     } __attribute__((packed)) map[E820MAX];// 单个map大小正好20B
 92 };


# 探测物理内存资源
 46 probe_memory:
        # 对0x8000处的32位单元清零,即将位于0x8000处的struct e820map的成员nr_map清零
 47     movl $0, 0x8000        
        # ebx:如果是第一次调用或内存区域扫描完毕,则为0。如果不是,则存放上次调用之后的计数值;
        # ebx作为入参,这里先将其清零
 48     xorl %ebx, %ebx   
        # es:di:指向保存地址范围描述符结构的缓冲区,BIOS把信息写入这个结构的起始地址。
        # 0x8000~0x8003这三个字节是struct e820map的成员nr_map
        # 作为入参,设置调用INT 15h BIOS中断后,BIOS返回的映射地址描述符(map)的起始地址
 49     movw $0x8004, %di      
 50 start_probe:  
        # INT 15的中断调用参数
 51     movl $0xE820, %eax     
        # 入参ecx:保存单个地址范围描述符的内存大小,应该大于等于20字节;
        # 设置地址范围描述符的大小为20字节,其大小等于struct e820map的成员变量map的单个大小
 52     movl $20, %ecx    
        # 设置edx为534D4150h (即4个ASCII字符“SMAP”),这是一个约定
 53     movl $SMAP, %edx   
        # 调用int 0x15中断,要求BIOS返回一个用地址范围描述符表示的内存段信息
 54     int $0x15       
        # 中断返回:cflags的CF位,若INT 15中断执行成功,则不置位,否则置位;
        # jnc表示CF=0,则跳转,即若INT 15中断执行成功则跳转到cont
        # 如果eflags的CF位为0,则表示还有内存段需要探测
 55     jnc cont       
        # 探测有问题,结束探测
        # 注意,如果探测失败,就看struct e820map的成员nr_map是不是12345
 56     movw $12345, 0x8000    
 57     jmp finish_probe       
 58 cont: 
        # 设置下一个BIOS返回的映射地址描述符的起始地址
        # 从E820MAX看,最多可以有20个,所以最多弄20次;
        # 单个的map是20B,所以这里要跨过20B
 59     addw $20, %di    
        # 递增struct e820map的成员变量nr_map
        # nr_map就是用来记录有多少个地址范围描述符(map)的
 60     incl 0x8000      
        # 如果INT0x15返回的ebx为零,表示探测结束,否则继续探测
 61     cmpl $0, %ebx          
 62     jnz start_probe        
 63 finish_probe:              
 64 
        # 从这里开始就和以前的差不多了
 65     # Switch from real to protected mode, using a bootstrap GDT
 66     # and segment translation that makes virtual addresses
 67     # identical to physical addresses, so that the
 68     # effective memory map does not change during the switch.                                                                                        
 69     lgdt gdtdesc           
 70     movl %cr0, %eax        
......

上述代码正常执行完毕后,在0x8000地址处保存了从BIOS中获得的内存分布信息,即整个结构体struct e820map,并按照struct e820map的设置来进行填充,nr_map记录一共有多少个“地址范围描述符(map)”,如果探测异常,则nr_map为12345,最大为E820MAX。这部分信息将在bootloader启动ucore后,由ucore的page_init函数来根据struct e820map类型的memmap(定义了起始地址为0x8000,struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE);来完成对整个机器中的物理内存的总体管理,这个时候相当于有了对当前整个物理内存的认知,有了进行内存管理的原料。

以页帧为单位管理物理内存

Page数据结构

在获得可用物理内存范围后,系统需要建立相应的数据结构来管理以物理页(按4KB对齐,且大小为4KB的物理内存单元)为最小单位的整个物理内存,以配合后续涉及的分页管理机制。每个物理页可以用一个 Page数据结构来表示。由于一个物理页需要占用一个Page结构的空间,Page结构在设计时须尽可能小,以减少对内存的占用。Page的定义在kern/mm/memlayout.h中。以页为单位的物理内存分配管理的实现在kern/default_pmm.[ch]。

为了与以后的分页机制配合,我们首先需要建立对整个计算机的每一个物理页的属性用结构Page来表示,它包含了映射此物理页的虚拟页个数,描述物理页属性的flags和双向链接各个Page结构的page_link双向链表。

struct Page {
    int ref;        // page frame's reference counter
    uint32_t flags; // array of flags that describe the status of the page frame
    unsigned int property;// the num of free block, used in first fit pm manager
    list_entry_t page_link;// free list link
};

这里看看Page数据结构的各个成员变量有何具体含义。

  • ref:表示这样页帧被页表的引用记数。如果这个页帧被页表引用了,即在某页表中有一个页表项设置了一个虚拟页到这个Page管理的物理页帧的映射关系,就会把Page的ref加一;反之,若页表项取消,即映射关系解除,就会把Page的ref减一。
  • flags:表示此物理页的状态标记,进一步查看kern/mm/memlayout.h中的定义,可以看到:
/* Flags describing the status of a page frame */
#define PG_reserved                 0       // the page descriptor is reserved for kernel or unusable
#define PG_property                 1       // the member 'property' is valid

      这表示flags目前用到了两个bit表示页目前具有的两种属性。 

  1. bit 0PG_reserved表示此页是否被保留(reserved)。如果是被保留的页,则bit 0会设置为1,且不能放到空闲页链表中,即这样的页不是空闲页,不能动态分配与释放。比如目前内核代码占用的物理页帧就属于这样“被保留”的页。
  2. bit 1PG_property表示此页是否是free的,如果设置为1,表示这页是free的,可以被分配;如果设置为0,表示这页已经被分配出去了,不能再被二次分配。另外,本实验这里取的名字PG_property比较不直观 ,主要是我们可以设计不同的页分配算法(best fit, buddy system等),那么这个PG_property就有不同的含义了。
  • property:记录某连续内存空闲块的大小(即地址连续的空闲页的个数)。这里需要注意的是用到此成员变量的这个Page比较特殊,是这个连续内存空闲地址最小的一页(即头一页帧, Head Page)。连续内存空闲块利用这个页的成员变量property来记录在此块内的空闲页帧的个数。这里使用的名字property也不是很直观,原因与上面类似,在不同的页分配算法中,property有不同的含义。
  • page_link:便于把多个连续内存空闲链接在一起的双向链表指针。这里需要注意的是用到此成员变量的这个Page比较特殊,是这个连续内存空闲块地址最小的一页(即头一页帧, Head Page)。连续内存空闲块利用这个页的成员变量page_link来链接比它地址小和大的其他连续内存空闲块

free_area_t数据结构

在初始情况下,也许当前物理内存的空闲物理页帧都是连续的,这样就形成了一个大的连续内存空闲块,但随着物理页帧的分配与释放,这个大的连续内存空闲块会分裂为一系列地址不连续的多个小连续内存空闲块,且每个连续内存空闲块内部的物理页帧是连续的。那么为了有效地管理这些小连续内存空闲块。所有的连续内存空闲块可用一个双向链表管理起来,便于分配和释放,为此定义了一个free_area_t数据结构,包含了一个list_entry结构的双向链表指针和记录当前空闲页的个数的无符号整型变量nr_free。其中的链表指针指向了空闲的物理页帧。

/* free_area_t - maintains a doubly linked list to record free (unused) pages */
typedef struct {
      list_entry_t free_list;      // the list header
      unsigned int nr_free;        // # of free pages in this free list
} free_area_t;

尽管free_area.free_list链表每个节点链接的是物理页帧,但是实际意思却是“内存块”,即该链表的每个节点表示的是“空闲物理内存块”,只是每个“块”用该块的首个物理页帧来表示,该页帧的property字段会记录该块包含了多少个空闲物理页帧,且由于pages是个数组,里面的元素位于连续的内存,因此找到首个物理页帧,同时知道该块有多少个物理页帧,那么就可以通过数组索引来找到所有的物理页帧描述符!!!提示,物理页帧描述符与实际的物理页帧不需要什么映射,直接计算就可以,因为物理页帧是从0地址到最高地址按照4KB划分出来的,pages数组的索引就表示第几个物理页帧,比如pages[3],表示pages数组第三个(从零开始)元素,即第三个物理页帧描述符,对应的物理页帧起始地址为4096 * 3

由此产生的两个问题

有了以上两个数据结构,ucore就可以管理起来整个以物理页帧为单位的物理内存空间。接下来需要解决两个问题:

  1. 管理页帧级物理内存空间所需的Page结构的内存空间从哪里开始,占多大空间?
  2. 空闲内存空间的起始地址在哪里?

对于这两个问题,我们首先根据bootloader给出的内存布局信息找出最大的物理内存地址maxpa(定义在page_init函数中的局部变量),由于x86的起始物理内存地址为0,所以可知需要管理的物理页帧个数为

npage = maxpa / PGSIZE

这样,我们就可以预估出管理页帧级物理内存空间所需的整个Page结构的内存空间所需的内存大小为

sizeof(struct Page) * npage

由于bootloader所加载的ucore的结束地址(用全局指针变量end记录,在kernel.ld中定义)之上的空间(即大于end的地址)没有被使用,所以我们可以把end按页帧大小为边界取整后(即找到end地址之后首个完整的物理页帧起始地址),作为管理页级物理内存空间所需的Page结构的内存空间,记为:

pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);,形如下图所示,

为了简化起见,从地址0到地址pages+ sizeof(struct Page) * npage)结束的物理内存空间设定为已占用物理内存空间(起始0~640KB的空间是空闲的),地址pages+ sizeof(struct Page) * npage)之上的空间为空闲物理内存空间,这时的空闲空间起始地址为

uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage);

我们需要把这两部分空间给标识出来。

  • 对于所有物理空间,通过如下语句即可实现占用标记
for (i = 0; i < npage; i ++) {
    SetPageReserved(pages + i);
}

      SetPageReserved只需把物理地址对应的Page结构中的flags标志设置为PG_reserved ,表示这些页已经被使用了,将来不能被用于分配。

  • 根据探测到的空闲物理空间,通过如下语句即可实现空闲标记
// 获得空闲空间的起始地址begin和结束地址end
......
init_memmap(pa2page(begin), (end - begin) / PGSIZE);

init_memmap函数则是把空闲物理页对应的Page结构中的flags和引用计数ref清零,并加到free_area.free_list指向的双向列表中,为将来的空闲页管理做好初始化准备工作。

其实实验二在内存分配和释放方面最主要的作用是建立了一个物理内存页管理器框架,这实际上是一个函数指针列表,定义如下:

struct pmm_manager {
      const char *name; //物理内存页管理器的名字
      void (*init)(void); //初始化内存管理器
      void (*init_memmap)(struct Page *base, size_t n); //初始化管理空闲内存页的数据结构
      struct Page *(*alloc_pages)(size_t n); //分配n个物理内存页
      void (*free_pages)(struct Page *base, size_t n); //释放n个物理内存页
      size_t (*nr_free_pages)(void); //返回当前剩余的空闲页数
      void (*check)(void); //用于检测分配/释放实现是否正确的辅助函数
};

重点是实现init_memmap/ alloc_pages/ free_pages这三个函数。当完成物理内存页管理初始化工作后,计算机系统的内存布局如下图所示:

确认bootloader到底把kernel加载到哪个位置

直接对kern_entry打断点,根本无法在这个位置断住,但是在kern_init处打断点却可以,显示的指令执行地址在0xC0000000之上,而且此时不能访问0x100000这个地址,所以推测此时gdb显示的地址已经不再是实际的物理地址了,应该是虚拟地址了。所以为了用GDB验证bootloader把kernel到底加载到内存的哪个位置,直接在一开始的时候就直接用地址来设置断点,断住的时候查看断点附近的指令和源代码的指令是不是对应就可以看出来。

直接在0x100000处设置断点,以下内容为断在这个地址后的30条指令
(gdb) x /30i $pc
=> 0x100000:	mov    $0x8000,%ax
   0x100003:	adc    %ax,(%bx,%si)
   0x100005:	mov    %eax,%cr3
   0x100008:	mov    %cr0,%eax
   0x10000b:	or     $0x2f,%ax
   0x10000e:	add    $0x8380,%ax
   0x100011:	loopne 0x100006
   0x100013:	mov    %eax,%cr0
   0x100016:	lea    (%di),%ax
   0x100018:	push   %ds
   0x100019:	add    %dl,(%bx,%si)
   0x10001b:	sar    $0xe0,%bh
   0x10001e:	xor    %ax,%ax
   0x100020:	mov    %ax,0x8000
   0x100023:	adc    %ax,%ax
   0x100025:	mov    $0x0,%bp
   0x100028:	add    %al,(%bx,%si)
   0x10002a:	mov    $0x7000,%sp
   0x10002d:	adc    %ax,%ax
   0x10002f:	call   0x100034
   0x100032:	add    %al,(%bx,%si)
   0x100034:	jmp    0x100034
   0x100036:	push   %bp
   0x100037:	mov    %sp,%bp
   0x100039:	sub    $0x18,%sp
   0x10003c:	mov    $0xaf28,%dx
   0x10003f:	adc    %ax,%ax
   0x100041:	mov    $0xa000,%ax
   0x100044:	adc    %ax,%ax
   0x100046:	sub    %ax,%dx

经过对比发现这些指令和kern_entry中的代码是对应的!!!接下来看到0x10002f: call 0x100034,所以在0x10002f0x100034处设置断点,发现根本断不住!!!揭开谜底,

 85 /* bootmain - the entry of bootloader */
 86 void
 87 bootmain(void) {
......
 95 
 96     struct proghdr *ph, *eph;
 97 
 98     // load each program segment (ignores ph flags)
 99     ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff);
100     eph = ph + ELFHDR->e_phnum;
101     for (; ph < eph; ph ++) {  
            # 谜底就在“ph->p_va & 0xFFFFFF”!!!
102         readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
103     }
104 
105     // call the entry point from the ELF header
106     // note: does not return
        # 谜底就在“ELFHDR->e_entry & 0xFFFFFF”!!!
107     ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
108 
......
115 }

谜底就在这里的“和0xFFFFFF做按位与”上面!!!ph->p_vaELFHDR->e_entry的确都是虚拟地址没错,虚拟地址都在0xC0000000之上没错,ELFHDR->e_entry == 0xC0100000,但是0xC0100000 & 0xFFFFFF == 0x100000所以bootloader最终还是把kernel的代码段和数据段都加载到了从实际物理地址0x100000处开始的地方!!!代码段和数据段的ph->p_va都需要和0xffffff做按位与的操作,然后readseg()会将代码段和数据段放到完成按位与之后的物理地址!这样的话,就算内存不足4GB也没有问题了,因为bootloader会把kernel继续加载到从实际物理内存地址1MB(1MB=0x100000)开始往后的地方!!!

因为kern_entry在实际物理内存中的地址是0x100000而不是0xC0100000,而且在进入kern_entry之前并没有设置一个基本的页机制,所以此时想通过b kern_entry这种方式打断点是断不住的,因为实际断在了物理地址0xC0100000处(因为没有开启页映射,所以此时把0xC0100000当成了实际的物理地址,在这个地址打断点,当然不会断住)!!!要想断住就只能在实际的物理地址0x100000处打断点,实际情况也正是这样的!

在kern_init处以b kern_init方式打断点会断住,而且会发现此时gdb显示的代码地址在0xC0000000往上的位置。但是从上面的分析知道,kernel的代码段和数据段都是被加载到实际物理内存0x100000处的,gdb之所以会这么显示,说明此时显示出来的指令的地址已经不是实际的物理地址了,而是虚拟地址(或线性地址)了!!!为什么在进入kern_entry前后,gdb的显示结果会有如此大的变化(进入前,显示实际的物理地址,进入后,就开始显示虚拟地址)?说明一定是kern_entry里面对页机制进行了配置,导致在刚刚进入kern_init的时候,就已经开始使用虚拟地址映射即启动页机制了!!!

整个kernel文件中的地址都在0xC0000000之上,即kern_entry__boot_pgdir__boot_pt1kern_init等的地址都在0xC0000000之上!!!需要经过映射才能到实际的物理地址上。

$ readelf bin/kernel -l

Elf file type is EXEC (Executable file)
Entry point 0xc0100000
There are 3 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0xc0100000 0xc0100000 0x158d5 0x158d5 R E 0x1000
  LOAD           0x017000 0xc0116000 0xc0116000 0x05000 0x05f28 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .stab .stabstr 
   01     .data .data.pgdir .bss 
   02     

从上面可以看出,数据段的虚拟地址起始位置是0xc0116000,与0xffffff做按位与的操作之后,数据段会被放置到实际物理地址0x116000处!而且根据obj/kernel.asm文件中的内容可以看到,__boot_pgdir的实际物理地址就是0x118000,正好差了一个0xC0000000,而且在0x116000之上,加载位置没错,就是按照这种方式加载的。

   11 kern_entry:
   12     # load pa of boot pgdir
   13     movl $REALLOC(__boot_pgdir), %eax
   14 c0100000:   b8 00 80 11 00          mov    $0x118000,%eax
   
   # 所以__boot_pgdir的虚拟地址是0xC0118000

entry.S

分析入口地址

$ readelf bin/kernel -l

Elf file type is EXEC (Executable file)
Entry point 0xc0100000
There are 3 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0xc0100000 0xc0100000 0x158d5 0x158d5 R E 0x1000
  LOAD           0x017000 0xc0116000 0xc0116000 0x05000 0x05f28 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10

 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .stab .stabstr 
   01     .data .data.pgdir .bss 
   02     

在kernel.ld中,

 ENTRY(kern_entry)

在boot/bootmain.c中,

# 从boot/bootasm.S中调用bootmain从而进入boot/bootmain.c
# 通过分析kernel.ld和查看bin/kernel的二进制文件,可以确认kernel的入口地址是0xC0100000
# 所以下面ELFHDR->e_entry的地址就是0xC0100000,而由kernel.ld知,该地址下的函数名为
# kern_entry,位于init/entry.S

 85 /* bootmain - the entry of bootloader */
 86 void
 87 bootmain(void) {
......
104 
105     // call the entry point from the ELF header
106     // note: does not return
        # 由此可知,这个入口地址就是init/entry.S中的kern_entry
        # 注意“& 0xFFFFFF”,这才能得到入口的实际物理地址
107     ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();
108 
109 bad:
110     outw(0x8A00, 0x8A00);
111     outw(0x8A00, 0x8E00);
112 
113     /* do nothing */
114     while (1);
115 }

虚拟内存布局

在k/m/memlayout.h中,

/* *
 * Virtual memory map:                                          Permissions
 *                                                              kernel/user
 *
 *     4G ------------------> +---------------------------------+
 *                            |                                 |
 *                            |         Empty Memory (*)        |
 *                            |                                 |
 *                            +---------------------------------+ 0xFB000000
 *                            |   Cur. Page Table (Kern, RW)    | RW/-- PTSIZE
 *     VPT -----------------> +---------------------------------+ 0xFAC00000
 *                            |        Invalid Memory (*)       | --/--                                                                                  
 *     KERNTOP -------------> +---------------------------------+ 0xF8000000
 *                            |                                 |
 *                            |    Remapped Physical Memory     | RW/-- KMEMSIZE
 *                            |                                 |
 *     KERNBASE ------------> +---------------------------------+ 0xC0000000=3GB
 *                            |                                 |
 *                            |                                 |
 *                            |                                 |
 *                            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 * (*) Note: The kernel ensures that "Invalid Memory" is *never* mapped.
 *     "Empty Memory" is normally unmapped, but user programs may map pages
 *     there if desired.
 *
 * */
 
 # 内存一共4GB,而0xC0000000是正好3GB的位置
 
/* All physical memory mapped at this address */
#define KERNBASE            0xC0000000                                                                                                                   
#define KMEMSIZE            0x38000000                  // the maximum amount of physical memory
#define KERNTOP             (KERNBASE + KMEMSIZE)

设置二级页表的page table

 64 .set i, 0
 65 __boot_pt1:
 66 .rept 1024
 67     .long i * PGSIZE + (PTE_P | PTE_W)                                                                                                               
 68     .set i, i + 1
 69 .endr

# i * PGSIZE 的结果
0x00000000  0x00001000  0x00002000  0x00003000  0x00004000  0x00005000  0x00006000  0x00007000  0x00008000  0x00009000  0x0000a000  0x0000b000  0x0000c000  0x0000d000  0x0000e000  0x0000f000  0x00010000  0x00011000  0x00012000  0x00013000  0x00014000  0x00015000  0x00016000  0x00017000  0x00018000  0x00019000  0x0001a000  0x0001b000  0x0001c000  0x0001d000  0x0001e000  0x0001f000  
0x00020000  0x00021000  0x00022000  0x00023000  0x00024000  0x00025000  0x00026000  0x00027000  0x00028000  0x00029000  0x0002a000  0x0002b000  0x0002c000  0x0002d000  0x0002e000  0x0002f000  0x00030000  0x00031000  0x00032000  0x00033000  0x00034000  0x00035000  0x00036000  0x00037000  0x00038000  0x00039000  0x0003a000  0x0003b000  0x0003c000  0x0003d000  0x0003e000  0x0003f000  
0x00040000  0x00041000  0x00042000  0x00043000  0x00044000  0x00045000  0x00046000  0x00047000  0x00048000  0x00049000  0x0004a000  0x0004b000  0x0004c000  0x0004d000  0x0004e000  0x0004f000  0x00050000  0x00051000  0x00052000  0x00053000  0x00054000  0x00055000  0x00056000  0x00057000  0x00058000  0x00059000  0x0005a000  0x0005b000  0x0005c000  0x0005d000  0x0005e000  0x0005f000  
0x00060000  0x00061000  0x00062000  0x00063000  0x00064000  0x00065000  0x00066000  0x00067000  0x00068000  0x00069000  0x0006a000  0x0006b000  0x0006c000  0x0006d000  0x0006e000  0x0006f000  0x00070000  0x00071000  0x00072000  0x00073000  0x00074000  0x00075000  0x00076000  0x00077000  0x00078000  0x00079000  0x0007a000  0x0007b000  0x0007c000  0x0007d000  0x0007e000  0x0007f000  
0x00080000  0x00081000  0x00082000  0x00083000  0x00084000  0x00085000  0x00086000  0x00087000  0x00088000  0x00089000  0x0008a000  0x0008b000  0x0008c000  0x0008d000  0x0008e000  0x0008f000  0x00090000  0x00091000  0x00092000  0x00093000  0x00094000  0x00095000  0x00096000  0x00097000  0x00098000  0x00099000  0x0009a000  0x0009b000  0x0009c000  0x0009d000  0x0009e000  0x0009f000  
0x000a0000  0x000a1000  0x000a2000  0x000a3000  0x000a4000  0x000a5000  0x000a6000  0x000a7000  0x000a8000  0x000a9000  0x000aa000  0x000ab000  0x000ac000  0x000ad000  0x000ae000  0x000af000  0x000b0000  0x000b1000  0x000b2000  0x000b3000  0x000b4000  0x000b5000  0x000b6000  0x000b7000  0x000b8000  0x000b9000  0x000ba000  0x000bb000  0x000bc000  0x000bd000  0x000be000  0x000bf000  
0x000c0000  0x000c1000  0x000c2000  0x000c3000  0x000c4000  0x000c5000  0x000c6000  0x000c7000  0x000c8000  0x000c9000  0x000ca000  0x000cb000  0x000cc000  0x000cd000  0x000ce000  0x000cf000  0x000d0000  0x000d1000  0x000d2000  0x000d3000  0x000d4000  0x000d5000  0x000d6000  0x000d7000  0x000d8000  0x000d9000  0x000da000  0x000db000  0x000dc000  0x000dd000  0x000de000  0x000df000  
0x000e0000  0x000e1000  0x000e2000  0x000e3000  0x000e4000  0x000e5000  0x000e6000  0x000e7000  0x000e8000  0x000e9000  0x000ea000  0x000eb000  0x000ec000  0x000ed000  0x000ee000  0x000ef000  0x000f0000  0x000f1000  0x000f2000  0x000f3000  0x000f4000  0x000f5000  0x000f6000  0x000f7000  0x000f8000  0x000f9000  0x000fa000  0x000fb000  0x000fc000  0x000fd000  0x000fe000  0x000ff000  
0x00100000  0x00101000  0x00102000  0x00103000  0x00104000  0x00105000  0x00106000  0x00107000  0x00108000  0x00109000  0x0010a000  0x0010b000  0x0010c000  0x0010d000  0x0010e000  0x0010f000  0x00110000  0x00111000  0x00112000  0x00113000  0x00114000  0x00115000  0x00116000  0x00117000  0x00118000  0x00119000  0x0011a000  0x0011b000  0x0011c000  0x0011d000  0x0011e000  0x0011f000  
0x00120000  0x00121000  0x00122000  0x00123000  0x00124000  0x00125000  0x00126000  0x00127000  0x00128000  0x00129000  0x0012a000  0x0012b000  0x0012c000  0x0012d000  0x0012e000  0x0012f000  0x00130000  0x00131000  0x00132000  0x00133000  0x00134000  0x00135000  0x00136000  0x00137000  0x00138000  0x00139000  0x0013a000  0x0013b000  0x0013c000  0x0013d000  0x0013e000  0x0013f000  
0x00140000  0x00141000  0x00142000  0x00143000  0x00144000  0x00145000  0x00146000  0x00147000  0x00148000  0x00149000  0x0014a000  0x0014b000  0x0014c000  0x0014d000  0x0014e000  0x0014f000  0x00150000  0x00151000  0x00152000  0x00153000  0x00154000  0x00155000  0x00156000  0x00157000  0x00158000  0x00159000  0x0015a000  0x0015b000  0x0015c000  0x0015d000  0x0015e000  0x0015f000  
0x00160000  0x00161000  0x00162000  0x00163000  0x00164000  0x00165000  0x00166000  0x00167000  0x00168000  0x00169000  0x0016a000  0x0016b000  0x0016c000  0x0016d000  0x0016e000  0x0016f000  0x00170000  0x00171000  0x00172000  0x00173000  0x00174000  0x00175000  0x00176000  0x00177000  0x00178000  0x00179000  0x0017a000  0x0017b000  0x0017c000  0x0017d000  0x0017e000  0x0017f000  
0x00180000  0x00181000  0x00182000  0x00183000  0x00184000  0x00185000  0x00186000  0x00187000  0x00188000  0x00189000  0x0018a000  0x0018b000  0x0018c000  0x0018d000  0x0018e000  0x0018f000  0x00190000  0x00191000  0x00192000  0x00193000  0x00194000  0x00195000  0x00196000  0x00197000  0x00198000  0x00199000  0x0019a000  0x0019b000  0x0019c000  0x0019d000  0x0019e000  0x0019f000  
0x001a0000  0x001a1000  0x001a2000  0x001a3000  0x001a4000  0x001a5000  0x001a6000  0x001a7000  0x001a8000  0x001a9000  0x001aa000  0x001ab000  0x001ac000  0x001ad000  0x001ae000  0x001af000  0x001b0000  0x001b1000  0x001b2000  0x001b3000  0x001b4000  0x001b5000  0x001b6000  0x001b7000  0x001b8000  0x001b9000  0x001ba000  0x001bb000  0x001bc000  0x001bd000  0x001be000  0x001bf000  
0x001c0000  0x001c1000  0x001c2000  0x001c3000  0x001c4000  0x001c5000  0x001c6000  0x001c7000  0x001c8000  0x001c9000  0x001ca000  0x001cb000  0x001cc000  0x001cd000  0x001ce000  0x001cf000  0x001d0000  0x001d1000  0x001d2000  0x001d3000  0x001d4000  0x001d5000  0x001d6000  0x001d7000  0x001d8000  0x001d9000  0x001da000  0x001db000  0x001dc000  0x001dd000  0x001de000  0x001df000  
0x001e0000  0x001e1000  0x001e2000  0x001e3000  0x001e4000  0x001e5000  0x001e6000  0x001e7000  0x001e8000  0x001e9000  0x001ea000  0x001eb000  0x001ec000  0x001ed000  0x001ee000  0x001ef000  0x001f0000  0x001f1000  0x001f2000  0x001f3000  0x001f4000  0x001f5000  0x001f6000  0x001f7000  0x001f8000  0x001f9000  0x001fa000  0x001fb000  0x001fc000  0x001fd000  0x001fe000  0x001ff000  
0x00200000  0x00201000  0x00202000  0x00203000  0x00204000  0x00205000  0x00206000  0x00207000  0x00208000  0x00209000  0x0020a000  0x0020b000  0x0020c000  0x0020d000  0x0020e000  0x0020f000  0x00210000  0x00211000  0x00212000  0x00213000  0x00214000  0x00215000  0x00216000  0x00217000  0x00218000  0x00219000  0x0021a000  0x0021b000  0x0021c000  0x0021d000  0x0021e000  0x0021f000  
0x00220000  0x00221000  0x00222000  0x00223000  0x00224000  0x00225000  0x00226000  0x00227000  0x00228000  0x00229000  0x0022a000  0x0022b000  0x0022c000  0x0022d000  0x0022e000  0x0022f000  0x00230000  0x00231000  0x00232000  0x00233000  0x00234000  0x00235000  0x00236000  0x00237000  0x00238000  0x00239000  0x0023a000  0x0023b000  0x0023c000  0x0023d000  0x0023e000  0x0023f000  
0x00240000  0x00241000  0x00242000  0x00243000  0x00244000  0x00245000  0x00246000  0x00247000  0x00248000  0x00249000  0x0024a000  0x0024b000  0x0024c000  0x0024d000  0x0024e000  0x0024f000  0x00250000  0x00251000  0x00252000  0x00253000  0x00254000  0x00255000  0x00256000  0x00257000  0x00258000  0x00259000  0x0025a000  0x0025b000  0x0025c000  0x0025d000  0x0025e000  0x0025f000  
0x00260000  0x00261000  0x00262000  0x00263000  0x00264000  0x00265000  0x00266000  0x00267000  0x00268000  0x00269000  0x0026a000  0x0026b000  0x0026c000  0x0026d000  0x0026e000  0x0026f000  0x00270000  0x00271000  0x00272000  0x00273000  0x00274000  0x00275000  0x00276000  0x00277000  0x00278000  0x00279000  0x0027a000  0x0027b000  0x0027c000  0x0027d000  0x0027e000  0x0027f000  
0x00280000  0x00281000  0x00282000  0x00283000  0x00284000  0x00285000  0x00286000  0x00287000  0x00288000  0x00289000  0x0028a000  0x0028b000  0x0028c000  0x0028d000  0x0028e000  0x0028f000  0x00290000  0x00291000  0x00292000  0x00293000  0x00294000  0x00295000  0x00296000  0x00297000  0x00298000  0x00299000  0x0029a000  0x0029b000  0x0029c000  0x0029d000  0x0029e000  0x0029f000  
0x002a0000  0x002a1000  0x002a2000  0x002a3000  0x002a4000  0x002a5000  0x002a6000  0x002a7000  0x002a8000  0x002a9000  0x002aa000  0x002ab000  0x002ac000  0x002ad000  0x002ae000  0x002af000  0x002b0000  0x002b1000  0x002b2000  0x002b3000  0x002b4000  0x002b5000  0x002b6000  0x002b7000  0x002b8000  0x002b9000  0x002ba000  0x002bb000  0x002bc000  0x002bd000  0x002be000  0x002bf000  
0x002c0000  0x002c1000  0x002c2000  0x002c3000  0x002c4000  0x002c5000  0x002c6000  0x002c7000  0x002c8000  0x002c9000  0x002ca000  0x002cb000  0x002cc000  0x002cd000  0x002ce000  0x002cf000  0x002d0000  0x002d1000  0x002d2000  0x002d3000  0x002d4000  0x002d5000  0x002d6000  0x002d7000  0x002d8000  0x002d9000  0x002da000  0x002db000  0x002dc000  0x002dd000  0x002de000  0x002df000  
0x002e0000  0x002e1000  0x002e2000  0x002e3000  0x002e4000  0x002e5000  0x002e6000  0x002e7000  0x002e8000  0x002e9000  0x002ea000  0x002eb000  0x002ec000  0x002ed000  0x002ee000  0x002ef000  0x002f0000  0x002f1000  0x002f2000  0x002f3000  0x002f4000  0x002f5000  0x002f6000  0x002f7000  0x002f8000  0x002f9000  0x002fa000  0x002fb000  0x002fc000  0x002fd000  0x002fe000  0x002ff000  
0x00300000  0x00301000  0x00302000  0x00303000  0x00304000  0x00305000  0x00306000  0x00307000  0x00308000  0x00309000  0x0030a000  0x0030b000  0x0030c000  0x0030d000  0x0030e000  0x0030f000  0x00310000  0x00311000  0x00312000  0x00313000  0x00314000  0x00315000  0x00316000  0x00317000  0x00318000  0x00319000  0x0031a000  0x0031b000  0x0031c000  0x0031d000  0x0031e000  0x0031f000  
0x00320000  0x00321000  0x00322000  0x00323000  0x00324000  0x00325000  0x00326000  0x00327000  0x00328000  0x00329000  0x0032a000  0x0032b000  0x0032c000  0x0032d000  0x0032e000  0x0032f000  0x00330000  0x00331000  0x00332000  0x00333000  0x00334000  0x00335000  0x00336000  0x00337000  0x00338000  0x00339000  0x0033a000  0x0033b000  0x0033c000  0x0033d000  0x0033e000  0x0033f000  
0x00340000  0x00341000  0x00342000  0x00343000  0x00344000  0x00345000  0x00346000  0x00347000  0x00348000  0x00349000  0x0034a000  0x0034b000  0x0034c000  0x0034d000  0x0034e000  0x0034f000  0x00350000  0x00351000  0x00352000  0x00353000  0x00354000  0x00355000  0x00356000  0x00357000  0x00358000  0x00359000  0x0035a000  0x0035b000  0x0035c000  0x0035d000  0x0035e000  0x0035f000  
0x00360000  0x00361000  0x00362000  0x00363000  0x00364000  0x00365000  0x00366000  0x00367000  0x00368000  0x00369000  0x0036a000  0x0036b000  0x0036c000  0x0036d000  0x0036e000  0x0036f000  0x00370000  0x00371000  0x00372000  0x00373000  0x00374000  0x00375000  0x00376000  0x00377000  0x00378000  0x00379000  0x0037a000  0x0037b000  0x0037c000  0x0037d000  0x0037e000  0x0037f000  
0x00380000  0x00381000  0x00382000  0x00383000  0x00384000  0x00385000  0x00386000  0x00387000  0x00388000  0x00389000  0x0038a000  0x0038b000  0x0038c000  0x0038d000  0x0038e000  0x0038f000  0x00390000  0x00391000  0x00392000  0x00393000  0x00394000  0x00395000  0x00396000  0x00397000  0x00398000  0x00399000  0x0039a000  0x0039b000  0x0039c000  0x0039d000  0x0039e000  0x0039f000  
0x003a0000  0x003a1000  0x003a2000  0x003a3000  0x003a4000  0x003a5000  0x003a6000  0x003a7000  0x003a8000  0x003a9000  0x003aa000  0x003ab000  0x003ac000  0x003ad000  0x003ae000  0x003af000  0x003b0000  0x003b1000  0x003b2000  0x003b3000  0x003b4000  0x003b5000  0x003b6000  0x003b7000  0x003b8000  0x003b9000  0x003ba000  0x003bb000  0x003bc000  0x003bd000  0x003be000  0x003bf000  
0x003c0000  0x003c1000  0x003c2000  0x003c3000  0x003c4000  0x003c5000  0x003c6000  0x003c7000  0x003c8000  0x003c9000  0x003ca000  0x003cb000  0x003cc000  0x003cd000  0x003ce000  0x003cf000  0x003d0000  0x003d1000  0x003d2000  0x003d3000  0x003d4000  0x003d5000  0x003d6000  0x003d7000  0x003d8000  0x003d9000  0x003da000  0x003db000  0x003dc000  0x003dd000  0x003de000  0x003df000  
0x003e0000  0x003e1000  0x003e2000  0x003e3000  0x003e4000  0x003e5000  0x003e6000  0x003e7000  0x003e8000  0x003e9000  0x003ea000  0x003eb000  0x003ec000  0x003ed000  0x003ee000  0x003ef000  0x003f0000  0x003f1000  0x003f2000  0x003f3000  0x003f4000  0x003f5000  0x003f6000  0x003f7000  0x003f8000  0x003f9000  0x003fa000  0x003fb000  0x003fc000  0x003fd000  0x003fe000  0x003ff000 

在kern/init/entry.S中,

# kern_entry函数的主要任务是为执行kern_init建立一个良好的C语言运行环境(设置堆栈),
# 而且临时建立了一个段映射关系(弄了个二级页表),为之后建立分页机制的过程做一个准备。
# 完成这些工作后,才调用kern_init函数。


#define PGSIZE          4096                    // bytes mapped by a page
/* page table/directory entry flags */ 
#define PTE_P           0x001                   // Present                                                                                           
#define PTE_W           0x002                   // Writeable


# KERNBASE是0xC0000000,而kern_entry的入口地址是0xc0100000,
# 即0xc0100000=KERNBASE+0x100000,而这个0x100000就是lab1里实际加载内核的地址,
# 这里相当于偏移了3GB去加载内核!!!kern_entry作为整个kernel的入口地址,也是entry.S
# 的入口地址,所以从entry.S文件的kern_entry处往下的所有内容,其地址都在0xc0100000之上。
# 例如bootstack,bootstacktop,__boot_pgdir,__boot_pt1等,对应的地址都在0xc0100000之上。
# 0xC0000000是虚拟地址,x是虚拟地址,REALLOC(x)宏其实就是从虚拟地址到物理地址的映射,
# 通过这个宏根据虚拟地址得到物理地址。
4 #define REALLOC(x) (x - KERNBASE) 


# 设置二级页表中的table,将其指向实际的物理页帧
# 重复1024次,(PTE_P | PTE_W)的结果是0x3,这里的汇编代码展开相当于
# .long 4096*0+3
# .set i, 1
# .long 4096*1+3
# .set i, 2
# .long 4096*2+3
# .set i, 3
# .long 4096*3+3
# .set i, 4
#  ... 
# .long 4096*1023+3
# .set i, 1024
# 4096=4KB,代表一张物理页帧的大小;
# 从0到1023,一共有1024页。每页4096B,所以共涉及4MB的物理内存。
# 这里的代码是将全部的页表项(PTE)都列出来,
 31                                  12 11                      0
+--------------------------------------+-------+---+-+-+---+-+-+-+
|                                      |       |   | | |   |U|R| |
|      PAGE FRAME ADDRESS 31..12       | AVAIL |0 0|D|A|0 0|/|/|P|
|                                      |       |   | | |   |S|W| |
+--------------------------------------+-------+---+-+-+---+-+-+-+
# 如图所示,每个PTE占4B,高20位表示物理页帧的起始地址(实际使用时需要左移12位,
# 而12位二进制数表示4096,高20位其实表示第几个4096),
# 低12位表示PTE的属性,从上面“i * PGSIZE 的结果”中可以看出,“i * PGSIZE”生成的结果的低12位
# 全部都是0,高20位才是地址有效位,所以下面这段汇编代码才可以直接用“i * PGSIZE”加“(PTE_P | PTE_W)”
# 来表示一个完整的PTE,想从PTE中获取物理页帧的实际物理起始地址,只要将PTE的高20位左移12位即可
# “__boot_pt1”是二级页表关系中table的虚拟起始地址,其实际物理起始地址为“REALLOC(__boot_pt1)”
# table的每一项,即PTE都对应一个实际的物理页帧的起始地址,一共对应了1024张物理页帧,每张物理
# 页帧大小为4KB,实际对应了4MB的内存。首张物理页帧的起始地址是0,可以看出,物业页帧的安排是从零开始的!
# 注意,这段汇编代码只是设置了table,设置了1024张物理页帧,此时并不知道实际有多少物理内存,
# 实际情况可能达不到4MB的内存!
 64 .set i, 0
 65 __boot_pt1:
 66 .rept 1024
 67     .long i * PGSIZE + (PTE_P | PTE_W)                                                                                                               
 68     .set i, i + 1
 69 .endr

综上,page table有如下内容:

  1. 起始地址(虚拟地址):__boot_pt1
  2. 大小:4096B;
  3. 共有1024条PTE,每条4B,每条PTE对应一张物理页帧;
  4. 每条PTE保存的都是实际物理页帧的起始地址(物理地址)和页帧的属性;
  5. 物理页帧的起始地址(物理地址)从0开始;
  6. 每张物理页帧4096B;
  7. 一张page table可以管理1024张物理页帧,即一张page table可以管理4MB的物理内存。

设置二级页表的page directory

在kern/init/entry.S中,

 50 # kernel builtin pgdir
 51 # an initial page directory (Page Directory Table, PDT)
 52 # These page directory table and page table can be reused!
 
 # 设置数据段,也是页目录段
 53 .section .data.pgdir
 
 # 设置4KB对齐
 54 .align PGSIZE
 
 # 页目录表的起始地址(是虚拟地址,在0xC0000000之上)
 # 以0xC0000000(KERNBASE)为界,分别填充page directory
 55 __boot_pgdir:
 56 .globl __boot_pgdir
        # 填充低于0xC0000000(KERNBASE)部分的PDE,即(0~0xC0000000)部分
 57     # map va 0 ~ 4M to pa 0 ~ 4M (temporary)
        # 从page table的设置中可知,目前只设置了4MB的物理内存映射
        # 目前table只有一张,即__boot_pt1,按照原理課,其实table应该有1024张!
        # 这里是设置首条PDE,REALLOC(__boot_pt1)是__boot_pt1的实际物理地址,所以
        # 首条PDE里面保存的是__boot_pt1的实际物理地址及其属性。
 58     .long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
 
        # (KERNBASE >> PGSHIFT >> 10 << 2)将移位操作视为乘法和除法,
        # 右移12位,即除以4KB;右移10位,即除以1024;左移2位,即乘以4。
        # “KERNBASE >> PGSHIFT”即0xC0000000/4096,一张物理页帧是4096B,故
        # 该除法表示从0到0xC0000000共有多少个物理页帧!!!由于每张物业页帧对应一条
        # PTE,所以这个除法也表示共有多少条PTE!!!
        # 每张table有1024个PTE,所以所有的PTE除以1024,就得到一共有多少张有效的table!!!
        # 每张table对应一条PDE,所有PDE组成唯一的一张页目录表page directory
        # 每条PDE大小4B,所以所有的PDE条数乘以4就得到整个page directory的大小!!!
        # 因此(KERNBASE >> PGSHIFT >> 10 << 2)得到的就是page directory的大小!!!
        # (. - __boot_pgdir)是从“__boot_pgdir”到“.space”之间的字节数,即首条PDE的大小
        # “(KERNBASE >> PGSHIFT >> 10 << 2) - (. - __boot_pgdir)” 就表示
        # 整个page directory的大小减去首条PDE之后的大小(字节数),
        # 配合上.space,就是让这么些字节全部置零,表示该page directory只有首条PDE是
        # 有效的,其他都是无效的PDE!!!
        # “.space”这句会将“0xc00-4=3068”个字节置零,涉及767(=3068/4)个PDE
        # 即(0~0xC0000000)部分共涉及768个PDE,其中767个PDE都是无效的
 59     .space (KERNBASE >> PGSHIFT >> 10 << 2) - (. - __boot_pgdir) # pad to PDE of KERNBASE                                                            
 
        
        # 填充高于0xC0000000(KERNBASE)部分的PDE,即(>0xC0000000)部分
 60     # map va KERNBASE + (0 ~ 4M) to pa 0 ~ 4M
        # 高于0xC0000000(KERNBASE)部分的首个PDE仍然指向__boot_pt1的实际物理起始地址
        # 即page directory中,有两个不同的PDE实际指向了同一张table的物理起始地址!!!
 61     .long REALLOC(__boot_pt1) + (PTE_P | PTE_U | PTE_W)
        # 根据原理課要求,page directory的大小为4096B,前面(0~0xC0000000)部分已经使用
        # 了3072B,(>0xC0000000)部分的首条PDE又占用4B,所以“PGSIZE - (. - __boot_pgdir)”
        # 就是“4096-3076=1020”个字节,“.space”指令将其全部置零,涉及255个PDE,都是无效的。
 62     .space PGSIZE - (. - __boot_pgdir) # pad to PGSIZE

综上,以虚拟地址0xC0000000(KERNBASE)为中间界设置页目录表page directorypage directory有如下内容,

  1. 起始地址(虚拟地址):__boot_pgdir
  2. 大小:4096B;
  3. 共有1024条PDE,每条4B;
  4. 第0个PDE和第768个PDE有效,都指向同一张table的起始地址(是物理地址,因为REALLOC(__boot_pt1),所以判断为物理地址);
  5. 第1个~第767个PDE、第769个~第1023个PDE都是无效的;

当前的二级页表视图

kern_entry入口

(gdb) b *0x100000
Breakpoint 1 at 0x100000
(gdb) c
Continuing.

Breakpoint 1, 0x00100000 in ?? ()
(gdb) info reg
eax            0x100000	1048576
ecx            0x0	0
edx            0x0	0
ebx            0x10094	65684
esp            0x7bec	0x7bec
ebp            0x7bf8	0x7bf8
esi            0x10094	65684
edi            0x807c	32892
eip            0x100000	0x100000
eflags         0x6	[ PF ]
cs             0x8	8
ss             0x10	16
ds             0x10	16
es             0x10	16
fs             0x10	16
gs             0x10	16
(gdb) x /20i $pc
=> 0x100000:	mov    $0x8000,%ax
   0x100003:	adc    %ax,(%bx,%si)
   0x100005:	mov    %eax,%cr3
   0x100008:	mov    %cr0,%eax
   0x10000b:	or     $0x2f,%ax
   0x10000e:	add    $0x8380,%ax
   0x100011:	loopne 0x100006
   0x100013:	mov    %eax,%cr0
   0x100016:	lea    (%di),%ax
   0x100018:	push   %ds
   0x100019:	add    %dl,(%bx,%si)
   0x10001b:	sar    $0xe0,%bh
   0x10001e:	xor    %ax,%ax
   0x100020:	mov    %ax,0x8000
   0x100023:	adc    %ax,%ax
   0x100025:	mov    $0x0,%bp
   0x100028:	add    %al,(%bx,%si)
   0x10002a:	mov    $0x7000,%sp
   0x10002d:	adc    %ax,%ax
   0x10002f:	call   0x100034
(gdb) b *0x100013               # 在开启页机制的地方打断点
Breakpoint 2 at 0x100013
(gdb) c
Continuing.

Breakpoint 2, 0x00100013 in ?? ()
(gdb) info reg
eax            0x80050033	-2147155917
ecx            0x0	0
edx            0x0	0
ebx            0x10094	65684
esp            0x7bec	0x7bec
ebp            0x7bf8	0x7bf8
esi            0x10094	65684
edi            0x807c	32892
eip            0x100013	0x100013
eflags         0x86	[ PF SF ]
cs             0x8	8
ss             0x10	16
ds             0x10	16
es             0x10	16
fs             0x10	16
gs             0x10	16
(gdb) x /20i $pc
=> 0x100013:	mov    %eax,%cr0      # 执行完这条指令才算开启页机制
   0x100016:	lea    (%di),%ax
   0x100018:	push   %ds
   0x100019:	add    %dl,(%bx,%si)
   0x10001b:	sar    $0xe0,%bh
   0x10001e:	xor    %ax,%ax
   0x100020:	mov    %ax,0x8000
   0x100023:	adc    %ax,%ax
   0x100025:	mov    $0x0,%bp
   0x100028:	add    %al,(%bx,%si)
   0x10002a:	mov    $0x7000,%sp
   0x10002d:	adc    %ax,%ax
   0x10002f:	call   0x100034
   0x100032:	add    %al,(%bx,%si)
   0x100034:	jmp    0x100034
   0x100036:	push   %bp
   0x100037:	mov    %sp,%bp
   0x100039:	sub    $0x18,%sp
   0x10003c:	mov    $0xaf28,%dx
   0x10003f:	adc    %ax,%ax
(gdb) si
0x00100016 in ?? ()
(gdb) info reg
eax            0x80050033	-2147155917
ecx            0x0	0
edx            0x0	0
ebx            0x10094	65684
esp            0x7bec	0x7bec
ebp            0x7bf8	0x7bf8
esi            0x10094	65684
edi            0x807c	32892
eip            0x100016	0x100016
eflags         0x86	[ PF SF ]
cs             0x8	8
ss             0x10	16
ds             0x10	16
es             0x10	16
fs             0x10	16
gs             0x10	16
(gdb) x /20i $pc
=> 0x100016:	lea    (%di),%ax
   0x100018:	push   %ds
   0x100019:	add    %dl,(%bx,%si)
   0x10001b:	sar    $0xe0,%bh
   0x10001e:	xor    %ax,%ax
   0x100020:	mov    %ax,0x8000
   0x100023:	adc    %ax,%ax
   0x100025:	mov    $0x0,%bp
   0x100028:	add    %al,(%bx,%si)
   0x10002a:	mov    $0x7000,%sp
   0x10002d:	adc    %ax,%ax
   0x10002f:	call   0x100034
   0x100032:	add    %al,(%bx,%si)
   0x100034:	jmp    0x100034
   0x100036:	push   %bp
   0x100037:	mov    %sp,%bp
   0x100039:	sub    $0x18,%sp
   0x10003c:	mov    $0xaf28,%dx
   0x10003f:	adc    %ax,%ax
   0x100041:	mov    $0xa000,%ax
(gdb) si
0x0010001c in ?? ()
(gdb) info reg        # 注意执行了leal指令之后,eax的内容变成了0xc010001e
eax            0xc010001e	-1072693218
ecx            0x0	0
edx            0x0	0
ebx            0x10094	65684
esp            0x7bec	0x7bec
ebp            0x7bf8	0x7bf8
esi            0x10094	65684
edi            0x807c	32892
eip            0x10001c	0x10001c   # 注意此时eip的值还是0x1...的物理地址
eflags         0x86	[ PF SF ]
cs             0x8	8
ss             0x10	16
ds             0x10	16
es             0x10	16
fs             0x10	16
gs             0x10	16
(gdb) x /20i $pc
=> 0x10001c:	jmp    *%ax
   0x10001e:	xor    %ax,%ax
   0x100020:	mov    %ax,0x8000
   0x100023:	adc    %ax,%ax
   0x100025:	mov    $0x0,%bp
   0x100028:	add    %al,(%bx,%si)
   0x10002a:	mov    $0x7000,%sp
   0x10002d:	adc    %ax,%ax
   0x10002f:	call   0x100034
   0x100032:	add    %al,(%bx,%si)
   0x100034:	jmp    0x100034
   0x100036:	push   %bp
   0x100037:	mov    %sp,%bp
   0x100039:	sub    $0x18,%sp
   0x10003c:	mov    $0xaf28,%dx
   0x10003f:	adc    %ax,%ax
   0x100041:	mov    $0xa000,%ax
   0x100044:	adc    %ax,%ax
   0x100046:	sub    %ax,%dx
   0x100048:	mov    %dx,%ax
(gdb) si                              # 执行完jmp *%eax指令
next () at kern/init/entry.S:27
27	    xorl %eax, %eax
(gdb) info reg
eax            0xc010001e	-1072693218
ecx            0x0	0
edx            0x0	0
ebx            0x10094	65684
esp            0x7bec	0x7bec
ebp            0x7bf8	0x7bf8
esi            0x10094	65684
edi            0x807c	32892
eip            0xc010001e	0xc010001e <next>   # 注意此时eip的值已经更新为0xc...的虚拟地址
eflags         0x86	[ PF SF ]
cs             0x8	8
ss             0x10	16
ds             0x10	16
es             0x10	16
fs             0x10	16
gs             0x10	16
(gdb) x /20i $pc             # 此时gdb已经开始在虚拟地址处显示指令,说明已经开始使用页机制映射模式
=> 0xc010001e <next>:	xor    %ax,%ax
   0xc0100020 <next+2>:	mov    %ax,0x8000
   0xc0100023 <next+5>:	adc    %ax,%ax
   0xc0100025 <next+7>:	mov    $0x0,%bp
   0xc0100028 <next+10>:	add    %al,(%bx,%si)
   0xc010002a <next+12>:	mov    $0x7000,%sp
   0xc010002d <next+15>:	adc    %ax,%ax
   0xc010002f <next+17>:	call   0xc0100034 <spin>
   0xc0100032 <next+20>:	add    %al,(%bx,%si)
   0xc0100034 <spin>:	jmp    0xc0100034 <spin>
   0xc0100036 <kern_init>:	push   %bp
   0xc0100037 <kern_init+1>:	mov    %sp,%bp
   0xc0100039 <kern_init+3>:	sub    $0x18,%sp
   0xc010003c <kern_init+6>:	mov    $0xaf28,%dx
   0xc010003f <kern_init+9>:	adc    %ax,%ax
   0xc0100041 <kern_init+11>:	mov    $0xa000,%ax
   0xc0100044 <kern_init+14>:	adc    %ax,%ax
   0xc0100046 <kern_init+16>:	sub    %ax,%dx
   0xc0100048 <kern_init+18>:	mov    %dx,%ax
   0xc010004a <kern_init+20>:	sub    $0x4,%sp
(gdb) x /20i 0x10001e            # 实际物理地址处的指令,和虚拟地址处的指令完全相同
   0x10001e:	xor    %ax,%ax     # 但是物理地址和虚拟地址正好差值为0xC0000000,
   0x100020:	mov    %ax,0x8000  # 符合汇编代码中的映射规则:REALLOC()
   0x100023:	adc    %ax,%ax
   0x100025:	mov    $0x0,%bp
   0x100028:	add    %al,(%bx,%si)
   0x10002a:	mov    $0x7000,%sp
   0x10002d:	adc    %ax,%ax
   0x10002f:	call   0x100034
   0x100032:	add    %al,(%bx,%si)
   0x100034:	jmp    0x100034
   0x100036:	push   %bp
   0x100037:	mov    %sp,%bp
   0x100039:	sub    $0x18,%sp
   0x10003c:	mov    $0xaf28,%dx
   0x10003f:	adc    %ax,%ax
   0x100041:	mov    $0xa000,%ax
   0x100044:	adc    %ax,%ax
   0x100046:	sub    %ax,%dx
   0x100048:	mov    %dx,%ax
   0x10004a:	sub    $0x4,%sp

启动页机制后的地址推算

以执行该地址处的指令为例,注意0xc010001e是虚拟地址
=> 0xc010001e <next>:	xor    %ax,%ax
从GDB结果看,0x10001e是该指令所在的实际物理地址
0x10001e:	xor    %ax,%ax

十六进制:0xc010001e
二进制  :1100000000 0100000000 000000011110
         页目录表内偏移量(10位)   页表内偏移量(10位)      物理页帧内偏移量
二进制  :1100000000              0100000000             000000011110
十六进制:0x300                   0x100                  0x1E
十进制  :768                     256                    30
解释   :页目录表第768项           页表第256项              物理页帧内第30个字节
(从零开始)                     (即第256个物理页帧)

# 从前文可知,page directory的第768个PDE正好是那个有效的PDE。
# 物理页帧是从物理地址0开始,每4KB一张页帧连续排布的,这里是第256张物理页帧(从零开始),
# 其起始物理地址就是256*4096=1048576=0x100000(这也是kernel代码在物理内存中的实际起始地址)。
# 加上物理页帧内偏移的30个字节,即0x100000+0x1e=0x10001e
# 从而确认,在开启页机制之后,通过页机制对虚拟地址的访问方式,可以得到实际的物理地址!!!

在kern/init/entry.S中,

  6 .text
  7 .globl kern_entry          
  8 kern_entry:     
  9     # load pa of boot pgdir
        # $REALLOC(__boot_pgdir)获取“__boot_pgdir”的实际物理地址
        # 将“__boot_pgdir”的实际物理地址加载到cr3寄存器,从而记录页目录表的实际物理起始地址
 10     movl $REALLOC(__boot_pgdir), %eax
 11     movl %eax, %cr3        
 12     
 13     # enable paging        
        # 通过修改cr0寄存器中的标志值,主要是CR0_PG,开启页机制
 14     movl %cr0, %eax        
 15     orl $(CR0_PE | CR0_PG | CR0_AM | CR0_WP | CR0_NE | CR0_TS | CR0_EM | CR0_MP), %eax
 16     andl $~(CR0_TS | CR0_EM), %eax  
 17     movl %eax, %cr0        
 18 
        # 此时已经开启页机制
 19     # update eip           
 20     # now, eip = 0x1..... 此时此刻,eip内保存的仍然是0x100000之上的实际的物理地址
        # 将next的地址(虚拟地址,0xc010001e)加载到eax寄存器
 21     leal next, %eax        
 22     # set eip = KERNBASE + 0x1..... 
        # 使用jmp间接跳转的方式,跳到虚拟地址0xc010001e处(即汇编指令next处)开始执行指令,
        # 通过GDB可以看出,在完成jmp指令之前,eip还是直接在物理内存地址中运行,
        # 完成jmp指令之后,eip内部马上就变成了0xc010001e这样的虚拟地址,而且既然
        # 能够利用虚拟地址正确的逐条执行指令,说明整个页机制已经开始工作,页机制映射正常!
 23     jmp *%eax              
 24 next:
 25 
 26     # unmap va 0 ~ 4M, it's temporary mapping
 27     xorl %eax, %eax
 28     movl %eax, __boot_pgdir
 29 
 30     # set ebp, esp
 31     movl $0x0, %ebp
 32     # the kernel stack region is from bootstack -- bootstacktop,
 33     # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h
 34     movl $bootstacktop, %esp
 35     # now kernel stack is ready , call the first C function
 36     call kern_init

next入口

# 在执行next中的指令之前,查看0x8000处的值为6,这是当初探测实际物理内存大小时候的e820map数
# 组元素,表示有6个“地址范围描述符(map)”
(gdb) x /2w 0x8000
0x8000:	0x00000006	0x00000000
(gdb) x /20i $pc
=> 0xc010001e <next>:	xor    %ax,%ax
   0xc0100020 <next+2>:	mov    %ax,0x8000     # 注意这里,真的是0x8000这个地址吗?!
   0xc0100023 <next+5>:	adc    %ax,%ax
   0xc0100025 <next+7>:	mov    $0x0,%bp
   0xc0100028 <next+10>:	add    %al,(%bx,%si)
   0xc010002a <next+12>:	mov    $0x7000,%sp
   0xc010002d <next+15>:	adc    %ax,%ax
   0xc010002f <next+17>:	call   0xc0100034 <spin>
   0xc0100032 <next+20>:	add    %al,(%bx,%si)
   0xc0100034 <spin>:	jmp    0xc0100034 <spin>
   0xc0100036 <kern_init>:	push   %bp
   0xc0100037 <kern_init+1>:	mov    %sp,%bp
   0xc0100039 <kern_init+3>:	sub    $0x18,%sp
   0xc010003c <kern_init+6>:	mov    $0xaf28,%dx
   0xc010003f <kern_init+9>:	adc    %ax,%ax
   0xc0100041 <kern_init+11>:	mov    $0xa000,%ax
   0xc0100044 <kern_init+14>:	adc    %ax,%ax
   0xc0100046 <kern_init+16>:	sub    %ax,%dx
   0xc0100048 <kern_init+18>:	mov    %dx,%ax
   0xc010004a <kern_init+20>:	sub    $0x4,%sp
(gdb) si
28	    movl %eax, __boot_pgdir
(gdb) si
31	    movl $0x0, %ebp
(gdb) x /20i $pc
=> 0xc0100025 <next+7>:	mov    $0x0,%bp
   0xc0100028 <next+10>:	add    %al,(%bx,%si)
   0xc010002a <next+12>:	mov    $0x7000,%sp
   0xc010002d <next+15>:	adc    %ax,%ax
   0xc010002f <next+17>:	call   0xc0100034 <spin>
   0xc0100032 <next+20>:	add    %al,(%bx,%si)
   0xc0100034 <spin>:	jmp    0xc0100034 <spin>
   0xc0100036 <kern_init>:	push   %bp
   0xc0100037 <kern_init+1>:	mov    %sp,%bp
   0xc0100039 <kern_init+3>:	sub    $0x18,%sp
   0xc010003c <kern_init+6>:	mov    $0xaf28,%dx
   0xc010003f <kern_init+9>:	adc    %ax,%ax
   0xc0100041 <kern_init+11>:	mov    $0xa000,%ax
   0xc0100044 <kern_init+14>:	adc    %ax,%ax
   0xc0100046 <kern_init+16>:	sub    %ax,%dx
   0xc0100048 <kern_init+18>:	mov    %dx,%ax
   0xc010004a <kern_init+20>:	sub    $0x4,%sp
   0xc010004d <kern_init+23>:	push   %ax
   0xc010004e <kern_init+24>:	push   $0x0
   0xc0100050 <kern_init+26>:	push   $0xa000
(gdb) x /2w 0x8000
0x8000:	Cannot access memory at address 0x8000

# 这个时候不让访问这个地址了!!!想到推算0x8000对应的虚拟地址来查看这个地址下的数据,
0x8000=32768
32768/4096=8
# 说明正好对应第8个物理页帧的起始地址,没有页帧内的偏移
# 构造虚拟地址为:
#        768           8               0 
# 二进制:1100000000    0000001000      000000000000
# 十六进制:0xC0008000

(gdb) x /2w 0xc0008000
0xc0008000:	0x00000006	0x00000000
# 发现next开头的几条汇编指令并没有改变0x8000地址下的值(即nr_map的值并没有被改变!)

在obj/kernel.asm中,

   36 c010001e <next>:
   37 next:
   38 
   39     # unmap va 0 ~ 4M, it's temporary mapping
   40     xorl %eax, %eax
   41 c010001e:   31 c0                   xor    %eax,%eax
   42     movl %eax, __boot_pgdir
   43 c0100020:   a3 00 80 11 c0          mov    %eax,0xc0118000                                                                                         
# 从反汇编的文件中可以看出,__boot_pgdir的虚拟地址是0xc0118000,并不是GDB中显示的0x8000!
# 保护模式下使用的是32位的地址!!!

此时出现两个问题,

1.为什么进入next之后,0x100000附近的物理地址就不能再通过GDB访问了?

答:估计是因为启动页机制之后,页表里面的属性位起作用了。

2.原本物理地址0x8000处的值为什么没有被改变?

答:注意看GDB中的寄存器%ax,不是%eax,而%ax是16位寄存器,%eax才是32位寄存器,所以真正的地址0xc0118000是无法被16位寄存器%ax完整保存的,只能保存低16位即0x8000,所以才给人一种错觉好像动的地址是0x8000,但实际上此时已经开启了保护模式,使用32位的寄存器,此时真正动的地址是0xc0118000!!!教训:今后看GDB要看清使用的是几位的寄存器,当前应该使用的是几位的寄存器,当前是实模式还是保护模式,结合反汇编(也要分清处于实模式还是保护模式,位数是多少)的结果去看。所以说,nr_map的实际物理地址是0x8000,对应的虚拟地址是0xc0008000;而__boot_pgdir的实际物理地址是0x118000,虚拟地址是0xc0118000

在kern/init/entry.S中,

 68 #define KSTACKPAGE          2                           // # of pages in kernel stack
 69 #define KSTACKSIZE          (KSTACKPAGE * PGSIZE)       // sizeof kernel stack                                                                       
 
 
 24 next:
 25 
 26     # unmap va 0 ~ 4M, it's temporary mapping
        # “__boot_pgdir”的首个PDE是有效的,但前面其实也没用到,这里直接就将该PDE置零了!
 27     xorl %eax, %eax
 28     movl %eax, __boot_pgdir
 29 
        # 设置堆栈(bootloader里调用C函数bootmain时设置过一次堆栈,现在kern_entry要
        # 调用C函数kern_init了,所以也要设置一次堆栈)
 30     # set ebp, esp
 31     movl $0x0, %ebp
 32     # the kernel stack region is from bootstack -- bootstacktop,
 33     # the kernel stack size is KSTACKSIZE (8KB)defined in memlayout.h
        # 根据反汇编结果(kernel.asm)可知,“bootstacktop”是“0xc0118000”
        # c010002a:   bc 00 80 11 c0          mov    $0xc0118000,%esp 
        # 注意此时直接将“bootstacktop”的虚拟地址放到了esp中,没问题,因为页机制已经启动,
        # 最终堆栈的实际地址还是要映射到实际的物理地址上去!!!
 34     movl $bootstacktop, %esp
 35     # now kernel stack is ready , call the first C function
 36     call kern_init
 ......
 42 .data
 43 .align PGSIZE              
 44     .globl bootstack       
 45 bootstack:
        # “.space”指令将KSTACKSIZE(8KB)的内存空间全部清零作为堆栈
 46     .space KSTACKSIZE                                                                                                                                
 47     .globl bootstacktop    
 48 bootstacktop:

init.c

print_kerninfo()

tools/kernel.ld中的内容:
  8 SECTIONS {                 
  9     /* Load the kernel at this address: "." means the current address */
 10     . = 0xC0100000;        
 11 
 12     .text : {              
 13         *(.text .stub .text.* .gnu.linkonce.t.*)
 14     }
 15   
 16     PROVIDE(etext = .); /* Define the 'etext' symbol to this value */
......
 52     PROVIDE(edata = .);
 53 
 54     .bss : {
 55         *(.bss)
 56     }
 57 
 58     PROVIDE(end = .);
......
 63 }
# 从这里可以看出,etext 是代码段的结束地址,同时也是rodata段的起始地址;
# edata 是rodata段/调试信息段/数据段的结束地址,同时也是bss段的起始地址;
# end 是bss段的结束地址,也是kernel文件可以被加载到内存的最后一部分的虚拟地址,
# 在内存中,end的地址标志着内核elf文件被加载到内存之后的结束地址(虚拟地址)。
# 在内存中,end之前是属于kernel的内容(无论代码还是数据),end之后就可以随便用了,不用担心
# 会踩到内核的代码或数据。


$ readelf -l bin/kernel
Elf file type is EXEC (Executable file)
Entry point 0xc0100000
There are 3 program headers, starting at offset 52
Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  LOAD           0x001000 0xc0100000 0xc0100000 0x158d5 0x158d5 R E 0x1000
  LOAD           0x017000 0xc0116000 0xc0116000 0x05000 0x05f28 RW  0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x10
 Section to Segment mapping:
  Segment Sections...
   00     .text .rodata .stab .stabstr 
   01     .data .data.pgdir .bss 
   02     


打印结果:
Special kernel symbols:
  entry  0xc0100036 (phys)       # kern_init的地址
  etext  0xc01063fa (phys)
  edata  0xc011b000 (phys)
  end    0xc011bf28 (phys)
Kernel executable memory footprint: 112KB


215 /* *
216  * print_kerninfo - print the information about kernel, including the location
217  * of kernel entry, the start addresses of data and text segements, the start
218  * address of free memory and how many memory that kernel has used.
219  * */
220 void
221 print_kerninfo(void) {                                                                                                                               
222     extern char etext[], edata[], end[], kern_init[];
223     cprintf("Special kernel symbols:\n");
224     cprintf("  entry  0x%08x (phys)\n", kern_init);
225     cprintf("  etext  0x%08x (phys)\n", etext);
226     cprintf("  edata  0x%08x (phys)\n", edata);
227     cprintf("  end    0x%08x (phys)\n", end);
        # 整个kernel的数据段中包含了调试信息段,“end-kern_init”的时候应该也会减去这部分,
        # 所以计算“Kernel executable memory”的时候得把这部分空间加回来(个人猜测)
228     cprintf("Kernel executable memory footprint: %dKB\n", (end - kern_init + 1023)/1024); 
229 }

pmm_init()

记录页目录表的物理地址

在k/m/pmm.c中,

/* *
 * PADDR - takes a kernel virtual address (an address that points above KERNBASE),
 * where the machine's maximum 256MB of physical memory is mapped and returns the
 * corresponding physical address.  It panics if you pass it a non-kernel virtual address.
 * */
# 该宏相当于汇编文件中的REALLOC(x)
#define PADDR(kva) ({                                                   \                                                                                
            uintptr_t __m_kva = (uintptr_t)(kva);                       \
            if (__m_kva < KERNBASE) {                                   \
                panic("PADDR called with invalid kva %08lx", __m_kva);  \
            }                                                           \
            __m_kva - KERNBASE;                                         \
        })


275 void
276 pmm_init(void) {
277     // We've already enabled paging
        # 用boot_cr3记录页目录表的实际物理地址
278     boot_cr3 = PADDR(boot_pgdir);
......
318 }

boot_pgdir作为指针变量,保存的是页目录表的起始地址。由于此时已经启动页机制,以虚拟地址0xc0119000为例,分析通过页机制获取的物理地址是否和直接使用__m_kva - KERNBASE获取的物理地址相同:

直接计算
0xc0119000 - 0xc0000000 = 0x119000

页机制
0xc0119000
1100000000		0100011001		000000000000
768             281
4096*281=1150976=0x119000
证毕!

init_pmm_manager()

在k/m/pmm.h中,

 10 // pmm_manager is a physical memory management class. A special pmm manager - XXX_pmm_manager
 11 // only needs to implement the methods in pmm_manager class, then XXX_pmm_manager can be used
 12 // by ucore to manage the total physical memory space.
 13 struct pmm_manager {       
 14     const char *name;                                 // XXX_pmm_manager's name
 15     void (*init)(void);                               // initialize internal description&management data structure                                   
 16                                                       // (free block list, number of free block) of XXX_pmm_manager 
 17     void (*init_memmap)(struct Page *base, size_t n); // setup description&management data structcure according to
 18                                                       // the initial free physical memory space 
 19     struct Page *(*alloc_pages)(size_t n);            // allocate >=n pages, depend on the allocation algorithm 
 20     void (*free_pages)(struct Page *base, size_t n);  // free >=n pages with "base" addr of Page descriptor structures(memlayout.h)
 21     size_t (*nr_free_pages)(void);                    // return the number of free pages 
 22     void (*check)(void);                              // check the correctness of XXX_pmm_manager 
 23 };

在k/m/default_pmm.c中,

303 const struct pmm_manager default_pmm_manager = {                                                                                                     
304     .name = "default_pmm_manager",
305     .init = default_init,
306     .init_memmap = default_init_memmap,
307     .alloc_pages = default_alloc_pages,
308     .free_pages = default_free_pages,
309     .nr_free_pages = default_nr_free_pages,
310     .check = default_check,
311 };      

在k/m/pmm.c中,

//We need to alloc/free the physical memory (granularity is 4KB or other size).
//So a framework of physical memory manager (struct pmm_manager)is defined in pmm.h
//First we should init a physical memory manager(pmm) based on the framework.
//Then pmm can alloc/free the physical memory. 
//Now the first_fit/best_fit/worst_fit/buddy_system pmm are available.

45 // physical memory management
46 const struct pmm_manager *pmm_manager;

137 //init_pmm_manager - initialize a pmm_manager instance
138 static void
139 init_pmm_manager(void) { 
        # 使用默认的内存管理方式作为当前的主要内存管理方式
140     pmm_manager = &default_pmm_manager;
141     cprintf("memory management: %s\n", pmm_manager->name);

        # initialize internal description&management data structure
        # (free block list, number of free block) of XXX_pmm_manager
        # 这个init就是default_init(),作用仅仅就是初始化空闲内存块链表,单纯
        # 做初始化链表的操作,根本没动内存相关的概念
142     pmm_manager->init();
143 }

在k/m/default_pmm.c中,

# 用free_area_t管理空闲的内存块
/* free_area_t - maintains a doubly linked list to record free (unused) pages */
typedef struct { 
    list_entry_t free_list;         // the list header
    unsigned int nr_free;           // # of free pages in this free list
} free_area_t;      

 /* *
  * list_init - initialize a new entry
  * @elm:        new entry to be initialized
  * */
 static inline void
 list_init(list_entry_t *elm) {                                                                                                                       
     elm->prev = elm->next = elm;
 }


    # free_area其实是空闲内存“块”链表的头节点,几个内存页组成一个块!
 96 free_area_t free_area;                                                                                                                               
 97 
 98 #define free_list (free_area.free_list)
 99 #define nr_free (free_area.nr_free)
100     
101 static void
102 default_init(void) {  
        # 初始化链表节点内容,将prev/next指针都指向当前节点
103     list_init(&free_list);
        # 将当前链表的空闲块个数初始化为0
104     nr_free = 0;
105 }

page_init()

e820map:
  memory: size                       begin       end-1
  memory: 0x0009fc00(000000654336), [0x00000000, 0x0009fbff], type = 1.
  memory: 0x00000400(000000001024), [0x0009fc00, 0x0009ffff], type = 2.
  memory: 0x00010000(000000065536), [0x000f0000, 0x000fffff], type = 2.
  memory: 0x07ee0000(000133038080), [0x00100000, 0x07fdffff], type = 1.
  memory: 0x00020000(000000131072), [0x07fe0000, 0x07ffffff], type = 2.
  memory: 0x00040000(000000262144), [0xfffc0000, 0xffffffff], type = 2.
  maxpa: 0x07fe0000
  npage = 32736
  freemem: 0x001bad80
# 注意,0x07fdffff是127MB,qemu配置的默认内存就是128MB
# type的含义
# 1表示实际的物理内存,2表示其他设备在内存地址上的映射,
# 真正能够作为内存使用的只有type是1的这种,但是进行内存管理的时候是把他们都放在一起
# 统一通过页机制来管理,只不过type是2的页帧不会被分配和释放,得做特殊处理。
#define E820_ARM            1       // address range memory                                                                                          
#define E820_ARR            2       // address range reserved

# 0x38000000=896MB
#define KMEMSIZE            0x38000000                  // the maximum amount of physical memory
# 物理页帧描述符(也是物理页帧链表节点)
 /* *
  * struct Page - Page descriptor structures. Each Page describes one
  * physical page. In kern/mm/pmm.h, you can find lots of useful functions
  * that convert Page to other data types, such as phyical address.
  * */
 struct Page {                                                                                                                                        
    int ref;                        // page frame's reference counter
    uint32_t flags;                 // array of flags that describe the status of the page frame
    unsigned int property;          // the num of free block, used in first fit pm manager
    list_entry_t page_link;         // free list link
};
# 以end为例,注意这种计算end之后首个页帧的起始地址的方法!!!
/* *
 * Rounding operations (efficient when n is a power of 2)
 * Round down to the nearest multiple of n
 * */
# 往小于a的方向找
#define ROUNDDOWN(a, n) ({                                          \                                                                                    
            size_t __a = (size_t)(a);                               \
            (typeof(a))(__a - __a % (n));                           \
        })

/* Round up to the nearest multiple of n */
# 往大于a的方向找
#define ROUNDUP(a, n) ({                                            \                                                                                    
            size_t __n = (size_t)(n);                               \
            (typeof(a))(ROUNDDOWN((size_t)(a) + __n - 1, __n));     \
        })
/* *
 * set_bit - Atomically set a bit in memory
 * @nr:     the bit to set
 * @addr:   the address to start counting from
 *
 * Note that @nr may be almost arbitrarily large; this function is not
 * restricted to acting on a single-word quantity.
 * */
static inline void
set_bit(int nr, volatile void *addr) {                                                                                                                   
    asm volatile ("btsl %1, %0" :"=m" (*(volatile long *)addr) : "Ir" (nr));
}

/* Flags describing the status of a page frame */
#define PG_reserved                 0       // if this bit=1: the Page is reserved for kernel, cannot be used in alloc/free_pages; otherwise, this bi
#define PG_property                 1       // if this bit=1: the Page is the head page of a free memory block(contains some continuous_addrress page

#define SetPageReserved(page)       set_bit(PG_reserved, &((page)->flags))
/* *
 * PADDR - takes a kernel virtual address (an address that points above KERNBASE),
 * where the machine's maximum 256MB of physical memory is mapped and returns the
 * corresponding physical address.  It panics if you pass it a non-kernel virtual address.
 * */
#define PADDR(kva) ({                                                   \                                                                                
            uintptr_t __m_kva = (uintptr_t)(kva);                       \
            if (__m_kva < KERNBASE) {                                   \
                panic("PADDR called with invalid kva %08lx", __m_kva);  \
            }                                                           \
            __m_kva - KERNBASE;                                         \
        })
# 0b1000000000000 = 4096
#define PTXSHIFT        12                      // offset of PTX in a linear address

# 右移12位相当于除以4096
# 该宏可以找到la地址在第几个物理页帧上
// page number field of address
#define PPN(la) (((uintptr_t)(la)) >> PTXSHIFT)                                                                                                          

# 该函数的作用就是找到pa地址所在物理页帧对应的page结构体(地址)
# 具体原理就是计算pa地址在第几个物理页帧,并以此为索引,在pages数组中找到对应的page结构体
# 注意,该函数的入参必须是物理地址!!!
static inline struct Page *
pa2page(uintptr_t pa) {
    # 这个判断就是在计算从pa地址算起,有多少个物理页帧,而npage表示总物理页帧个数
    if (PPN(pa) >= npage) {                                                                                                                              
        panic("pa2page called with invalid pa");
    }   
    # PPN(pa)同样也表示pa地址在第几个物理页帧
    return &pages[PPN(pa)];
}

在k/m/pmm.h中,

123 static inline void
124 set_page_ref(struct Page *page, int val) {                                                                                                           
125     page->ref = val;
126 }

在k/m/memlayout.h中,

113 #define SetPageProperty(page)       set_bit(PG_property, &((page)->flags)) 

121 /* free_area_t - maintains a doubly linked list to record free (unused) pages */
122 typedef struct {
123     list_entry_t free_list;         // the list header
124     unsigned int nr_free;           // # of free pages in this free list
125 } free_area_t;

在k/m/default_pmm.c中,

 96 free_area_t free_area; // this list is to record memory block, not pages, several pages compose one block.

 98 #define free_list (free_area.free_list)
 99 #define nr_free (free_area.nr_free)

107 static void
108 default_init_memmap(struct Page *base, size_t n) {                                                                                                   
109     assert(n > 0);         
110     struct Page *p = base; 
        # 将这些页的ref/flags/property全部设置为0
111     for (; p != base + n; p ++) {   
112         assert(PageReserved(p));        
113         p->flags = p->property = 0;     
114         set_page_ref(p, 0);
115     }
        # 只将base页的property设置为n,表示以base为首页的内存块里面一共有n页
116     base->property = n;    
        # 只将base页中flags的PG_property位置位,表明这页(页还是块?)是free的,可以被分配
        # 问题:为什么只将base页中flags的PG_property位置位而不管其他页的这一位???
117     SetPageProperty(base); 
        
        # 在free_area中记录当前总的空闲页个数
118     nr_free += n;

        # 将base作为一个链表节点加入free_area.free_list链表。
        # 注意,free_area.free_list链表里面的节点表示空闲内存块,但每个空闲内存块通过
        # 该内存块的首个物理页帧来表示,而该块具体有多少页帧记录在首个物理页帧的property中,
        # 也就是记录在free_area.free_list链表节点的property中。所以,free_area.free_list
        # 这个链表链的是空闲内存块!!!在free_area.free_list链表中找到一个空闲块,实际上找到
        # 的是该块的首个物理页帧,该物理页帧会记录该空闲块具体有多少页帧,而且这些物理页帧在物理
        # 内存上都是连续的!!!所以可通过pages数组索引的方式一口气得到整个空闲块的所有页帧!!!
119     list_add(&free_list, &(base->page_link));
120 }

303 const struct pmm_manager default_pmm_manager = {
304     .name = "default_pmm_manager",
305     .init = default_init,
306     .init_memmap = default_init_memmap,                                                                                                              
307     .alloc_pages = default_alloc_pages,
308     .free_pages = default_free_pages,
309     .nr_free_pages = default_nr_free_pages,
310     .check = default_check,
311 };  

在k/m/pmm.c中,

 34 // virtual address of physicall page array
 35 struct Page *pages;   
 
145 //init_memmap - call pmm->init_memmap to build Page struct for free memory  
146 static void
147 init_memmap(struct Page *base, size_t n) {  
        # 这里调用的是default_init_memmap
148     pmm_manager->init_memmap(base, n);
149 }

# 解析当前可用的物理内存块
189 /* pmm_init - initialize the physical memory management */
190 static void
191 page_init(void) {
        # “0x8000 + KERNBASE”,现在已经开启了页机制,使用虚拟地址,所以必须加上KERNBASE
        # 才能表示虚拟地址,否则就是物理地址。该地址下保存了目前可用的物理内存块。
        # 这些数据是用BIOS 15h中断获取的
192     struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE);
        # 记录最大的物理地址
193     uint64_t maxpa = 0;
194 
195     cprintf("e820map:\n");
196     int i;
        # 打印的还是物理地址,因为当初存的就是物理地址
197     for (i = 0; i < memmap->nr_map; i ++) {
198         uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size;
199         cprintf("  memory: %08llx, [%08llx, %08llx], type = %d.\n",
200                 memmap->map[i].size, begin, end - 1, memmap->map[i].type);
201         if (memmap->map[i].type == E820_ARM) {
                # 由此可知,只有type==1的才是实际可用的物理内存
                # type==2的都是其他设备通过总线在内存的映射
                # 内存管理只负责type==1的这部分,这里最终的maxpa就是“0x07fdffff+1”
                # 注意,maxpa的范围内其实还包括了两块type==2的内存,这两块内存不能被
                # 分配和释放,其实压根就不能参与内存管理,但是由于maxpa将其统计了进去,
                # 那么只能在后续代码中将这两块type==2的内存通过打reserved标记的方式
                # 让他们不参与内存管理过程!
202             if (maxpa < end && begin < KMEMSIZE) {
                    # maxpa里面存的仍然是物理地址,end是物理地址!
203                 maxpa = end;
204             }
205         }
206     }
207     if (maxpa > KMEMSIZE) {                                                                                                                          
208         maxpa = KMEMSIZE;
209     }
210 
        # end[]见kernel.ld,这个end是虚拟地址
211     extern char end[];
212 
        # 计算总的物理内存可以分配多少物理页帧
        # npage是个全局变量,注意,它还统计了type==2的那两块内存
213     npage = maxpa / PGSIZE;

        # <end的内存,存的是kernel的代码和数据,由bootloader加载到内存的,这部分空间不能动
        # >end的内存,可以使用了,不用担心会踩到kernel的代码或数据。
        # 这里的操作就是让pages直接定位到end之后首个完整物理页帧的起始地址
        #(由于开启了页机制,这里使用的仍然是虚拟地址,就连物理页帧的起始地址也是按照虚拟地址计算的)                         
        # 以pages指向的位置为起点,开始存储物理页帧描述符,有多少个物理页帧,该数组就有
        # 多少个元素!从而可以看出,end和储物理页帧描述符链表之间可能是有内存碎片的!!!
                                     |<--pages指向这里
        |  page frame |  page frame  |
        |*************|*****end______| 
                                  ^----------这里就是内存碎片
                                    
        # pages是一个全局的指针变量,所有的物理页帧描述符(page)组成一个数组,不是链表!
        # 这个数组描述了所有物理内存页帧!!!
214     pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);
215 
        # 将全部的物理页帧描述符的flags的第0位置一,即初始化为reserved状态,
        # 这样所有的物理页帧就不能被分配和释放。这样做的好处是使type==2的那两块内存所涉及
        # 的全部物理页帧被初始化为reserved状态,从而无法被分配和释放。后面再修改其他实际
        # 可用物理内存的标记位。
216     for (i = 0; i < npage; i ++) {
217         SetPageReserved(pages + i);
218     }
219 
        # freemem记录的是一个实际的物理地址,该物理地址是内存安置完物理页帧数组后的紧接着的那个地址,
        # 原本是虚拟地址,但是通过PADDR()宏将该虚拟地址转化为物理地址,记录在freemem变量中。
        # freemem实际上记录的是空闲内存空间的实际物理起始地址,>freemem的地址是空闲空间,
        # <freemem的地址用来放置ucore内核代码、数据、堆栈等。
        # 通过调试可知freemem是0x001bad80,在0x100000之上,属于第二个type==1的内存块
        # 也可通过分析知,ucore代码在0x100000之上,pages几乎紧挨着代码结尾,而freemem
        # 又紧挨着pages数组结尾,所以肯定在0x100000之上。
220     uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage);
221 
222     for (i = 0; i < memmap->nr_map; i ++) {
223         uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size;
224         if (memmap->map[i].type == E820_ARM) {
225             if (begin < freemem) {
226                 begin = freemem;
227             }
228             if (end > KMEMSIZE) {
229                 end = KMEMSIZE;
230             }
                
                # 只有第二个type==1的内存块会进入这里
                # 这部分的代码就是以freemem为起点,对第二个type==2的内存块所涉及的
                # 物理页帧进行初始化!因为之前已经将全部的物理页帧置为reserved状态,
                # 现在要重新找到真正可用的物理页帧并修改其状态。
231             if (begin < end) {
                    # 以begin之后(大于begin)首个物理页帧的起始地址作为新的begin
232                 begin = ROUNDUP(begin, PGSIZE);
                    # 以end  之前(小于end  )首个物理页帧的起始地址作为新的end
233                 end = ROUNDDOWN(end, PGSIZE);
                                     
|  page frame |  page frame |  page frame  |  page frame  |  page frame  |
|*************|*****________|______________|______________|_______||||||||
          freemem--^   ^_.  ^----newbegin       newend----^   ^_. ^--end
                         |_碎片                                  |_碎片
234                 if (begin < end) {
                        # 将begin-end处的一整块空闲物理内存挂到free_area.free_list链表
                        # 以备后面使用!!!
235                     init_memmap(pa2page(begin), (end - begin) / PGSIZE);
236                 }
237             }
238         }
239     }
240 }

建立虚拟页表

在k/m/memlayout.h中,

 60 /* *
 61  * Virtual page table. Entry PDX(VPT) in the PD (Page Directory) contains
 62  * a pointer to the page directory itself, thereby turning the PD into a page
 63  * table, which maps all the PTEs (Page Table Entry) containing the page mappings
 64  * for the entire virtual address space into that 4 Meg region starting at VPT.
 65  * */
 # 0xFAC00000 == 11111010110000000000000000000000b
 # PDX[VPT] == 1111101011b == 0x3EB == 1003
 # 即PD中第1003个PDE指向的正是PD本身,因此当索引到第1003个PDE时,实际指向的正是PD本身,此时
 # PD也就相当于转化成了PT!这个由PD转化成的PT,可以找到所有的PTE,因为实际上相当于仍然是通过
 # PD去找PTE。
 66 #define VPT                 0xFAC00000       

在k/m/pmm.c中,

305     // recursively insert boot_pgdir in itself
306     // to form a virtual page table at virtual address VPT
        # 让boot_pgdir[1003]项指向PD自己
307     boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W;   

boot_map_segment()建立完整的二级页表机制

该函数的作用应该就是建立(初始化)完整的二级页表,完成pte到物理页帧的一对一映射关系,但是随着内核的运行,用户态进程的运行,这种一对一的映射关系应该会被打破,可能会出现多个PTE指向同一个物理页帧的情况,也可能出现PTE没有指向任何物理页帧的情况。总之,这个函数就是建立完整的二级页表的,完成一个初始化映射的工作。

在k/m/pmm.c中,

247 //boot_map_segment - setup&enable the paging mechanism
248 // parameters
249 //  la:   linear address of this memory need to map (after x86 segment map)
250 //  size: memory size
251 //  pa:   physical address of this memory
252 //  perm: permission of this memory  
253 static void
254 boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) {                                                             
      	# 低12位要相等,页帧内的偏移要相同
255     assert(PGOFF(la) == PGOFF(pa));
      	# size向上取,n表示当前ucore预设置的物理内存条件下最多能有多少物理页帧
256     size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE;
        # la和pa向下取最近的页帧起始地址
      	# la == 0xC0000000时,ROUNDDOWN(la, PGSIZE)还是0xC0000000
257     la = ROUNDDOWN(la, PGSIZE);
258     pa = ROUNDDOWN(pa, PGSIZE);

      	# la和pa一对一逐页帧映射,一共映射n页
      	# la和pa必须按照页帧的起始地址来映射,即此时的la和pa都是各自页帧的起始地址
        # 虚拟地址的一帧的起始地址映射到物理地址的一帧的起始地址
259     for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) {
          	# 0xC0000000 >> 22 == 768,正对应在汇编代码里创建的PD的第768个PDE!!!
          	# 在get_pte()中,由于第768个PDE是有效的,指向汇编代码里建立的那个PT,而这个PT
          	# 在汇编代码里被映射了1024个物理页帧!!!
          	# 注意get_pte(pgdir, la, 1)的第三个参数是1,而la是从0xC0000000开始往后的,
          	# 这就意味着PD中,0~767个PDE仍然是无效的,由于第三个参数是1,所以从第768个PDE开始,
          	# 每个PDE都是有效的,都被分配了一个page作为一个PT并指向这个PT。因此,从768~1023共
          	# 有256个PDE,即共有256个PT,每个PT管理4MB(1024*4096)的内存,256个PT就可以管理1GB的内存。
          	# 也即到此时为止,ucore的代码是可以管理1GB的物理内存的,就看实际内存够不够了。
260         pte_t *ptep = get_pte(pgdir, la, 1);
261         assert(ptep != NULL);
          	# 建立从PTE到实际物理页帧的映射,pa作为实际物理页帧的起始地址(每帧4096B),
          	# 记录在PTE中。此时才真正建立好完整的二级页表结构,把最多256*1024个PTE都映射到了
          	# 实际的物理页帧上,而之前的汇编代码只映射了1*1024个PTE到实际的物理页帧。
          	# 注意,实际映射多少个PTE,要看n是多少,n最多就是256*1024,因为PD从0xC0000000
          	# 开始最多也就剩256个PDE了,也就是最多256*1024个PTE。
          	# 注意,这里只是增加了PTE,但是原有的PTE并没有被改动,所以不用刷新TLB。
262         *ptep = pa | PTE_P | perm;
263     }
264 }


#define KERNBASE            0xC0000000
#define KMEMSIZE            0x38000000


309     // map all physical memory to linear memory with base linear addr KERNBASE
310     // linear_addr KERNBASE ~ KERNBASE + KMEMSIZE = phy_addr 0 ~ KMEMSIZE,“=”表示“映射到”
        # 虚拟地址从KERNBASE开始和物理地址0进行一对一的映射
      	# 表面上看是把虚拟地址的KERNBASE映射到物理地址的0处,但起始具体数字得根据
      	# boot_map_segment中的处理方式来定,真正的映射是使用页帧的起始地址来实现的,
      	# 即虚拟地址的页帧起始地址映射到物理地址的页帧起始地址!!!
      	# 该函数的作用就是,将从KERNBASE 到 KERNBASE + KMEMSIZE的虚拟页帧(每帧4096B)
        # 起始地址通过二级页表结构映射到从 phy_addr 0 到 KMEMSIZE 的物理页帧起始地址
311     boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W);         

如果希望虚拟地址与物理地址相等,boot_map_segment的第二个参数从KERNBASE改为0即可。

gdt_init()建立完整的GDT

在k/m/pmm.c中,

 12 /* *
 13  * Task State Segment:
 14  *
 15  * The TSS may reside anywhere in memory. A special segment register called
 16  * the Task Register (TR) holds a segment selector that points a valid TSS
 17  * segment descriptor which resides in the GDT. Therefore, to use a TSS
 18  * the following must be done in function gdt_init:
 19  *   - create a TSS descriptor entry in GDT
 20  *   - add enough information to the TSS in memory as needed
 21  *   - load the TR register with a segment selector for that segment
 22  *
 23  * There are several fileds in TSS for specifying the new stack pointer when a
 24  * privilege level change happens. But only the fields SS0 and ESP0 are useful
 25  * in our os kernel.
 26  *
 27  * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0
 28  * contains the new ESP value for CPL = 0. When an interrupt happens in protected
 29  * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value
 30  * into SS and ESP respectively.
 31  * */
 # TR寄存器保存了一个段选择子,该段选择子指向GDT当中的TSS段的段描述符。
 # 整个TSS的信息都用这一个结构体ts来存储,所谓TSS段,其实就是这么个结构体
 32 static struct taskstate ts = {0};                                                                                                                    

 77 static struct segdesc gdt[] = {
 78     SEG_NULL,
 79     [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL),                                                                                   
 80     [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL),
 81     [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER),
 82     [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER),
 83     [SEG_TSS]   = SEG_NULL,
 84 };
 85 
 86 static struct pseudodesc gdt_pd = {
 87     sizeof(gdt) - 1, (uintptr_t)gdt
 88 };

110 /* *
111  * load_esp0 - change the ESP0 in default task state segment,
112  * so that we can use different kernel stack when we trap frame
113  * user to kernel.         
114  * */
115 void
116 load_esp0(uintptr_t esp0) {                                                                                                                          
117     ts.ts_esp0 = esp0;     
118 } 

120 /* gdt_init - initialize the default GDT and TSS */
121 static void
122 gdt_init(void) {                                                                                                                                     
123     // set boot kernel stack and default SS0
      	# 将内核栈地址保存在TSS段(用结构体ts表示)的esp0中
124     load_esp0((uintptr_t)bootstacktop);
125     ts.ts_ss0 = KERNEL_DS; # ss0记录数据段 
126   
127     // initialize the TSS filed of the gdt
      	# 注意,汇编代码里的gdt没有使用“.global”修饰,意味着c代码是看不到汇编代码的gdt的,
      	# 所以这里的gdt是c代码重新定义的!!!gdt是个结构体数组。
      	# 这里是设置该描述符,使其可以指向TSS段,而TSS段放在内存
      	# 任何一个可能的位置(这个不重要)。
128     gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL);
129     
130     // reload all segment registers 
      	# 重新将GDT加载到寄存器,同时这个函数里面还会设置其他各个段寄存器,并且修改CS寄存器!
131     lgdt(&gdt_pd);         
132 
133     // load the TSS      
      	# 将TSS段的段选择子加载到寄存器
134     ltr(GD_TSS);
135 } 


316     // Since we are using bootloader's GDT,
317     // we should reload gdt (second time, the last time) to get user segments and the TSS
318     // map virtual_addr 0 ~ 4G = linear_addr 0 ~ 4G
319     // then set kernel stack (ss:esp) in TSS, setup TSS in gdt, load TSS
      	# bootloader里面已经设置了一次GDT,以完成0~4G的一对一对等映射,这里重新加载一次GDT,
      	# 也是最后一次加载GDT,主要是设置TSS,也是重新设置GDT。
320     gdt_init(); 

get_pte(pde_t *pgdir, uintptr_t la, bool create)

段机制和页机制同时使能后,有如下关系:

逻辑地址-->段机制-->线性地址-->页机制-->物理地址

由于ucore弱化了段机制,因此实际上在ucore中逻辑地址==线性地址-->页机制-->物理地址

在k/m/pmm.h中,

 77 static inline ppn_t
 78 page2ppn(struct Page *page) {
 79     return page - pages;
 80 }
 81 
 82 static inline uintptr_t
 83 page2pa(struct Page *page) {                                                                                                                         
 84     return page2ppn(page) << PGSHIFT;
 85 }

page2pa的功能是获取page所管理的那个物理页帧的起始地址。
原理为:
(page - pages) == 得到该page对应第几个物理页帧,即page相对于pages在数组中的索引(偏移量)
(page - pages)*4096 == 得到该page所管理的物理页帧的起始地址
左移12位就是乘以4096

在k/m/pmm.c中,

 39 // virtual address of boot-time page directory
 40 extern pde_t __boot_pgdir;
 # boot_pgdir指向页目录表的虚拟地址
 41 pde_t *boot_pgdir = &__boot_pgdir;                                                                                                                   

480     assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL);


327 //get_pte - get pte and return the kernel virtual address of this pte for la
328 //        - if the PT contians this pte didn't exist, alloc a page for PT
329 // parameter:
330 //  pgdir:  the kernel virtual base address of PDT
331 //  la:     the linear address need to map,由于ucore弱化了段机制,因此la就是虚拟地址
332 //  create: a logical value to decide if alloc a page for PT
333 // return vaule: the kernel virtual address of this pte
334 pte_t *
335 get_pte(pde_t *pgdir, uintptr_t la, bool create) {                                                                                                   
        # 如果没有page table,就分配一页从而就在这里建立page table
... }

该函数建立的是页目录表到页表的映射关系,而不是从页表到物理页帧的映射关系。当发现pde为空(0)时即表示该pde没有对应的page table,因此需要根据参数create去判断是否应该获取一页从而创建一个page table。新建的page table里面的pte全部都是0,尽管此时通过get_pte可以获得pte,但是对应的pte其实是0,即没有对应的物理页帧存在,这样应该会引发缺页异常,这个时候才应该用别的函数去建立page table到物理页帧的映射。

page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep)

boot_map_setment()会将pte和所有物理页帧的一对一映射关系建立好,但是page_remove_pte()会断开pte到物理页帧的映射关系。

在k/m/pmm.c中,

456 //page_remove_pte - free an Page sturct which is related linear address la                                                                           
457 //                - and clean(invalidate) pte which is related linear address la
458 //note: PT is changed, so the TLB need to be invalidate 
459 static inline void         
460 page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) {
461     /* LAB2 EXERCISE 3: YOUR CODE   
462      *
463      * Please check if ptep is valid, and tlb must be manually updated if mapping is updated
464      *
465      * Maybe you want help comment, BELOW comments can help you finish the code
466      *
467      * Some Useful MACROs and DEFINEs, you can use them in below implementation.
468      * MACROs or Functions:
469      *   struct Page *page pte2page(*ptep): get the according page from the value of a ptep
470      *   free_page : free a page
471      *   page_ref_dec(page) : decrease page->ref. NOTICE: if page->ref == 0 , then this page should be free.
472      *   tlb_invalidate(pde_t *pgdir, uintptr_t la) : Invalidate a TLB entry, but only if the page tables being
473      *                        edited are the ones currently in use by the processor.
474      * DEFINEs:
475      *   PTE_P           0x001                   // page table/directory entry flags bit : Present
476      */
477 #if 0
478     if (0) {                      //(1) check if this page table entry is present
479         struct Page *page = NULL; //(2) find corresponding page to pte
480                                   //(3) decrease page reference
481                                   //(4) and free this page when page reference reachs 0
482                                   //(5) clear second page table entry
483                                   //(6) flush tlb
484     }
485 #endif
486     struct Page *tmp_page = NULL;   
487 
488     //(1) check if this page table entry is present
489     if (!(*ptep & PTE_P)) {
490         goto ret_label;    
491     }
492   
493     //(2) find corresponding page to pte
494     tmp_page = pte2page(*ptep);
495     
496     //(3) decrease page reference   
497     page_ref_dec(tmp_page);
498   
499     //(4) and free this page when page reference reachs 0
500     if (0 == tmp_page->ref) {
501         free_page(tmp_page);
502     }
503 
504     //(5) clear second page table entry
      	# 这就是remove_pte的核心概念,核心要做的事情!!!
      	# 就是断开pte和物理页帧的映射。
505     *ptep = 0;             
506 
507     //(6) flush tlb
        # 刷新快表
        # 只要改动了PTE,就得刷新快表
508     tlb_invalidate(pgdir, la);
509 
510 ret_label:
511     return;
512 }

几个标记的含义

PTE_U:位3,表示用户态的软件可以读取对应地址的物理内存页内容

PTE_W:位2,表示物理内存页内容可写

PTE_P:位1,表示物理内存页存在

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值