diff --git a/labcodes/lab3/run.sh b/labcodes/lab3/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..a51e918c6dede5c77c640b0a3261e167779d27cf --- /dev/null +++ b/labcodes/lab3/run.sh @@ -0,0 +1 @@ +qemu-system-i386 -no-reboot -serial mon:stdio -hda bin/ucore.img -drive file=bin/swap.img,media=disk,cache=writeback -nographic -s -S diff --git a/labcodes/lab3/tools/gdbinit b/labcodes/lab3/tools/gdbinit index 600f648570ee7d3686afa117629c316eda2f8f25..6a429c1b2692afb6fd94ab0051171026b0b26350 100644 --- a/labcodes/lab3/tools/gdbinit +++ b/labcodes/lab3/tools/gdbinit @@ -1,5 +1,5 @@ file bin/kernel target remote :1234 b kern_init -break vmm_init -continue \ No newline at end of file +break swap_init +continue diff --git a/labcodes/lab4/boot/bootasm.S b/labcodes/lab4/boot/bootasm.S index 6f769b719e80d97ffd29240de2f1a816ff8f47b5..56852b2acc26f961006816e49489d38699b1b712 100644 --- a/labcodes/lab4/boot/bootasm.S +++ b/labcodes/lab4/boot/bootasm.S @@ -4,44 +4,49 @@ # The BIOS loads this code from the first sector of the hard disk into # memory at physical address 0x7c00 and starts executing in real mode # with %cs=0 %ip=7c00. +# 启动CPU:切换到32位保护模式,跳转到C语言。 +# BIOS将这段代码从硬盘的第一个扇区加载到物理地址0x7c00的内存中, +# 并在实模式下执行,%cs=0,%ip=7c00 -.set PROT_MODE_CSEG, 0x8 # kernel code segment selector -.set PROT_MODE_DSEG, 0x10 # kernel data segment selector -.set CR0_PE_ON, 0x1 # protected mode enable flag + +.set PROT_MODE_CSEG, 0x8 # kernel code segment selector 定义PROT_MODE_CSEG 的常量,其值为 0x8 +.set PROT_MODE_DSEG, 0x10 # kernel data segment selector 定义PROT_MODE_DSEG 的常量,其值为 0x10 +.set CR0_PE_ON, 0x1 # protected mode enable flag 受保护模式启用标志 定义CR0_PE_ON 的常量,其值为 0x1 .set SMAP, 0x534d4150 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader -.globl start -start: -.code16 # Assemble for 16-bit mode - cli # Disable interrupts - cld # String operations increment +# 起始地址应该是 0:7c00,在实模式下,这是运行的引导加载程序的起始地址。 +.globl start # 表示将 start 标签声明为全局可见的入口点。 +start: # 定义 start 标签 +.code16 # Assemble for 16-bit mode 指示编译器将后续代码编译为 16 位模式 + cli # Disable interrupts 禁用中断 + cld # String operations increment 清除方向标志(DF),使字符串操作在内存中从低地址到高地址进行增量操作。 # Set up the important data segment registers (DS, ES, SS). - xorw %ax, %ax # Segment number zero - movw %ax, %ds # -> Data Segment - movw %ax, %es # -> Extra Segment - movw %ax, %ss # -> Stack Segment + xorw %ax, %ax # Segment number zero 将寄存器 AX 清零,准备将其作为段寄存器的值。 + movw %ax, %ds # -> Data Segment 将 AX(值为 0)存入数据段寄存器 DS,初始化数据段为 0。 + movw %ax, %es # -> Extra Segment 将 AX(仍然是 0)存入附加段寄存器 ES,初始化附加段为 0。 + movw %ax, %ss # -> Stack Segment 将 AX(仍然是 0)存入栈段寄存器 SS,初始化栈段为 0。 # Enable A20: # For backwards compatibility with the earliest PCs, physical # address line 20 is tied low, so that addresses higher than # 1MB wrap around to zero by default. This code undoes this. seta20.1: - inb $0x64, %al # Wait for not busy(8042 input buffer empty). + inb $0x64, %al # Wait for not busy(8042 input buffer empty). 设置标签 seta20.1,从 I/O 端口 0x64 读取数据到寄存器 AL,等待8042控制器输入缓冲区为空。 testb $0x2, %al - jnz seta20.1 + jnz seta20.1 # 测试 AL 中的第 2 位,如果不为零,则跳回 seta20.1,表示8042仍在忙。 - movb $0xd1, %al # 0xd1 -> port 0x64 - outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port + movb $0xd1, %al # 0xd1 -> port 0x64 将值 0xd1 存入 AL,这个值将被写入端口 0x64。 + outb %al, $0x64 # 0xd1 means: write data to 8042's P2 port 将 AL 的值(即 0xd1)写入端口 0x64 seta20.2: - inb $0x64, %al # Wait for not busy(8042 input buffer empty). + inb $0x64, %al # Wait for not busy(8042 input buffer empty). 设置标签 seta20.2,同样从端口 0x64 读取数据到 AL,等待8042输入缓冲区为空。 testb $0x2, %al - jnz seta20.2 + jnz seta20.2 # 再次测试 AL 中的第 2 位,如果不为零,则跳回 seta20.2。 - movb $0xdf, %al # 0xdf -> port 0x60 - outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1 + movb $0xdf, %al # 0xdf -> port 0x60 将值 0xdf 存入 AL,此值将被写入端口 0x60。 + outb %al, $0x60 # 0xdf = 11011111, means set P2's A20 bit(the 1 bit) to 1 将 AL 的值(即 0xdf)写入端口 0x60,这将设置8042控制器的 A20 地址线为高(1),允许访问超过1MB的内存。 probe_memory: movl $0, 0x8000 @@ -66,24 +71,24 @@ finish_probe: # and segment translation that makes virtual addresses # identical to physical addresses, so that the # effective memory map does not change during the switch. - lgdt gdtdesc - movl %cr0, %eax - orl $CR0_PE_ON, %eax - movl %eax, %cr0 + lgdt gdtdesc #加载全局描述符表寄存器(GDTR)到 gdtdesc,以便设置保护模式的段描述符。 + movl %cr0, %eax #将控制寄存器 CR0 的值加载到寄存器 EAX 中 + orl $CR0_PE_ON, %eax #将 CR0 中的保护模式开启标志(CR0_PE_ON)与 EAX 相或,以便设置 CR0 的保护模式位。 + movl %eax, %cr0 #将修改后的 EAX 的值存回 CR0,使处理器进入保护模式。 # Jump to next instruction, but in 32-bit code segment. # Switches processor into 32-bit mode. - ljmp $PROT_MODE_CSEG, $protcseg + ljmp $PROT_MODE_CSEG, $protcseg #执行远跳转到保护模式的代码段 PROT_MODE_CSEG 和偏移地址 protcseg,实现模式切换并开始执行32位代码。 .code32 # Assemble for 32-bit mode -protcseg: +protcseg: #定义标签 protcseg,表示保护模式下的代码段开始。 # Set up the protected-mode data segment registers - movw $PROT_MODE_DSEG, %ax # Our data segment selector - movw %ax, %ds # -> DS: Data Segment - movw %ax, %es # -> ES: Extra Segment - movw %ax, %fs # -> FS - movw %ax, %gs # -> GS - movw %ax, %ss # -> SS: Stack Segment + movw $PROT_MODE_DSEG, %ax # Our data segment selector 将数据段选择子 PROT_MODE_DSEG 的值加载到寄存器 AX 中。 + movw %ax, %ds # -> DS: Data Segment 将 AX 中的数据段选择子存入数据段寄存器 DS,设置数据段 + movw %ax, %es # -> ES: Extra Segment 将 AX 中的数据段选择子存入附加段寄存器 ES,设置附加段 + movw %ax, %fs # -> FS 将 AX 中的数据段选择子存入段寄存器 FS,设置 FS 段。 + movw %ax, %gs # -> GS 将 AX 中的数据段选择子存入段寄存器 GS + movw %ax, %ss # -> SS: Stack Segment 将 AX 中的数据段选择子存入栈段寄存器 SS,设置栈段。 # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00) movl $0x0, %ebp diff --git a/labcodes/lab4/boot/bootmain.c b/labcodes/lab4/boot/bootmain.c index 4b55eb7e7d43c3eb7f761e60bca0e4d8feaf89bc..20b4b3473961054ba3dc22b3fee9d0f5d9b9018f 100644 --- a/labcodes/lab4/boot/bootmain.c +++ b/labcodes/lab4/boot/bootmain.c @@ -34,6 +34,8 @@ #define ELFHDR ((struct elfhdr *)0x10000) // scratch space /* waitdisk - wait for disk ready */ +//waitdisk() 函数用于等待磁盘准备就绪。它通过读取端口 0x1F7 来检查状态, +//只有在状态位符合条件(即标志位为 0x40)时,表示磁盘准备好了,函数才返回。 static void waitdisk(void) { while ((inb(0x1F7) & 0xC0) != 0x40) @@ -41,22 +43,24 @@ waitdisk(void) { } /* readsect - read a single sector at @secno into @dst */ +//readsect() 函数的声明,接受两个参数:目标指针 dst 和扇区编号 secno。 static void readsect(void *dst, uint32_t secno) { - // wait for disk to be ready + // wait for disk to be ready 调用 waitdisk() 函数,确保磁盘准备好进行读取。 waitdisk(); - outb(0x1F2, 1); // count = 1 + outb(0x1F2, 1); // count = 1 向端口 0x1F2 写入值 1,表示要读取的扇区数量为1。 outb(0x1F3, secno & 0xFF); outb(0x1F4, (secno >> 8) & 0xFF); outb(0x1F5, (secno >> 16) & 0xFF); outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0); - outb(0x1F7, 0x20); // cmd 0x20 - read sectors - - // wait for disk to be ready + outb(0x1F7, 0x20); // cmd 0x20 - read sectors //写入命令 0x20 到端口 0x1F7,指示磁盘控制器执行读取扇区操作。 + //这几行将扇区号 secno 分成字节,并写入对应的端口,以指定要读取的扇区。端口 0x1F6 还设置了设备号。 + // wait for disk to be ready 再次调用 waitdisk(),以确保读取操作完成,磁盘再次准备好。 waitdisk(); - // read a sector + // read a sector从端口 0x1F0 读取数据到目标地址 dst,每次读取 SECTSIZE / 4(即512字节/4 = 128次)。 + // insl 指令用于从端口读取数据到内存。 insl(0x1F0, dst, SECTSIZE / 4); } @@ -64,53 +68,64 @@ readsect(void *dst, uint32_t secno) { * readseg - read @count bytes at @offset from kernel into virtual address @va, * might copy more than asked. * */ +//readseg() 函数定义,接受三个参数:虚拟地址 va、字节数 count 和内核中的偏移 offset。 static void readseg(uintptr_t va, uint32_t count, uint32_t offset) { - uintptr_t end_va = va + count; + uintptr_t end_va = va + count; //计算结束虚拟地址 end_va - // round down to sector boundary + // round down to sector boundary 将虚拟地址 va 调整到扇区边界,确保从正确的地址读取数据。 va -= offset % SECTSIZE; // translate from bytes to sectors; kernel starts at sector 1 + //将偏移量 offset 转换为扇区号 secno,因为内核镜像从第二个扇区(编号为1)开始存储。 uint32_t secno = (offset / SECTSIZE) + 1; // If this is too slow, we could read lots of sectors at a time. // We'd write more to memory than asked, but it doesn't matter -- // we load in increasing order. + //循环读取数据,直到读取到结束虚拟地址 end_va。 + //每次调用 readsect() 读取一个扇区的数据到指定的虚拟地址。 for (; va < end_va; va += SECTSIZE, secno ++) { readsect((void *)va, secno); } } /* bootmain - the entry of bootloader */ +//bootmain() 函数是引导加载程序的入口点。 void bootmain(void) { // read the 1st page off disk + //从磁盘读取前8个扇区(4096字节)到 ELFHDR 指向的内存地址,准备解析ELF头。 readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0); // is this a valid ELF? + //检查读取的ELF头部的魔数(magic number)是否有效。如果无效,跳转到 bad 标签处理。 if (ELFHDR->e_magic != ELF_MAGIC) { goto bad; } - + //定义指针 ph 和 eph,分别指向程序头和程序头的结束位置。 struct proghdr *ph, *eph; // load each program segment (ignores ph flags) + //将程序头的起始地址设置为 ELFHDR 中的 e_phoff 偏移,并计算结束位置 eph。 ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); eph = ph + ELFHDR->e_phnum; + //循环读取每个程序段的数据,调用 readseg() 将内核程序段加载到内存指定地址。 for (; ph < eph; ph ++) { readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset); } // call the entry point from the ELF header // note: does not return + //从ELF头部获取内核的入口点,调用该入口函数,注意此调用不会返回。 ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))(); - +//如果在之前的步骤中发现了错误,将会执行到 bad 标签处。 +//这里发送了一些特定的端口命令,通常用于向调试端口或显示错误信息。 bad: outw(0x8A00, 0x8A00); outw(0x8A00, 0x8E00); /* do nothing */ - while (1); + while (1);//进入无限循环,表示引导加载程序无法继续执行。这通常是为了指示错误状态,程序将不再继续。 } diff --git a/labcodes/lab4/kern/init/init.c b/labcodes/lab4/kern/init/init.c index ef984128de31d9d3a1ea33d9df74e346b8453c2f..a626ef1657a7ed06772b1861e99d61d0282da338 100644 --- a/labcodes/lab4/kern/init/init.c +++ b/labcodes/lab4/kern/init/init.c @@ -18,32 +18,32 @@ int kern_init(void) __attribute__((noreturn)); void grade_backtrace(void); static void lab1_switch_test(void); -int + + int kern_init(void) { - extern char edata[], end[]; - memset(edata, 0, end - edata); + extern char edata[], end[]; //声明外部变量 edata 和 end + memset(edata, 0, end - edata); // 将数据段清零 - cons_init(); // init the console + cons_init(); // init the console 初始化控制台 const char *message = "(THU.CST) os is loading ..."; - cprintf("%s\n\n", message); + cprintf("%s\n\n", message);// 将消息输出到控制台 - print_kerninfo(); + print_kerninfo();// 输出内核信息的函数 - grade_backtrace(); + grade_backtrace(); //调用回溯函数,通常用于调试,显示函数调用栈。 - pmm_init(); // init physical memory management + pmm_init(); // init physical memory management初始化物理内存管理 - pic_init(); // init interrupt controller - idt_init(); // init interrupt descriptor table + pic_init(); // init interrupt controller初始化可编程中断控制器 + idt_init(); // init interrupt descriptor table初始化中断描述符表 - vmm_init(); // init virtual memory management - proc_init(); // init process table - - ide_init(); // init ide devices - swap_init(); // init swap + vmm_init(); // init virtual memory management 初始化虚拟内存管理 + + ide_init(); // init ide devices初始化IDE设备 + swap_init(); // init swap 初始化交换分区 - clock_init(); // init clock interrupt + clock_init(); // init clock interrupt 初始化时钟中断 intr_enable(); // enable irq interrupt //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test() @@ -53,54 +53,73 @@ kern_init(void) { cpu_idle(); // run idle process } +//不进行内联的回溯函数,调用 mon_backtrace 显示当前的调用栈。 void __attribute__((noinline)) grade_backtrace2(int arg0, int arg1, int arg2, int arg3) { mon_backtrace(0, NULL, NULL); } - +//不进行内联的回溯函数,传递参数到 grade_backtrace2 void __attribute__((noinline)) grade_backtrace1(int arg0, int arg1) { grade_backtrace2(arg0, (int)&arg0, arg1, (int)&arg1); } - +//不进行内联的回溯函数,传递参数到 grade_backtrace1 void __attribute__((noinline)) grade_backtrace0(int arg0, int arg1, int arg2) { grade_backtrace1(arg0, arg2); } - +//触发回溯的起始点,传递初始化函数地址。 void grade_backtrace(void) { grade_backtrace0(0, (int)kern_init, 0xffff0000); } - +//打印当前的段寄存器状态。 static void lab1_print_cur_status(void) { static int round = 0; uint16_t reg1, reg2, reg3, reg4; + //嵌入汇编代码,确保编译器不优化这些代码。 asm volatile ( - "mov %%cs, %0;" + "mov %%cs, %0;"// 将当前代码段寄存器的值移动到 reg1 "mov %%ds, %1;" "mov %%es, %2;" "mov %%ss, %3;" : "=m"(reg1), "=m"(reg2), "=m"(reg3), "=m"(reg4)); - cprintf("%d: @ring %d\n", round, reg1 & 3); + cprintf("%d: @ring %d\n", round, reg1 & 3);//打印当前的 round、权限级(ring)和各段寄存器的值。 cprintf("%d: cs = %x\n", round, reg1); cprintf("%d: ds = %x\n", round, reg2); cprintf("%d: es = %x\n", round, reg3); cprintf("%d: ss = %x\n", round, reg4); - round ++; + round ++;//将 round 增加1,以便每次调用时记录状态。 } static void lab1_switch_to_user(void) { + // 从内核模式切换到用户模式 //LAB1 CHALLENGE 1 : TODO + asm volatile ( + "sub $0x8, %%esp \n"//从堆栈指针中减去 8 字节 + "int %0 \n"//通过触发一个中断,将控制权转移到内核,切换到用户模式。 + "movl %%ebp, %%esp"// 将基指针(EBP)值移动到堆栈指针(ESP),恢复堆栈指针。 + : + : "i"(T_SWITCH_TOU)//T_SWITCH_TOU是一个常量,表示切换到用户态的中断号。传入常量 T_SWITCH_TOU + ); } static void lab1_switch_to_kernel(void) { + // 从用户模式切换到内核模式 //LAB1 CHALLENGE 1 : TODO + asm volatile ( + "int %0 \n"// 同样触发中断,这里用的是 T_SWITCH_TOK,从用户态切换回内核态。 + "movl %%ebp, %%esp \n"//恢复堆栈指针 + : + : "i"(T_SWITCH_TOK)//传入常量 T_SWITCH_TOU + ); } +//测试用户模式和内核模式切换。 +//调用 lab1_print_cur_status 打印当前状态,进行模式切换,然后再次打印状态。 static void lab1_switch_test(void) { lab1_print_cur_status(); diff --git a/labcodes/lab4/kern/mm/default_pmm.c b/labcodes/lab4/kern/mm/default_pmm.c index b388bca0c9b088a485cb8c162b83418bd826269e..30947df57c0232376a0924ef5b30aaf8b99b99fa 100644 --- a/labcodes/lab4/kern/mm/default_pmm.c +++ b/labcodes/lab4/kern/mm/default_pmm.c @@ -9,30 +9,47 @@ * the request. If the chosen block is significantly larger than requested, it * is usually splitted, and the remainder will be added into the list as * another free block. + * 在首次适应算法中,分配器维护一个空闲块列表,(称为空闲列表)。一旦收到内存的分配请求, + * 它会沿着列表扫描第一个足够大的块以满足请求。如果选中的块比请求的块大得多,通常会将其拆分, + * 剩余部分将作为另一个空闲块添加到列表中。 * Please refer to Page 196~198, Section 8.2 of Yan Wei Min's Chinese book * "Data Structure -- C programming language". */ // LAB2 EXERCISE 1: YOUR CODE // you should rewrite functions: `default_init`, `default_init_memmap`, // `default_alloc_pages`, `default_free_pages`. +// `default_alloc_pages`, `default_free_pages`.你需要重写函数: /* * Details of FFMA * (1) Preparation: * In order to implement the First-Fit Memory Allocation (FFMA), we should * manage the free memory blocks using a list. The struct `free_area_t` is used * for the management of free memory blocks. + * * 为了实现首次适应内存分配(FFMA),我们应使用一个列表来管理空闲内存块。 + * 结构体 `free_area_t` 用于管理空闲内存块。 + * * First, you should get familiar with the struct `list` in list.h. Struct * `list` is a simple doubly linked list implementation. You should know how to * USE `list_init`, `list_add`(`list_add_after`), `list_add_before`, `list_del`, * `list_next`, `list_prev`. + * 首先,你需要熟悉 `list.h` 中的 `list` 结构。结构体 `list` 是一个简单的双向链表实现。 + * 你需要知道如何使用 `list_init`、`list_add`(`list_add_after`)、`list_add_before`、`list_del`、 + * `list_next`, `list_prev`. + * * There's a tricky method that is to transform a general `list` struct to a * special struct (such as struct `page`), using the following MACROs: `le2page` * (in memlayout.h), (and in future labs: `le2vma` (in vmm.h), `le2proc` (in * proc.h), etc). + * 有一种巧妙的方法是将通用的 `list` 结构转换为特定的结构(例如 `struct page`), + * 使用以下宏:`le2page`(在 memlayout.h 中),(在未来的实验中:`le2vma`(在 vmm.h 中)、`le2proc`(在 proc.h 中)等)。 + * * (2) `default_init`: * You can reuse the demo `default_init` function to initialize the `free_list` * and set `nr_free` to 0. `free_list` is used to record the free memory blocks. * `nr_free` is the total number of the free memory blocks. + * 你可以重用示例 `default_init` 函数来初始化 `free_list`,并将 `nr_free` 设置为 0。 + * `free_list` 用于记录空闲内存块。`nr_free` 是空闲内存块的总数。 + * * (3) `default_init_memmap`: * CALL GRAPH: `kern_init` --> `pmm_init` --> `page_init` --> `init_memmap` --> * `pmm_manager` --> `init_memmap`. @@ -40,21 +57,40 @@ * `page_number`). In order to initialize a free block, firstly, you should * initialize each page (defined in memlayout.h) in this free block. This * procedure includes: + * 此函数用于初始化一个空闲块(带参数 `addr_base`、`page_number`)。 + * 为了初始化一个空闲块,首先,你需要初始化此空闲块中的每一页(在 memlayout.h 中定义)。该过程包括: + * * - Setting the bit `PG_property` of `p->flags`, which means this page is * valid. P.S. In function `pmm_init` (in pmm.c), the bit `PG_reserved` of * `p->flags` is already set. + * 设置 `p->flags` 的 `PG_property` 位,表示该页有效。注:在 `pmm_init` 函数中(在 pmm.c 中), + * `p->flags` 的 `PG_reserved` 位已经设置。 + * * - If this page is free and is not the first page of a free block, * `p->property` should be set to 0. + * 如果该页是空闲的且不是空闲块的第一页,则应将 `p->property` 设置为 0。 + * * - If this page is free and is the first page of a free block, `p->property` * should be set to be the total number of pages in the block. + * 如果该页是空闲的并且是空闲块的第一页,则应将 `p->property` 设置为该块中的总页数。 + * * - `p->ref` should be 0, because now `p` is free and has no reference. * After that, We can use `p->page_link` to link this page into `free_list`. + * `p->ref` 应设置为 0,因为现在 `p` 是空闲的,没有引用。 + * 之后,我们可以使用 `p->page_link` 将此页链接到 `free_list` 中。 + * * (e.g.: `list_add_before(&free_list, &(p->page_link));` ) * Finally, we should update the sum of the free memory blocks: `nr_free += n`. + * (例如:`list_add_before(&free_list, &(p->page_link));`) + * Finally, we should update the sum of the free memory blocks: `nr_free += n`. + * 最后,我们应更新空闲内存块的总数:`nr_free += n`。 + * * (4) `default_alloc_pages`: * Search for the first free block (block size >= n) in the free list and reszie * the block found, returning the address of this block as the address required by * `malloc`. + * 在空闲列表中搜索第一个空闲块(块大小 >= n),并调整找到的块的大小,返回该块的地址作为 `malloc` 所需的地址。 + * * (4.1) * So you should search the free list like this: * list_entry_t le = &free_list; @@ -63,6 +99,9 @@ * (4.1.1) * In the while loop, get the struct `page` and check if `p->property` * (recording the num of free pages in this block) >= n. + * 在 while 循环中,获取 `struct page` 并检查 `p->property` + * (记录该块中空闲页的数量)是否 >= n。 + * * struct Page *p = le2page(le, page_link); * if(p->property >= n){ ... * (4.1.2) @@ -70,6 +109,8 @@ * >= n, whose first `n` pages can be malloced. Some flag bits of this page * should be set as the following: `PG_reserved = 1`, `PG_property = 0`. * Then, unlink the pages from `free_list`. + * 如果我们找到这个 `p`,这意味着我们找到了一个大小>= n 的空闲块,其前 `n` 页可以分配。此页面的一些标志位应设置如下: + * `PG_reserved = 1`,`PG_property = 0`。然后,从 `free_list` 中移除这些页。 * (4.1.2.1) * If `p->property > n`, we should re-calculate number of the rest * pages of this free block. (e.g.: `le2page(le,page_link))->property @@ -98,137 +139,170 @@ free_area_t free_area; #define free_list (free_area.free_list) #define nr_free (free_area.nr_free) +//free_list` 用于记录空闲内存块,nr_free` 是空闲内存块的总数。 +//用default_init函数来初始化 `free_list`,并将 `nr_free` 设置为 0。 static void default_init(void) { list_init(&free_list); nr_free = 0; } +//用于初始化一段连续的物理页,并将它们加入到空闲内存管理系统中. +//struct Page *base:指向要初始化的页块的起始地址。size_t n:要初始化的页的数量。 static void default_init_memmap(struct Page *base, size_t n) { - assert(n > 0); - struct Page *p = base; + assert(n > 0);// 确保请求的页数大于零 + struct Page *p = base;// 指向当前初始化的页 + // 遍历每一页,设置其状态 for (; p != base + n; p ++) { - assert(PageReserved(p)); - p->flags = p->property = 0; - set_page_ref(p, 0); + assert(PageReserved(p));//检查每个页是否被标记为“保留”。若没有被保留,函数将抛出错误。 + p->flags = p->property = 0;//将页的 flags 和 property 字段设置为 0,表示该页未分配、未使用。 + set_page_ref(p, 0);//将页的引用计数设置为 0,表明没有任何引用指向此页。 } + // 设置第一个页的 property 为块的总数 base->property = n; - SetPageProperty(base); - nr_free += n; - list_add(&free_list, &(base->page_link)); + SetPageProperty(base);// 设置当前页的有效标志 + nr_free += n;// 更新空闲页计数 + list_add_before(&free_list, &(base->page_link));// 将该块添加到空闲列表中 } +//用于分配指定数量的连续物理页。该函数实现了首次适应内存分配算法。 static struct Page * default_alloc_pages(size_t n) { - assert(n > 0); - if (n > nr_free) { + assert(n > 0);// 确保请求的页数大于零 + if (n > nr_free) {// 检查请求的页数是否超过空闲页数 return NULL; } - struct Page *page = NULL; - list_entry_t *le = &free_list; + struct Page *page = NULL;// 初始化分配的页指针 + list_entry_t *le = &free_list;// 初始化链表迭代器 + // 遍历空闲列表,寻找第一个满足条件的块 while ((le = list_next(le)) != &free_list) { - struct Page *p = le2page(le, page_link); - if (p->property >= n) { - page = p; - break; + struct Page *p = le2page(le, page_link);// 将链表节点转换为 Page 结构体 + if (p->property >= n) {// 检查当前块的页数是否满足请求 + page = p;// 找到合适的块 + break;// 退出循环 } } - if (page != NULL) { - list_del(&(page->page_link)); + if (page != NULL) {// 如果找到合适的块 + //list_del(&(page->page_link));// 从空闲列表中删除该块 if (page->property > n) { - struct Page *p = page + n; - p->property = page->property - n; - list_add(&free_list, &(p->page_link)); + struct Page *p = page + n;// 指向剩余的页 + p->property = page->property - n;// 更新剩余块的页数 + SetPageProperty(p); + list_add_after(&(page->page_link), &(p->page_link));// 将剩余块添加回空闲列表 } - nr_free -= n; - ClearPageProperty(page); + list_del(&(page->page_link)); + nr_free -= n;// 减少空闲页的计数 + ClearPageProperty(page);// 清除已分配页的属性 } - return page; + return page;// 返回分配的页块 } static void default_free_pages(struct Page *base, size_t n) { - assert(n > 0); + assert(n > 0);// 确保请求释放的页数大于零 struct Page *p = base; + // 遍历释放的页,检查状态并重置 for (; p != base + n; p ++) { - assert(!PageReserved(p) && !PageProperty(p)); - p->flags = 0; - set_page_ref(p, 0); + assert(!PageReserved(p) && !PageProperty(p));// 确保页没有被保留并且没有属性 + p->flags = 0;// 清除 flags 字段 + set_page_ref(p, 0);// 清除引用计数 } + // 设置基页的属性为释放的页数 base->property = n; - SetPageProperty(base); + SetPageProperty(base);// 设置页的有效标志 + // 遍历空闲列表,检查是否需要合并 list_entry_t *le = list_next(&free_list); while (le != &free_list) { p = le2page(le, page_link); le = list_next(le); + // 如果当前页块与释放的页块相邻,合并 if (base + base->property == p) { - base->property += p->property; - ClearPageProperty(p); - list_del(&(p->page_link)); + base->property += p->property;// 合并当前页块 + ClearPageProperty(p);// 清除合并页的属性 + list_del(&(p->page_link));// 从空闲列表中删除合并页 } else if (p + p->property == base) { - p->property += base->property; - ClearPageProperty(base); - base = p; - list_del(&(p->page_link)); + p->property += base->property;// 合并前一个页块 + ClearPageProperty(base);// 清除当前页的属性 + base = p;// 更新 base 指针 + list_del(&(p->page_link));// 从空闲列表中删除当前页 + } + } + nr_free += n;// 更新空闲页的计数 + le = list_next(&free_list); + while (le != &free_list) + { + p = le2page(le, page_link); + if (base + base->property <= p) + { + assert(base + base->property != p); + break; } + le = list_next(le); } - nr_free += n; - list_add(&free_list, &(base->page_link)); + + list_add_before(le, &(base->page_link));// 将释放的页块添加到空闲列表中 } +//用于返回当前系统中可用的空闲页的数量。 static size_t default_nr_free_pages(void) { - return nr_free; + return nr_free;// 返回当前空闲页的数量 } +//basic_check 函数用于测试内存分配和释放的基本功能, +//确保在不同情况下内存管理系统的正确性,包括分配、释放、合并和引用计数等操作。 static void basic_check(void) { struct Page *p0, *p1, *p2; p0 = p1 = p2 = NULL; + // 分配三个页面 assert((p0 = alloc_page()) != NULL); assert((p1 = alloc_page()) != NULL); assert((p2 = alloc_page()) != NULL); - + // 确保所有分配的页面是不同的 assert(p0 != p1 && p0 != p2 && p1 != p2); + // 确保页面的引用计数为 0 assert(page_ref(p0) == 0 && page_ref(p1) == 0 && page_ref(p2) == 0); - + // 确保页面地址在合法范围内 assert(page2pa(p0) < npage * PGSIZE); assert(page2pa(p1) < npage * PGSIZE); assert(page2pa(p2) < npage * PGSIZE); - + // 保存当前的空闲页面链表和数量 list_entry_t free_list_store = free_list; - list_init(&free_list); - assert(list_empty(&free_list)); - - unsigned int nr_free_store = nr_free; - nr_free = 0; + list_init(&free_list);// 初始化空闲列表 + assert(list_empty(&free_list));// 确保空闲列表为空 + unsigned int nr_free_store = nr_free;// 保存当前空闲页数量 + nr_free = 0;// 将空闲页数量设为 0 + // 请求分配页面,但当前没有空闲页面 assert(alloc_page() == NULL); - + // 释放之前分配的页面 free_page(p0); free_page(p1); free_page(p2); - assert(nr_free == 3); - + assert(nr_free == 3);// 确保释放后空闲页数量为 3 + // 再次分配三个页面 assert((p0 = alloc_page()) != NULL); assert((p1 = alloc_page()) != NULL); assert((p2 = alloc_page()) != NULL); - + // 测试空闲页面是否不足 assert(alloc_page() == NULL); - + // 释放 p0,并检查空闲列表 free_page(p0); - assert(!list_empty(&free_list)); + assert(!list_empty(&free_list));// 确保空闲列表不为空 struct Page *p; + // 重新分配 p0,确保取回的是相同的页面 assert((p = alloc_page()) == p0); - assert(alloc_page() == NULL); + assert(alloc_page() == NULL);// 确保没有更多的页面可分配 - assert(nr_free == 0); + assert(nr_free == 0);// 确保当前空闲页面数量为 0 + // 恢复之前的空闲页面链表和数量 free_list = free_list_store; nr_free = nr_free_store; - + // 释放最后的页面 free_page(p); free_page(p1); free_page(p2); @@ -240,63 +314,65 @@ static void default_check(void) { int count = 0, total = 0; list_entry_t *le = &free_list; + // 遍历空闲列表,计算空闲页面的数量和总属性值 while ((le = list_next(le)) != &free_list) { struct Page *p = le2page(le, page_link); - assert(PageProperty(p)); - count ++, total += p->property; + assert(PageProperty(p));// 确保每个页面的属性是有效的 + count ++, total += p->property;// 累加页面属性 } + // 确保总属性值与空闲页面数量匹配 assert(total == nr_free_pages()); - + // 调用 basic_check 以验证基本的内存管理功能 basic_check(); - + // 分配 5 个页面 struct Page *p0 = alloc_pages(5), *p1, *p2; - assert(p0 != NULL); - assert(!PageProperty(p0)); - + assert(p0 != NULL);// 确保成功分配 + assert(!PageProperty(p0));// 确保分配的页面不带属性 + // 初始化并检查空闲列表 list_entry_t free_list_store = free_list; list_init(&free_list); - assert(list_empty(&free_list)); - assert(alloc_page() == NULL); - - unsigned int nr_free_store = nr_free; - nr_free = 0; + assert(list_empty(&free_list));// 确保空闲列表为空 + assert(alloc_page() == NULL);// 确保没有页面可分配 + unsigned int nr_free_store = nr_free;// 保存当前空闲页数 + nr_free = 0;// 将空闲页数设为 0 + // 释放 3 个页面并确保分配页面时没有足够的空闲页 free_pages(p0 + 2, 3); - assert(alloc_pages(4) == NULL); - assert(PageProperty(p0 + 2) && p0[2].property == 3); - assert((p1 = alloc_pages(3)) != NULL); - assert(alloc_page() == NULL); - assert(p0 + 2 == p1); - - p2 = p0 + 1; - free_page(p0); - free_pages(p1, 3); - assert(PageProperty(p0) && p0->property == 1); - assert(PageProperty(p1) && p1->property == 3); - + assert(alloc_pages(4) == NULL);// 确保无法分配 4 个页面 + assert(PageProperty(p0 + 2) && p0[2].property == 3);// 检查页面属性 + assert((p1 = alloc_pages(3)) != NULL);// 再次分配 3 个页面 + assert(alloc_page() == NULL);// 确保没有页面可分配 + assert(p0 + 2 == p1);// 确保分配的页面是释放的页面 + + p2 = p0 + 1;// 设置 p2 为 p0 的下一个页面 + free_page(p0);// 释放 p0 页面 + free_pages(p1, 3);// 释放 p1 指向的页面 + assert(PageProperty(p0) && p0->property == 1);// 检查 p0 属性 + assert(PageProperty(p1) && p1->property == 3);// 检查 p1 属性 + // 确保重分配的页面是之前释放的页面 assert((p0 = alloc_page()) == p2 - 1); - free_page(p0); - assert((p0 = alloc_pages(2)) == p2 + 1); - + free_page(p0);// 释放分配的页面 + assert((p0 = alloc_pages(2)) == p2 + 1);// 分配 2 个页面并检查 + // 释放页面并检查空闲状态 free_pages(p0, 2); free_page(p2); - + // 再次分配 5 个页面 assert((p0 = alloc_pages(5)) != NULL); - assert(alloc_page() == NULL); - - assert(nr_free == 0); - nr_free = nr_free_store; + assert(alloc_page() == NULL);// 确保没有额外页面可分配 + assert(nr_free == 0);// 确保空闲页数为 0 + nr_free = nr_free_store;// 恢复空闲页数 + // 恢复空闲列表状态 free_list = free_list_store; - free_pages(p0, 5); - + free_pages(p0, 5);// 释放所有分配的页面 + // 验证空闲列表的一致性 le = &free_list; while ((le = list_next(le)) != &free_list) { struct Page *p = le2page(le, page_link); count --, total -= p->property; } - assert(count == 0); - assert(total == 0); + assert(count == 0);// 确保所有页面都已处理 + assert(total == 0);// 确保总属性值为 0 } const struct pmm_manager default_pmm_manager = { diff --git a/labcodes/lab4/kern/mm/pmm.c b/labcodes/lab4/kern/mm/pmm.c index bd534bd7ae4b0c938e69a45f451dbf4f12f084ce..de28df6f978fbb307789b4a16018dfb2817b115c 100644 --- a/labcodes/lab4/kern/mm/pmm.c +++ b/labcodes/lab4/kern/mm/pmm.c @@ -12,101 +12,130 @@ #include #include -/* * - * Task State Segment: + + /* * + * Task State Segment:任务状态段(TSS) * * The TSS may reside anywhere in memory. A special segment register called * the Task Register (TR) holds a segment selector that points a valid TSS * segment descriptor which resides in the GDT. Therefore, to use a TSS * the following must be done in function gdt_init: - * - create a TSS descriptor entry in GDT - * - add enough information to the TSS in memory as needed - * - load the TR register with a segment selector for that segment + * TSS 可以位于内存的任何位置。一个特殊的段寄存器称为任务寄存器(TR),它保存一个段选择子, + * 该选择子指向位于全局描述符表(GDT)中的有效 TSS 段描述符。 + * 因此,要使用 TSS,必须在 gdt_init 函数中完成以下操作: + * + * - create a TSS descriptor entry in GDT 在 GDT 中创建一个 TSS 描述符条目 + * - add enough information to the TSS in memory as needed 根据需要将足够的信息添加到内存中的 TSS + * - load the TR register with a segment selector for that segment 使用该段的段选择子加载 TR 寄存器 * * There are several fileds in TSS for specifying the new stack pointer when a * privilege level change happens. But only the fields SS0 and ESP0 are useful * in our os kernel. + * TSS 中有几个字段用于指定特权级变化发生时的新栈指针。 + * 但在我们的操作系统内核中,仅有 SS0 和 ESP0 字段是有用的。 * * The field SS0 contains the stack segment selector for CPL = 0, and the ESP0 * contains the new ESP value for CPL = 0. When an interrupt happens in protected * mode, the x86 CPU will look in the TSS for SS0 and ESP0 and load their value * into SS and ESP respectively. + * 字段 SS0 包含 CPL = 0 的栈段选择子,而 ESP0 包含 CPL = 0 的新 ESP 值。当在保护模式下发生中断时, + * x86 CPU 将在 TSS 中查找 SS0 和 ESP0,并将其值分别加载到 SS 和 ESP 中。 * */ +//静态任务状态结构体初始化为零 static struct taskstate ts = {0}; -// virtual address of physicall page array +// virtual address of physicall page array声明了一个指向物理页面的数组的指针,用于管理物理内存中的页面。 struct Page *pages; -// amount of physical memory (in pages) +// amount of physical memory (in pages)用于记录物理内存的总量,以页面为单位,初始值为0。 size_t npage = 0; // virtual address of boot-time page directory -extern pde_t __boot_pgdir; -pde_t *boot_pgdir = &__boot_pgdir; +extern pde_t __boot_pgdir;//声明了一个外部变量,表示启动时的页目录。 +pde_t *boot_pgdir = &__boot_pgdir;//将其指针赋值给 boot_pgdir // physical address of boot-time page directory +// 用于存储启动时页目录的物理地址,这通常用于内存管理中的页表转换 uintptr_t boot_cr3; // physical memory management +//声明了一个指向物理内存管理器结构的指针,用于管理和分配物理内存。 const struct pmm_manager *pmm_manager; /* * * The page directory entry corresponding to the virtual address range * [VPT, VPT + PTSIZE) points to the page directory itself. Thus, the page * directory is treated as a page table as well as a page directory. + * 对应于虚拟地址范围 [VPT, VPT + PTSIZE) 的页目录项指向页目录本身。 + * 因此,页目录既被视为页表,也被视为页目录。 * * One result of treating the page directory as a page table is that all PTEs * can be accessed though a "virtual page table" at virtual address VPT. And the * PTE for number n is stored in vpt[n]. + * 将页目录视为页表的一个结果是,所有的页表项(PTE)可以通过位于虚拟地址 VPT 的“虚拟页表”进行访问。 + * 页表项编号为 n 的项存储在 vpt[n] 中。 * * A second consequence is that the contents of the current page directory will * always available at virtual address PGADDR(PDX(VPT), PDX(VPT), 0), to which * vpd is set bellow. + * 第二个结果是,当前页目录的内容总是可以在虚拟地址 PGADDR(PDX(VPT), PDX(VPT), 0) 处获得, + * vpd 将被设置为该地址。 * */ -pte_t * const vpt = (pte_t *)VPT; -pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0); - +pte_t * const vpt = (pte_t *)VPT;//定义了一个常量指针 vpt,指向虚拟地址 VPT,该地址表示虚拟页表。 +pde_t * const vpd = (pde_t *)PGADDR(PDX(VPT), PDX(VPT), 0);//定义了另一个常量指针 vpd,指向当前页目录的虚拟地址。 +//PGADDR 是一个宏或函数,用于生成特定格式的虚拟地址,其中 PDX 可能用于计算虚拟地址的索引部分。 /* * * Global Descriptor Table: - * + *全局描述符表 * The kernel and user segments are identical (except for the DPL). To load * the %ss register, the CPL must equal the DPL. Thus, we must duplicate the * segments for the user and the kernel. Defined as follows: - * - 0x0 : unused (always faults -- for trapping NULL far pointers) - * - 0x8 : kernel code segment - * - 0x10: kernel data segment - * - 0x18: user code segment - * - 0x20: user data segment - * - 0x28: defined for tss, initialized in gdt_init + * 内核和用户段是相同的(除了特权级 DPL )。要加载 %ss 寄存器,当前特权级 CPL 必须等于 DPL。 + * 因此,我们必须为用户和内核重复这些段。定义如下: + * - 0x0 : unused (always faults -- for trapping NULL far pointers)未使用(始终发生错误 -- 用于捕获 NULL 远指针) + * - 0x8 : kernel code segment 内核代码段 + * - 0x10: kernel data segment 内核数据段 + * - 0x18: user code segment 用户代码段 + * - 0x20: user data segment 用户数据段 + * - 0x28: defined for tss, initialized in gdt_init 定义用于 TSS,在 gdt_init 中初始化 * */ +//定义了一个静态的全局描述符表(GDT),用于管理内核和用户的段描述符。 static struct segdesc gdt[] = { - SEG_NULL, + SEG_NULL,//第一个段描述符,保留为 NULL,用于捕获无效的段访问。 + //内核代码段,具有执行(STA_X)和可读(STA_R)属性,基地址为 0x0,大小为 0xFFFFFFFF,特权级为内核(DPL_KERNEL)。 [SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL), + //内核数据段,具有可写(STA_W)属性,基地址为 0x0,大小为 0xFFFFFFFF,特权级为内核(DPL_KERNEL)。 [SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL), + //用户代码段,具有执行(STA_X)和可读(STA_R)属性,基地址为 0x0,大小为 0xFFFFFFFF,特权级为用户(DPL_USER)。 [SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER), + //用户数据段,具有可写(STA_W)属性,基地址为 0x0,大小为 0xFFFFFFFF,特权级为用户(DPL_USER)。 [SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER), - [SEG_TSS] = SEG_NULL, + [SEG_TSS] = SEG_NULL,//任务状态段(TSS),保留为 NULL,后续会在初始化时设置。 }; - +//定义了一个静态的伪描述符,用于描述 GDT 的长度和地址。 static struct pseudodesc gdt_pd = { sizeof(gdt) - 1, (uintptr_t)gdt }; -static void check_alloc_page(void); -static void check_pgdir(void); -static void check_boot_pgdir(void); +static void check_alloc_page(void);//声明一个静态函数,用于检查内存页的分配。 +static void check_pgdir(void);//声明一个静态函数,用于检查页目录的有效性。 +static void check_boot_pgdir(void);//声明一个静态函数,用于检查启动时的页目录。 /* * * lgdt - load the global descriptor table register and reset the * data/code segement registers for kernel. + * lgdt - 加载全局描述符表寄存器并重置内核的数据/代码段寄存器。 * */ +//定义了一个静态内联函数 lgdt,接收一个指向伪描述符(struct pseudodesc)的指针 pd static inline void lgdt(struct pseudodesc *pd) { + //这行汇编代码使用 lgdt 指令加载 GDT。%0 被替换为指向 pd 的指针,告诉处理器 GDT 的地址。 asm volatile ("lgdt (%0)" :: "r" (pd)); - asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS)); - asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS)); - asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS)); - asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS)); - asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS)); + asm volatile ("movw %%ax, %%gs" :: "a" (USER_DS));//将 USER_DS(用户数据段)的值移动到 gs 段寄存器。 + asm volatile ("movw %%ax, %%fs" :: "a" (USER_DS));//将 USER_DS 的值移动到 fs 段寄存器。 + asm volatile ("movw %%ax, %%es" :: "a" (KERNEL_DS));//将 KERNEL_DS(内核数据段)的值移动到 es 段寄存器。 + asm volatile ("movw %%ax, %%ds" :: "a" (KERNEL_DS));//将 KERNEL_DS 的值移动到 ds 段寄存器 + asm volatile ("movw %%ax, %%ss" :: "a" (KERNEL_DS));//将 KERNEL_DS 的值移动到 ss 段寄存器 // reload cs + //通过 ljmp 指令重新加载代码段寄存器 cs,并跳转到标签 1。 asm volatile ("ljmp %0, $1f\n 1:\n" :: "i" (KERNEL_CS)); } @@ -114,54 +143,66 @@ lgdt(struct pseudodesc *pd) { * load_esp0 - change the ESP0 in default task state segment, * so that we can use different kernel stack when we trap frame * user to kernel. + * load_esp0 - 修改默认任务状态段中的 ESP0,以便在从用户态陷入内核态时能够使用不同的内核栈。 * */ +//uintptr_t esp0:这是新的堆栈指针,通常指向内核栈的顶部。 +//修改当前任务状态段(TSS)中的 ESP0 值。ESP0 是在从用户态切换到内核态时,CPU 使用的内核栈指针。 void load_esp0(uintptr_t esp0) { ts.ts_esp0 = esp0; } /* gdt_init - initialize the default GDT and TSS */ +/* gdt_init - 初始化默认的 GDT 和 TSS */ static void gdt_init(void) { + // 设置启动内核栈和默认的 SS0 // set boot kernel stack and default SS0 load_esp0((uintptr_t)bootstacktop); ts.ts_ss0 = KERNEL_DS; - + // 初始化 GDT 中的 TSS 字段 // initialize the TSS filed of the gdt gdt[SEG_TSS] = SEGTSS(STS_T32A, (uintptr_t)&ts, sizeof(ts), DPL_KERNEL); - + // 使用lgdt加载全局描述符表,更新所有段寄存器 // reload all segment registers lgdt(&gdt_pd); - + // 加载 TSS,使 CPU 在进行特权级切换时能够正确使用 TSS。 // load the TSS ltr(GD_TSS); } //init_pmm_manager - initialize a pmm_manager instance +//初始化一个 pmm_manager 实例 static void init_pmm_manager(void) { + //将 pmm_manager 指向默认的 PMM 管理器实例。 pmm_manager = &default_pmm_manager; + //使用 cprintf 打印当前内存管理器的名称。 cprintf("memory management: %s\n", pmm_manager->name); + //调用 PMM 管理器的初始化函数,以设置和准备内存管理的相关数据结构。 pmm_manager->init(); } -//init_memmap - call pmm->init_memmap to build Page struct for free memory +//init_memmap - call pmm->init_memmap to build Page struct for free memory +// init_memmap - 调用 pmm->init_memmap 构建空闲内存的 Page 结构 +//struct Page *base:指向内存页的基础地址。 size_t n:要初始化的页数。 static void init_memmap(struct Page *base, size_t n) { pmm_manager->init_memmap(base, n); } //alloc_pages - call pmm->alloc_pages to allocate a continuous n*PAGESIZE memory +// alloc_pages - 调用 pmm->alloc_pages 分配连续的 n*PAGESIZE 内存 struct Page * alloc_pages(size_t n) { struct Page *page=NULL; bool intr_flag; - + //使用 local_intr_save 保存当前的中断状态,以避免在分配内存时发生中断。 while (1) { local_intr_save(intr_flag); { - page = pmm_manager->alloc_pages(n); + page = pmm_manager->alloc_pages(n);//尝试分配 n 个页面。 } local_intr_restore(intr_flag); @@ -176,11 +217,15 @@ alloc_pages(size_t n) { } //free_pages - call pmm->free_pages to free a continuous n*PAGESIZE memory +// free_pages - 调用 pmm->free_pages 释放连续的 n*PAGESIZE 内存 +//struct Page *base:指向要释放的内存页的基础地址。size_t n:要释放的页数。 void free_pages(struct Page *base, size_t n) { bool intr_flag; + //使用 local_intr_save 保存当前的中断状态,以避免在释放内存时发生中断。 local_intr_save(intr_flag); { + //调用物理内存管理器的 free_pages 函数释放 n 页的内存。 pmm_manager->free_pages(base, n); } local_intr_restore(intr_flag); @@ -188,65 +233,68 @@ free_pages(struct Page *base, size_t n) { //nr_free_pages - call pmm->nr_free_pages to get the size (nr*PAGESIZE) //of current free memory +// nr_free_pages - 调用 pmm->nr_free_pages 获取当前空闲内存的大小 (nr * PAGESIZE) size_t nr_free_pages(void) { - size_t ret; - bool intr_flag; - local_intr_save(intr_flag); + size_t ret;// 定义变量 ret 用于存储返回的空闲内存大小 + bool intr_flag;// 定义变量 intr_flag 用于保存中断状态 + local_intr_save(intr_flag);// 保存当前中断状态,并禁用中断 { - ret = pmm_manager->nr_free_pages(); + ret = pmm_manager->nr_free_pages();// 调用物理内存管理器的函数获取空闲内存页数 } - local_intr_restore(intr_flag); - return ret; + local_intr_restore(intr_flag);// 恢复之前保存的中断状态 + return ret;// 返回空闲内存的大小 } /* pmm_init - initialize the physical memory management */ +/* pmm_init - 初始化物理内存管理 */ static void page_init(void) { + // 获取物理内存映射信息,存于特定地址 struct e820map *memmap = (struct e820map *)(0x8000 + KERNBASE); - uint64_t maxpa = 0; - - cprintf("e820map:\n"); + uint64_t maxpa = 0;// 初始化最大物理地址为0 + + cprintf("e820map:\n");// 打印“e820map”标题 int i; - for (i = 0; i < memmap->nr_map; i ++) { - uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; - cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n", + for (i = 0; i < memmap->nr_map; i ++) {// 遍历内存映射数组 + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size;// 获取每个区域的起始和结束地址 + cprintf(" memory: %08llx, [%08llx, %08llx], type = %d.\n",// 打印内存区域的信息 memmap->map[i].size, begin, end - 1, memmap->map[i].type); - if (memmap->map[i].type == E820_ARM) { - if (maxpa < end && begin < KMEMSIZE) { - maxpa = end; + if (memmap->map[i].type == E820_ARM) {// 检查内存类型是否为可用内存 + if (maxpa < end && begin < KMEMSIZE) {// 检查当前区域是否在有效范围内 + maxpa = end;// 更新最大物理地址 } } } - if (maxpa > KMEMSIZE) { - maxpa = KMEMSIZE; + if (maxpa > KMEMSIZE) {// 如果最大物理地址超过了预定义的内存上限 + maxpa = KMEMSIZE;// 将其限制为内存上限 } - extern char end[]; + extern char end[];// 引入全局变量 end,指向内存的结束位置 - npage = maxpa / PGSIZE; - pages = (struct Page *)ROUNDUP((void *)end, PGSIZE); + npage = maxpa / PGSIZE;// 计算可用页数 + pages = (struct Page *)ROUNDUP((void *)end, PGSIZE);// 将 end 对齐到页边界,指向页结构数组的开头 - for (i = 0; i < npage; i ++) { - SetPageReserved(pages + i); + for (i = 0; i < npage; i ++) {// 遍历每一页 + SetPageReserved(pages + i);// 将每一页标记为保留状态 } - uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage); + uintptr_t freemem = PADDR((uintptr_t)pages + sizeof(struct Page) * npage);// 计算可用内存的起始地址 - for (i = 0; i < memmap->nr_map; i ++) { - uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size; - if (memmap->map[i].type == E820_ARM) { - if (begin < freemem) { - begin = freemem; + for (i = 0; i < memmap->nr_map; i ++) {// 再次遍历内存映射 + uint64_t begin = memmap->map[i].addr, end = begin + memmap->map[i].size;// 获取每个区域的起始和结束地址 + if (memmap->map[i].type == E820_ARM) {// 如果区域类型为可用内存 + if (begin < freemem) {// 如果起始地址小于可用内存地址 + begin = freemem;//将起始地址设置为可用内存地址 } - if (end > KMEMSIZE) { - end = KMEMSIZE; + if (end > KMEMSIZE) {// 如果结束地址超过内存上限 + end = KMEMSIZE;// 将其限制为内存上限 } - if (begin < end) { - begin = ROUNDUP(begin, PGSIZE); - end = ROUNDDOWN(end, PGSIZE); - if (begin < end) { - init_memmap(pa2page(begin), (end - begin) / PGSIZE); + if (begin < end) {// 如果起始地址小于结束地址 + begin = ROUNDUP(begin, PGSIZE);// 将起始地址对齐到页边界 + end = ROUNDDOWN(end, PGSIZE);// 将结束地址对齐到页边界 + if (begin < end) {// 如果调整后的起始地址仍小于结束地址 + init_memmap(pa2page(begin), (end - begin) / PGSIZE);// 初始化内存页映射 } } } @@ -255,19 +303,29 @@ page_init(void) { //boot_map_segment - setup&enable the paging mechanism // parameters +// boot_map_segment - 设置并启用分页机制 // la: linear address of this memory need to map (after x86 segment map) -// size: memory size -// pa: physical address of this memory -// perm: permission of this memory +//la: 需要映射的线性地址(经过 x86 段映射后的地址) +// size: memory size size: 内存大小 +// pa: physical address of this memory pa:该内存的物理地址 +// perm: permission of this memory perm: 该内存的权限 static void boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t perm) { + // 确保线性地址和物理地址的页偏移相同 assert(PGOFF(la) == PGOFF(pa)); + // 计算需要映射的页数,ROUNDUP 将总大小对齐到下一个页大小的边界 size_t n = ROUNDUP(size + PGOFF(la), PGSIZE) / PGSIZE; + // 将线性地址向下对齐到页边界 la = ROUNDDOWN(la, PGSIZE); + // 将物理地址向下对齐到页边界 pa = ROUNDDOWN(pa, PGSIZE); + // 循环遍历每一页,直到映射的页数为零 for (; n > 0; n --, la += PGSIZE, pa += PGSIZE) { + // 获取当前页的页表项指针,如果不存在则创建新的页表项 pte_t *ptep = get_pte(pgdir, la, 1); + // 确保页表项指针不为空 assert(ptep != NULL); + // 设置页表项,包含物理地址、存在位和权限 *ptep = pa | PTE_P | perm; } } @@ -275,20 +333,25 @@ boot_map_segment(pde_t *pgdir, uintptr_t la, size_t size, uintptr_t pa, uint32_t //boot_alloc_page - allocate one page using pmm->alloc_pages(1) // return value: the kernel virtual address of this allocated page //note: this function is used to get the memory for PDT(Page Directory Table)&PT(Page Table) +//boot_alloc_page - 使用 pmm->alloc_pages(1) 分配一页内存.返回值: 分配的页面的内核虚拟地址 +//注意: 此函数用于获取页目录表(PDT)和页表(PT)的内存 static void * boot_alloc_page(void) { - struct Page *p = alloc_page(); - if (p == NULL) { - panic("boot_alloc_page failed.\n"); + struct Page *p = alloc_page();// 调用分配页面的函数 + if (p == NULL) {// 检查分配是否成功 + panic("boot_alloc_page failed.\n");// 如果分配失败,则触发异常 } - return page2kva(p); + return page2kva(p);// 返回分配页面的内核虚拟地址 } //pmm_init - setup a pmm to manage physical memory, build PDT&PT to setup paging mechanism // - check the correctness of pmm & paging mechanism, print PDT&PT +//pmm_init - 设置物理内存管理器,构建页目录表(PDT)和页表(PT),以设置分页机制 +// - 检查物理内存管理器和分页机制的正确性,打印页目录表和页表 void pmm_init(void) { // We've already enabled paging + // 我们已经启用了分页 boot_cr3 = PADDR(boot_pgdir); //We need to alloc/free the physical memory (granularity is 4KB or other size). @@ -296,39 +359,56 @@ pmm_init(void) { //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. - init_pmm_manager(); + // 我们需要分配/释放物理内存(粒度为 4KB 或其他大小)。 + // 因此在 pmm.h 中定义了物理内存管理器的框架(struct pmm_manager)。 + // 首先,我们应该基于该框架初始化一个物理内存管理器(pmm)。 + // 然后 pmm 可以分配/释放物理内存。 + // 现在,first_fit/best_fit/worst_fit/buddy_system 的 pmm 都可用。 + init_pmm_manager();// 初始化物理内存管理器 // detect physical memory space, reserve already used memory, // then use pmm->init_memmap to create free page list - page_init(); + // 检测物理内存空间,保留已经使用的内存, + // 然后使用 pmm->init_memmap 创建空闲页面列表 + page_init();// 初始化页面管理 //use pmm->check to verify the correctness of the alloc/free function in a pmm - check_alloc_page(); + // 使用 pmm->check 验证 pmm 中分配/释放函数的正确性 + check_alloc_page();// 检查页面分配功能 - check_pgdir(); + check_pgdir();// 检查页目录的状态 - static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0); + static_assert(KERNBASE % PTSIZE == 0 && KERNTOP % PTSIZE == 0);// 确保 KERNBASE 和 KERNTOP 是页大小的倍数 // recursively insert boot_pgdir in itself // to form a virtual page table at virtual address VPT - boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W; + // 递归地将 boot_pgdir 插入到自身中 + // 在虚拟地址 VPT 处形成虚拟页表 + boot_pgdir[PDX(VPT)] = PADDR(boot_pgdir) | PTE_P | PTE_W;// 设置页目录项,映射自身 // map all physical memory to linear memory with base linear addr KERNBASE // linear_addr KERNBASE ~ KERNBASE + KMEMSIZE = phy_addr 0 ~ KMEMSIZE - boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W); + // 将所有物理内存映射到线性内存,基地址为 KERNBASE + // 线性地址 KERNBASE ~ KERNBASE + KMEMSIZE = 物理地址 0 ~ KMEMSIZE + boot_map_segment(boot_pgdir, KERNBASE, KMEMSIZE, 0, PTE_W);// 映射物理内存 // Since we are using bootloader's GDT, // we should reload gdt (second time, the last time) to get user segments and the TSS // map virtual_addr 0 ~ 4G = linear_addr 0 ~ 4G // then set kernel stack (ss:esp) in TSS, setup TSS in gdt, load TSS - gdt_init(); + // 由于我们正在使用引导加载程序的 GDT, + // 我们应该重新加载 GDT(第二次,也是最后一次),以获取用户段和 TSS + // 映射虚拟地址 0 ~ 4G = 线性地址 0 ~ 4G + // 然后在 TSS 中设置内核栈 (ss:esp),在 gdt 中设置 TSS,加载 TSS + gdt_init();// 初始化全局描述符表 //now the basic virtual memory map(see memalyout.h) is established. //check the correctness of the basic virtual memory map. - check_boot_pgdir(); + // 现在基本的虚拟内存映射(见 memlayout.h)已建立。 + // 检查基础虚拟内存映射的正确性。 + check_boot_pgdir(); // 检查页目录的正确性 - print_pgdir(); - + print_pgdir(); // 打印页目录表 kmalloc_init(); } @@ -340,6 +420,13 @@ pmm_init(void) { // la: the linear address need to map // create: a logical value to decide if alloc a page for PT // return vaule: the kernel virtual address of this pte +// get_pte - 获取页表项(PTE),并返回该 PTE 的内核虚拟地址 +// 如果页表中不存在该 PTE,则为页表分配一页 +// 参数: +// pgdir: 页目录的内核虚拟基地址 +// la: 需要映射的线性地址 +// create: 一个逻辑值,决定是否为页表分配一页 +// 返回值:该 PTE 的内核虚拟地址 pte_t * get_pte(pde_t *pgdir, uintptr_t la, bool create) { /* LAB2 EXERCISE 2: YOUR CODE @@ -375,18 +462,42 @@ get_pte(pde_t *pgdir, uintptr_t la, bool create) { } return NULL; // (8) return page table entry #endif + // (1) 找到页目录项 + pde_t *pdep = &pgdir[PDX(la)];// 使用 PDX 宏获取页目录索引 + // (2) 检查页目录项是否存在 + if (!(*pdep & PTE_P)) {// 如果页目录项的存在位 PTE_P 没有被设置 + struct Page *page;// 声明一个指针,用于指向新分配的页面 + // 检查是否允许创建新页表,或者分配页表失败 + if (!create || (page = alloc_page()) == NULL) {// 如果不允许创建或分配失败 + return NULL;// 返回 NULL,表示无法获取页表 + } + // 设置新分配页面的引用计数为 1 + set_page_ref(page, 1); + uintptr_t pa = page2pa(page);// 获取新分配页面的物理地址 + memset(KADDR(pa), 0, PGSIZE);// 清空新分配的页表内容,初始化为零 + // 更新页目录项,设置物理地址和权限位 + *pdep = pa | PTE_U | PTE_W | PTE_P;// 将物理地址和权限位(用户可访问、可写、有效)合并设置 + } + // 返回指定线性地址 la 对应的页表项的内核虚拟地址 + return &((pte_t *)KADDR(PDE_ADDR(*pdep)))[PTX(la)];// 计算并返回页表项的指针 } //get_page - get related Page struct for linear address la using PDT pgdir +// get_page - 获取与线性地址 la 相关的 Page 结构体,使用页目录 pgdir struct Page * get_page(pde_t *pgdir, uintptr_t la, pte_t **ptep_store) { + // 调用 get_pte 函数获取对应线性地址 la 的页表项指针 pte_t *ptep = get_pte(pgdir, la, 0); + // 如果 ptep_store 指针不为 NULL,将 ptep 存储到 ptep_store 指向的位置 if (ptep_store != NULL) { - *ptep_store = ptep; + *ptep_store = ptep; // 存储当前页表项的指针 } + // 检查 ptep 是否有效以及页表项的存在位 PTE_P 是否被设置 if (ptep != NULL && *ptep & PTE_P) { - return pte2page(*ptep); + // 返回与页表项对应的 Page 结构体 + return pte2page(*ptep);// 将页表项转换为对应的 Page 结构 } + // 如果未找到有效的页,返回 NULL return NULL; } @@ -420,12 +531,24 @@ page_remove_pte(pde_t *pgdir, uintptr_t la, pte_t *ptep) { //(6) flush tlb } #endif + if (*ptep & PTE_P) { + struct Page *page = pte2page(*ptep);// 找到对应的物理页 + // 减少物理页的引用计数,如果引用计数为零,释放该物理页 + if (page_ref_dec(page) == 0) { + free_page(page); + } + *ptep = 0;// 清除页表项 + tlb_invalidate(pgdir, la);// 刷新 TLB + } } //page_remove - free an Page which is related linear address la and has an validated pte +//移除一个虚拟地址对应的页面 void page_remove(pde_t *pgdir, uintptr_t la) { + //调用 get_pte 函数获取给定虚拟地址 la 对应的页表项指针 ptep。 pte_t *ptep = get_pte(pgdir, la, 0); + //如果 ptep 不为 NULL,则调用 page_remove_pte 函数移除该页表项。 if (ptep != NULL) { page_remove_pte(pgdir, la, ptep); } @@ -439,32 +562,41 @@ page_remove(pde_t *pgdir, uintptr_t la) { // perm: the permission of this Page which is setted in related pte // return value: always 0 //note: PT is changed, so the TLB need to be invalidate +//将一个页面插入到页表中。 int page_insert(pde_t *pgdir, struct Page *page, uintptr_t la, uint32_t perm) { + //通过 get_pte 函数获取指定虚拟地址 la 对应的页表项指针 ptep。 pte_t *ptep = get_pte(pgdir, la, 1); + //如果 ptep 为 NULL,表示内存分配失败,返回 -E_NO_MEM。 if (ptep == NULL) { return -E_NO_MEM; } + //调用 page_ref_inc 增加页面的引用计数。 page_ref_inc(page); + //如果页表项已存在且指向当前页面,则减少页面引用计数。 if (*ptep & PTE_P) { struct Page *p = pte2page(*ptep); if (p == page) { page_ref_dec(page); } + //如果页表项已存在但指向其他页面,则调用 page_remove_pte 移除旧的页表项。 else { page_remove_pte(pgdir, la, ptep); } } *ptep = page2pa(page) | PTE_P | perm; - tlb_invalidate(pgdir, la); + tlb_invalidate(pgdir, la);//刷新 TLB return 0; } // invalidate a TLB entry, but only if the page tables being // edited are the ones currently in use by the processor. +//无效化指定地址的TLB条目 void tlb_invalidate(pde_t *pgdir, uintptr_t la) { + //检查当前页目录地址是否与传入的页目录地址相同。 if (rcr3() == PADDR(pgdir)) { + //如果相同,则调用 invlpg 函数无效化指定线性地址的TLB条目。 invlpg((void *)la); } } @@ -472,17 +604,21 @@ tlb_invalidate(pde_t *pgdir, uintptr_t la) { // pgdir_alloc_page - call alloc_page & page_insert functions to // - allocate a page size memory & setup an addr map // - pa<->la with linear address la and the PDT pgdir +//参数包括页目录指针 pgdir、线性地址 la 和权限 perm。 struct Page * pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { - struct Page *page = alloc_page(); - if (page != NULL) { - if (page_insert(pgdir, page, la, perm) != 0) { - free_page(page); - return NULL; + struct Page *page = alloc_page();//分配一个新的页面存储在 page 指针中 + if (page != NULL) {//检查 page 是否不为 NULL,即分配是否成功。 + if (page_insert(pgdir, page, la, perm) != 0) {//将页面插入到指定的线性地址 la 处。 + free_page(page);//释放分配的页面。 + return NULL;//返回 NULL,表示页面插入失败。 } - if (swap_init_ok){ + if (swap_init_ok){//检查交换区是否已初始化成功 + //将页面映射到交换区。 swap_map_swappable(check_mm_struct, la, page, 0); + //设置页面的虚拟地址 pra_vaddr 为 la page->pra_vaddr=la; + //断言页面的引用计数为1,确保页面没有被其他地方引用。 assert(page_ref(page) == 1); //cprintf("get No. %d page: pra_vaddr %x, pra_link.prev %x, pra_link_next %x in pgdir_alloc_page\n", (page-pages), page->pra_vaddr,page->pra_page_link.prev, page->pra_page_link.next); } @@ -494,99 +630,142 @@ pgdir_alloc_page(pde_t *pgdir, uintptr_t la, uint32_t perm) { static void check_alloc_page(void) { + //调用内存管理器的 check 方法,用于检查内存分配是否正常。 pmm_manager->check(); cprintf("check_alloc_page() succeeded!\n"); } +//用于验证页目录和页表的正确性。 static void check_pgdir(void) { + //确保内存页面数量在合理范围内 assert(npage <= KMEMSIZE / PGSIZE); + //确保页目录不为空且对齐, assert(boot_pgdir != NULL && (uint32_t)PGOFF(boot_pgdir) == 0); + //确保虚拟地址 0x0 没有映射任何页面 assert(get_page(boot_pgdir, 0x0, NULL) == NULL); - + + //定义两个页面指针 p1 和 p2 struct Page *p1, *p2; + //分配一个页面 p1 p1 = alloc_page(); + //将 p1 插入到虚拟地址 0x0 assert(page_insert(boot_pgdir, p1, 0x0, 0) == 0); + // 获取虚拟地址 0x0 对应的页表项指针 pte_t *ptep; assert((ptep = get_pte(boot_pgdir, 0x0, 0)) != NULL); + // 验证页表项对应的页面是 p1 assert(pte2page(*ptep) == p1); + // 验证 p1 的引用计数为 1 assert(page_ref(p1) == 1); - + // 获取虚拟地址 PGSIZE 对应的页表项指针 ptep = &((pte_t *)KADDR(PDE_ADDR(boot_pgdir[0])))[1]; assert(get_pte(boot_pgdir, PGSIZE, 0) == ptep); - + // 分配一个页面 p2 p2 = alloc_page(); + // 将 p2 插入到虚拟地址 PGSIZE,并设置用户和写权限 assert(page_insert(boot_pgdir, p2, PGSIZE, PTE_U | PTE_W) == 0); + // 获取虚拟地址 PGSIZE 对应的页表项指针 assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + // 验证页表项设置了用户权限 assert(*ptep & PTE_U); + // 验证页表项设置了写权限 assert(*ptep & PTE_W); + // 验证页目录项设置了用户权限 assert(boot_pgdir[0] & PTE_U); + // 验证 p2 的引用计数为 1 assert(page_ref(p2) == 1); + // 将 p1 插入到虚拟地址 PGSIZE,替换掉 p2 assert(page_insert(boot_pgdir, p1, PGSIZE, 0) == 0); + // 验证 p1 的引用计数增加到 2 assert(page_ref(p1) == 2); + // 验证 p2 的引用计数减少到 0 assert(page_ref(p2) == 0); + // 获取虚拟地址 PGSIZE 对应的页表项指针 assert((ptep = get_pte(boot_pgdir, PGSIZE, 0)) != NULL); + // 验证页表项对应的页面是 p1 assert(pte2page(*ptep) == p1); + // 验证页表项没有设置用户权限 assert((*ptep & PTE_U) == 0); - + + //移除虚拟地址 0x0 的映射, page_remove(boot_pgdir, 0x0); + //验证 p1 的引用计数减少到 1。 assert(page_ref(p1) == 1); + //验证 p2 的引用计数减少到 0 assert(page_ref(p2) == 0); + //移除虚拟地址 PGSIZE 的映射, page_remove(boot_pgdir, PGSIZE); + //验证 p1 的引用计数减少到 0 assert(page_ref(p1) == 0); + //验证 p2 的引用计数减少到 0 assert(page_ref(p2) == 0); - + + //验证页目录的第一页表的引用计数为 1。 assert(page_ref(pde2page(boot_pgdir[0])) == 1); + //释放页目录的第一页表 free_page(pde2page(boot_pgdir[0])); + //清空页目录的第一页表 boot_pgdir[0] = 0; cprintf("check_pgdir() succeeded!\n"); } +//检查内核页表 boot_pgdir 的正确性 static void check_boot_pgdir(void) { - pte_t *ptep; + pte_t *ptep;// 定义一个指向页表项的指针 int i; - for (i = 0; i < npage; i += PGSIZE) { + for (i = 0; i < npage; i += PGSIZE) {// 遍历所有页面 + // 获取第 i 个页面的页表项,并确保其不为空 assert((ptep = get_pte(boot_pgdir, (uintptr_t)KADDR(i), 0)) != NULL); + // 验证页表项的物理地址是否正确 assert(PTE_ADDR(*ptep) == i); } - + // 验证页目录项的物理地址是否正确 assert(PDE_ADDR(boot_pgdir[PDX(VPT)]) == PADDR(boot_pgdir)); - assert(boot_pgdir[0] == 0); + assert(boot_pgdir[0] == 0);// 确保页目录的第一个项为0 - struct Page *p; - p = alloc_page(); + struct Page *p;// 定义一个指向页面的指针 + p = alloc_page();// 分配一个页面 + // 将页面插入到虚拟地址 0x100,并确保操作成功 assert(page_insert(boot_pgdir, p, 0x100, PTE_W) == 0); - assert(page_ref(p) == 1); + assert(page_ref(p) == 1);// 验证页面的引用计数为1 + // 将页面插入到虚拟地址 0x100 + PGSIZE,并确保操作成功 assert(page_insert(boot_pgdir, p, 0x100 + PGSIZE, PTE_W) == 0); - assert(page_ref(p) == 2); + assert(page_ref(p) == 2);// 验证页面的引用计数为2 - const char *str = "ucore: Hello world!!"; - strcpy((void *)0x100, str); + const char *str = "ucore: Hello world!!";// 定义一个字符串 + strcpy((void *)0x100, str);// 将字符串复制到虚拟地址 0x100 + // 验证两个映射地址的数据是否一致 assert(strcmp((void *)0x100, (void *)(0x100 + PGSIZE)) == 0); - + // 在页面的 0x100 偏移处设置字符串结束符 *(char *)(page2kva(p) + 0x100) = '\0'; - assert(strlen((const char *)0x100) == 0); + assert(strlen((const char *)0x100) == 0);// 验证字符串长度为0 - free_page(p); - free_page(pde2page(boot_pgdir[0])); - boot_pgdir[0] = 0; + free_page(p);// 释放页面 p + free_page(pde2page(boot_pgdir[0]));// 释放页目录项对应的页面 + boot_pgdir[0] = 0;// 将页目录的第一个项设为0 - cprintf("check_boot_pgdir() succeeded!\n"); + cprintf("check_boot_pgdir() succeeded!\n");// 输出成功信息 } //perm2str - use string 'u,r,w,-' to present the permission static const char * perm2str(int perm) { + //定义一个静态字符数组 str,长度为4 static char str[4]; + //如果 perm 与 PTE_U 按位与的结果不为0,则 str[0] 设置为 'u',否则设置为 '-' str[0] = (perm & PTE_U) ? 'u' : '-'; + //str[1] 始终设置为 'r' str[1] = 'r'; + //如果 perm 与 PTE_W 按位与的结果不为0,则 str[2] 设置为 'w',否则设置为 '-' str[2] = (perm & PTE_W) ? 'w' : '-'; + //str[3] 设置为字符串结束符 \0 str[3] = '\0'; return str; } @@ -602,40 +781,47 @@ perm2str(int perm) { // left_store: the pointer of the high side of table's next range // right_store: the pointer of the low side of table's next range // return value: 0 - not a invalid item range, perm - a valid item range with perm permission +//从页表中获取指定范围内的有效项,并根据权限进行处理。 static int get_pgtable_items(size_t left, size_t right, size_t start, uintptr_t *table, size_t *left_store, size_t *right_store) { - if (start >= right) { - return 0; + if (start >= right) {// 检查起始索引是否超出右边界 + return 0;// 如果超出右边界,返回0 } - while (start < right && !(table[start] & PTE_P)) { - start ++; + while (start < right && !(table[start] & PTE_P)) {// 查找第一个有效项(PTE_P位为1的项) + start ++;// 索引递增 } - if (start < right) { - if (left_store != NULL) { - *left_store = start; + if (start < right) {// 检查是否找到有效项 + if (left_store != NULL) {// 如果left_store不为NULL + *left_store = start;// 记录左边界索引 } - int perm = (table[start ++] & PTE_USER); - while (start < right && (table[start] & PTE_USER) == perm) { - start ++; + int perm = (table[start ++] & PTE_USER);// 获取当前项的用户权限位并递增索引 + while (start < right && (table[start] & PTE_USER) == perm) {// 查找具有相同用户权限的连续项 + start ++;// 索引递增 } - if (right_store != NULL) { - *right_store = start; + if (right_store != NULL) {// 如果right_store不为NULL + *right_store = start;// 记录右边界索引 } - return perm; + return perm;// 返回用户权限位 } - return 0; + return 0;// 如果未找到有效项,返回0 } //print_pgdir - print the PDT&PT void print_pgdir(void) { cprintf("-------------------- BEGIN --------------------\n"); + // 定义变量 left, right 和 perm size_t left, right = 0, perm; + // 遍历页目录项 while ((perm = get_pgtable_items(0, NPDEENTRY, right, vpd, &left, &right)) != 0) { + // 打印页目录项的信息 cprintf("PDE(%03x) %08x-%08x %08x %s\n", right - left, left * PTSIZE, right * PTSIZE, (right - left) * PTSIZE, perm2str(perm)); + // 计算页表项的起始和结束索引 size_t l, r = left * NPTEENTRY; + // 遍历页表项 while ((perm = get_pgtable_items(left * NPTEENTRY, right * NPTEENTRY, r, vpt, &l, &r)) != 0) { + // 打印页表项的信息 cprintf(" |-- PTE(%05x) %08x-%08x %08x %s\n", r - l, l * PGSIZE, r * PGSIZE, (r - l) * PGSIZE, perm2str(perm)); } diff --git a/labcodes/lab4/kern/mm/swap.c b/labcodes/lab4/kern/mm/swap.c index 0ce392fdf7783074b5979315d34a5932b8647500..e56bda56c1192b0394877325e82578e33482ee4f 100644 --- a/labcodes/lab4/kern/mm/swap.c +++ b/labcodes/lab4/kern/mm/swap.c @@ -30,45 +30,86 @@ static void check_swap(void); int swap_init(void) { + // 初始化交换文件系统 swapfs_init(); + // 检查最大交换偏移量是否在合法范围内 if (!(1024 <= max_swap_offset && max_swap_offset < MAX_SWAP_OFFSET_LIMIT)) - { + { + // 如果最大交换偏移量不合法,输出错误信息并panic panic("bad max_swap_offset %08x.\n", max_swap_offset); } - + // 选择并设置交换管理器为FIFO(先进先出)策略 sm = &swap_manager_fifo; + + // 调用选定交换管理器的初始化函数 int r = sm->init(); - + + // 如果交换管理器初始化成功 if (r == 0) { + // 标记交换空间初始化为成功 swap_init_ok = 1; + cprintf("SWAP: manager = %s\n", sm->name); + // 检查交换空间状态 check_swap(); } - + // 返回初始化结果 return r; } int swap_init_mm(struct mm_struct *mm) { + //调用 sm 结构体中的 init_mm 函数,并将 mm 作为参数传递 return sm->init_mm(mm); } +/** + * swap_tick_event - 触发与交换相关的tick事件 + * @mm: 进程的内存描述符 + * + * 此函数通过调用sm结构体中的tick_event函数来处理与交换相关的tick事件。 + * 它将进程的内存描述符作为参数传递给sm的tick_event函数,并返回该调用的结果。 + * + * 返回值: sm->tick_event(mm)的返回值 + */ int swap_tick_event(struct mm_struct *mm) { return sm->tick_event(mm); } +/** + * 调用结构体sm中的map_swappable函数来处理页面交换操作 + * + * @param mm 内存描述符,表示一个内存管理对象 + * @param addr 需要交换的内存地址 + * @param page 指向页面的指针,表示要交换出或交换入的页面 + * @param swap_in 表示交换方向的标志,非零表示交换入,零表示交换出 + * @return 返回map_swappable函数的执行结果,通常表示操作的成功与否 + * + * 此函数的作用是作为交换操作的接口,根据swap_in参数决定是将页面交换入还是交换出 + * 它实际上调用了结构体sm中定义的map_swappable函数,因此具体的操作逻辑依赖于该函数的实现 + */ int swap_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) { return sm->map_swappable(mm, addr, page, swap_in); } +/** + * 将指定内存区域标记为不可交换到磁盘 + * + * @param mm 包含待操作内存区域的内存描述符 + * @param addr 待操作的内存区域的地址 + * @return 返回操作结果,0表示成功,非零表示失败 + * + * 此函数通过调用sm->set_unswappable方法来实现,其主要目的是防止指定的内存区域被交换到磁盘, + * 通常用于那些需要常驻内存不能被交换出的特殊页面。成功时返回0,失败时返回非零值。 + */ int swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) { @@ -77,55 +118,71 @@ swap_set_unswappable(struct mm_struct *mm, uintptr_t addr) volatile unsigned int swap_out_num=0; +/** + * 从内存中交换出指定数量的页面到磁盘交换空间。 + * + * @param mm 内存描述符,表示要执行交换出操作的内存空间。 + * @param n 需要交换出的页面数量。 + * @param in_tick 当前的时钟周期,用于判断页面是否已经过期。 + * + * @return 实际交换出的页面数量。 + * + * 此函数通过选择 victim 页面并将其内容写入到交换空间中,来实现内存中页面的交换出操作。 + * 它主要用于内存压力较大时,释放内存空间,确保系统稳定运行。 + */ int swap_out(struct mm_struct *mm, int n, int in_tick) { int i; - for (i = 0; i != n; ++ i) + for (i = 0; i != n; ++ i)// 循环 n 次,每次处理一个页面 { - uintptr_t v; + uintptr_t v; // 用于存储页面的虚拟地址 //struct Page **ptr_page=NULL; - struct Page *page; + struct Page *page; // 用于存储选中的页面 // cprintf("i %d, SWAP: call swap_out_victim\n",i); + // 调用 swap_out_victim 函数选择一个 victim 页面 int r = sm->swap_out_victim(mm, &page, in_tick); - if (r != 0) { - cprintf("i %d, swap_out: call swap_out_victim failed\n",i); - break; + if (r != 0) {// 如果选择失败 + cprintf("i %d, swap_out: call swap_out_victim failed\n",i);// 输出错误信息 + break;// 中断循环 } //assert(!PageReserved(page)); //cprintf("SWAP: choose victim page 0x%08x\n", page); - v=page->pra_vaddr; - pte_t *ptep = get_pte(mm->pgdir, v, 0); - assert((*ptep & PTE_P) != 0); + v=page->pra_vaddr; // 获取页面的虚拟地址 + pte_t *ptep = get_pte(mm->pgdir, v, 0); // 获取页面表项指针 + assert((*ptep & PTE_P) != 0); // 断言页面在物理内存中存在 - if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) { - cprintf("SWAP: failed to save\n"); - sm->map_swappable(mm, v, page, 0); + if (swapfs_write( (page->pra_vaddr/PGSIZE+1)<<8, page) != 0) {// 将页面内容写入交换文件 + cprintf("SWAP: failed to save\n");// 如果写入失败,输出错误信息 + sm->map_swappable(mm, v, page, 0);// 标记页面为不可交换 continue; } else { cprintf("swap_out: i %d, store page in vaddr 0x%x to disk swap entry %d\n", i, v, page->pra_vaddr/PGSIZE+1); - *ptep = (page->pra_vaddr/PGSIZE+1)<<8; - free_page(page); + *ptep = (page->pra_vaddr/PGSIZE+1)<<8; // 更新页面表项,标记页面已交换到磁盘 + free_page(page); // 释放页面 } - tlb_invalidate(mm->pgdir, v); + tlb_invalidate(mm->pgdir, v); // 使 TLB 中对应的条目失效 } - return i; + return i; // 返回实际交换出的页面数量 } +//实现一个页交换功能。 int swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) { + //分配一个新的页面result struct Page *result = alloc_page(); assert(result!=NULL); - + //获取虚拟地址 addr 对应的页表项指针 ptep pte_t *ptep = get_pte(mm->pgdir, addr, 0); // cprintf("SWAP: load ptep %x swap entry %d to vaddr 0x%08x, page %x, No %d\n", ptep, (*ptep)>>8, addr, result, (result-pages)); int r; + //从交换文件中读取数据到新分配的页面 result 中 if ((r = swapfs_read((*ptep), result)) != 0) { assert(r!=0); @@ -136,7 +193,11 @@ swap_in(struct mm_struct *mm, uintptr_t addr, struct Page **ptr_result) } - +/** + * check_content_set函数用于验证页面故障处理的正确性。 + * 通过向特定内存地址写入数据,并检查页面故障次数是否符合预期,来验证页面管理机制的正确性。 + * 此函数没有输入参数和返回值。 + */ static inline void check_content_set(void) { @@ -158,9 +219,18 @@ check_content_set(void) assert(pgfault_num==4); } +/** + * 检查内容访问权限 + * + * 此函数旨在通过调用swap管理器的检查方法来确定当前上下文是否具有访问特定内容的权限 + * 它提供了一种快速、内联的方式,以确保在进行内容访问之前,所有的权限都已经被正确地授予 + * + * @return int 返回权限检查的结果,非零表示成功,零表示失败 + */ static inline int check_content_access(void) { + // 调用swap管理器的检查方法,并将结果返回 int ret = sm->check_swap(); return ret; } @@ -174,85 +244,89 @@ extern free_area_t free_area; #define free_list (free_area.free_list) #define nr_free (free_area.nr_free) +// 检查交换机制的正确性,通过模拟页面替换算法 static void check_swap(void) { - //backup mem env + //backup mem env// 备份内存环境,确保检查后没有页面丢失 int ret, count = 0, total = 0, i; list_entry_t *le = &free_list; while ((le = list_next(le)) != &free_list) { - struct Page *p = le2page(le, page_link); - assert(PageProperty(p)); - count ++, total += p->property; + struct Page *p = le2page(le, page_link);// 将链表条目转换为页面结构 + assert(PageProperty(p));// 断言页面属性有效 + count ++, total += p->property;// 统计页面数量和属性总和 } - assert(total == nr_free_pages()); - cprintf("BEGIN check_swap: count %d, total %d\n",count,total); + assert(total == nr_free_pages());// 断言统计的属性总和与空闲页面数一致 + cprintf("BEGIN check_swap: count %d, total %d\n",count,total);// 打印初始状态 - //now we set the phy pages env - struct mm_struct *mm = mm_create(); - assert(mm != NULL); + //now we set the phy pages env // 设置物理页面环境 + struct mm_struct *mm = mm_create();// 创建内存管理结构 + assert(mm != NULL); // 断言内存管理结构创建成功 - extern struct mm_struct *check_mm_struct; - assert(check_mm_struct == NULL); + extern struct mm_struct *check_mm_struct;// 声明外部变量 + assert(check_mm_struct == NULL);// 断言外部变量为空 + // 将新创建的内存管理结构赋值给外部变量 check_mm_struct = mm; - pde_t *pgdir = mm->pgdir = boot_pgdir; - assert(pgdir[0] == 0); + pde_t *pgdir = mm->pgdir = boot_pgdir;// 设置页目录 + assert(pgdir[0] == 0);// 断言页目录的第一个条目为空 + // 创建虚拟内存区域 struct vma_struct *vma = vma_create(BEING_CHECK_VALID_VADDR, CHECK_VALID_VADDR, VM_WRITE | VM_READ); - assert(vma != NULL); + assert(vma != NULL);// 断言虚拟内存区域创建成功 + // 插入虚拟内存区域到内存管理结构 insert_vma_struct(mm, vma); - //setup the temp Page Table vaddr 0~4MB - cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n"); + //setup the temp Page Table vaddr 0~4MB/ 设置临时页表,用于虚拟地址 0~4MB + cprintf("setup Page Table for vaddr 0X1000, so alloc a page\n");// 打印设置页表的信息 pte_t *temp_ptep=NULL; - temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1); - assert(temp_ptep!= NULL); - cprintf("setup Page Table vaddr 0~4MB OVER!\n"); + temp_ptep = get_pte(mm->pgdir, BEING_CHECK_VALID_VADDR, 1);// 获取页表项 + assert(temp_ptep!= NULL);// 断言获取页表项成功 + cprintf("setup Page Table vaddr 0~4MB OVER!\n");// 打印设置页表完成的信息 for (i=0;iphy_page environment for page relpacement algorithm - + // 设置初始虚拟到物理页面环境,用于页面替换算法 - pgfault_num=0; + pgfault_num=0;// 初始化页面故障数 - check_content_set(); - assert( nr_free == 0); + check_content_set();// 设置检查内容 + assert( nr_free == 0); // 断言空闲页面数为 0 for(i = 0; iproperty; + struct Page *p = le2page(le, page_link);// 将链表条目转换为页面结构 + count --, total -= p->property;// 更新页面数量和属性总和 } - cprintf("count is %d, total is %d\n",count,total); + cprintf("count is %d, total is %d\n",count,total);// 打印恢复后的状态 //assert(count == 0); - cprintf("check_swap() succeeded!\n"); + cprintf("check_swap() succeeded!\n");// 打印检查成功的信息 } diff --git a/labcodes/lab4/kern/mm/swap_fifo.c b/labcodes/lab4/kern/mm/swap_fifo.c index cd96a03d2f2d6db39b1d5406ff49d8b513002ed0..629ab1a76404dca202c4b87060a9da8b5882a922 100644 --- a/labcodes/lab4/kern/mm/swap_fifo.c +++ b/labcodes/lab4/kern/mm/swap_fifo.c @@ -33,9 +33,12 @@ list_entry_t pra_list_head; static int _fifo_init_mm(struct mm_struct *mm) { + //初始化一个链表头 pra_list_head list_init(&pra_list_head); + //将 mm 结构中的 sm_priv 字段指向这个链表头 mm->sm_priv = &pra_list_head; //cprintf(" mm->sm_priv %x in fifo_init_mm\n",mm->sm_priv); + //返回 0 表示成功 return 0; } /* @@ -44,6 +47,8 @@ _fifo_init_mm(struct mm_struct *mm) static int _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int swap_in) { + //获取 mm_struct 结构中的 sm_priv 指针, + //并将其转换为 list_entry_t 类型的链表头指针 head list_entry_t *head=(list_entry_t*) mm->sm_priv; list_entry_t *entry=&(page->pra_page_link); @@ -51,12 +56,23 @@ _fifo_map_swappable(struct mm_struct *mm, uintptr_t addr, struct Page *page, int //record the page access situlation /*LAB3 EXERCISE 2: YOUR CODE*/ //(1)link the most recent arrival page at the back of the pra_list_head qeueue. + //将最近到达的页面链接到 pra_list_head 队列的末尾 + list_add(head, entry); return 0; } /* * (4)_fifo_swap_out_victim: According FIFO PRA, we should unlink the earliest arrival page in front of pra_list_head qeueue, * then assign the value of *ptr_page to the addr of this page. */ +/** + * _fifo_swap_out_victim 选择一个牺牲页进行换出。 + * + * @param mm 指向内存管理结构的指针,包含页面替换所需的信息。 + * @param ptr_page 指向一个指向页面的指针的指针,用于返回选中的牺牲页。 + * @param in_tick 当前时间刻度,用于检查是否为0。 + * + * @return 返回0表示成功,其他值表示失败。 + */ static int _fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick) { @@ -67,44 +83,85 @@ _fifo_swap_out_victim(struct mm_struct *mm, struct Page ** ptr_page, int in_tick /*LAB3 EXERCISE 2: YOUR CODE*/ //(1) unlink the earliest arrival page in front of pra_list_head qeueue //(2) assign the value of *ptr_page to the addr of this page + //head->prev 获取链表中最先到达的页面 + list_entry_t *le = head->prev; + assert(head!=le); + struct Page *p = le2page(le, pra_page_link); + //使用 list_del 函数将该页面从链表中移除。 + list_del(le); + assert(p != NULL); + //将移除的页面指针赋值给 *ptr_page + *ptr_page = p; + return 0; } + +/** + * _fifo_check_swap 是一个静态函数,用于验证虚拟内存系统中的 FIFO(先进先出)页面置换算法。 + * 该函数通过向特定的虚拟内存地址写入数据,并检查每次写入后发生的页面故障数量是否符合预期,从而验证 FIFO 页面置换算法的正确性。 + * + * 返回值: + * - 0: 表示所有检查均通过。 + */ static int _fifo_check_swap(void) { + // 写入虚拟页 c 并检查页面故障数 cprintf("write Virt Page c in fifo_check_swap\n"); *(unsigned char *)0x3000 = 0x0c; assert(pgfault_num==4); + + // 写入虚拟页 a 并检查页面故障数 cprintf("write Virt Page a in fifo_check_swap\n"); *(unsigned char *)0x1000 = 0x0a; assert(pgfault_num==4); + + // 写入虚拟页 d 并检查页面故障数 cprintf("write Virt Page d in fifo_check_swap\n"); *(unsigned char *)0x4000 = 0x0d; assert(pgfault_num==4); + + // 写入虚拟页 b 并检查页面故障数 cprintf("write Virt Page b in fifo_check_swap\n"); *(unsigned char *)0x2000 = 0x0b; assert(pgfault_num==4); + + // 写入虚拟页 e 并检查页面故障数 cprintf("write Virt Page e in fifo_check_swap\n"); *(unsigned char *)0x5000 = 0x0e; assert(pgfault_num==5); + + // 再次写入虚拟页 b 并检查页面故障数 cprintf("write Virt Page b in fifo_check_swap\n"); *(unsigned char *)0x2000 = 0x0b; assert(pgfault_num==5); + + // 再次写入虚拟页 a 并检查页面故障数 cprintf("write Virt Page a in fifo_check_swap\n"); *(unsigned char *)0x1000 = 0x0a; assert(pgfault_num==6); + + // 再次写入虚拟页 b 并检查页面故障数 cprintf("write Virt Page b in fifo_check_swap\n"); *(unsigned char *)0x2000 = 0x0b; assert(pgfault_num==7); + + // 再次写入虚拟页 c 并检查页面故障数 cprintf("write Virt Page c in fifo_check_swap\n"); *(unsigned char *)0x3000 = 0x0c; assert(pgfault_num==8); + + // 再次写入虚拟页 d 并检查页面故障数 cprintf("write Virt Page d in fifo_check_swap\n"); *(unsigned char *)0x4000 = 0x0d; assert(pgfault_num==9); + + // 再次写入虚拟页 e 并检查页面故障数 cprintf("write Virt Page e in fifo_check_swap\n"); *(unsigned char *)0x5000 = 0x0e; assert(pgfault_num==10); + + // 再次写入虚拟页 a 并检查页面故障数 cprintf("write Virt Page a in fifo_check_swap\n"); assert(*(unsigned char *)0x1000 == 0x0a); *(unsigned char *)0x1000 = 0x0a; diff --git a/labcodes/lab4/kern/mm/vmm.c b/labcodes/lab4/kern/mm/vmm.c index b402145a5ce32819fc870c9486e66d1df4677a25..901c81c189a1e3f662173e6f29d185f85b96caea 100644 --- a/labcodes/lab4/kern/mm/vmm.c +++ b/labcodes/lab4/kern/mm/vmm.c @@ -7,29 +7,31 @@ #include #include #include -#include /* vmm design include two parts: mm_struct (mm) & vma_struct (vma) mm is the memory manager for the set of continuous virtual memory area which have the same PDT. vma is a continuous virtual memory area. There a linear link list for vma & a redblack link list for vma in mm. + 虚拟内存管理设计包括两个部分:mm_struct(mm)和 vma_struct(vma)。 + mm 是一组具有相同页目录表(PDT)的连续虚拟内存区域的内存管理器。 + vma 是一个连续的虚拟内存区域。在 mm 中有一个用于 vma 的线性链表和一个用于 vma 的红黑树链表。 --------------- - mm related functions: - golbal functions + mm related functions: 与 mm 相关的函数: + golbal functions 全局函数 struct mm_struct * mm_create(void) void mm_destroy(struct mm_struct *mm) int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) -------------- - vma related functions: - global functions + vma related functions: 与 vma 相关的函数: + global functions 全局函数 struct vma_struct * vma_create (uintptr_t vm_start, uintptr_t vm_end,...) void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) - local functions + local functions 本地函数 inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) --------------- - check correctness functions + check correctness functions 确认正确性函数 void check_vmm(void); void check_vma_struct(void); void check_pgfault(void); @@ -40,58 +42,100 @@ static void check_vma_struct(void); static void check_pgfault(void); // mm_create - alloc a mm_struct & initialize it. +/** + * 创建并初始化一个内存管理结构体 + * + * 此函数负责分配并初始化一个`mm_struct`结构体,该结构体用于描述一个进程的内存空间状态 + * 它包括内存映射列表、页目录、映射缓存等重要信息 + * + * @return 分配并初始化后的`mm_struct`结构体指针,如果分配失败则返回NULL + */ struct mm_struct * mm_create(void) { + // 分配一个mm_struct结构体的空间 struct mm_struct *mm = kmalloc(sizeof(struct mm_struct)); - + // 检查是否成功分配了内存 if (mm != NULL) { + // 初始化内存映射列表 list_init(&(mm->mmap_list)); + // 设置映射缓存为NULL,表示尚未缓存任何映射 mm->mmap_cache = NULL; + // 设置页目录为NULL,表示尚未分配页目录 mm->pgdir = NULL; + // 初始化映射计数为0,表示尚未创建任何内存映射 mm->map_count = 0; - + // 如果交换空间初始化成功,则为当前内存管理结构体进行交换空间初始化 if (swap_init_ok) swap_init_mm(mm); else mm->sm_priv = NULL; } + // 返回分配并初始化后的内存管理结构体指针 return mm; } // vma_create - alloc a vma_struct & initialize it. (addr range: vm_start~vm_end) +/** + * 创建并初始化一个虚拟内存区域(VMA)结构体。 + * + * @param vm_start 虚拟内存区域的起始地址。 + * @param vm_end 虚拟内存区域的结束地址。 + * @param vm_flags 虚拟内存区域的标志,表示内存区域的权限和特性。 + * + * @return 返回指向新创建的vma_struct结构体的指针,如果内存分配失败,则返回NULL。 + */ struct vma_struct * vma_create(uintptr_t vm_start, uintptr_t vm_end, uint32_t vm_flags) { + // 分配vma_struct结构体所需的内存空间 struct vma_struct *vma = kmalloc(sizeof(struct vma_struct)); - + // 检查内存是否成功分配 if (vma != NULL) { + // 初始化vma_struct的成员变量 vma->vm_start = vm_start; vma->vm_end = vm_end; vma->vm_flags = vm_flags; } + // 返回指向新创建的vma_struct结构体的指针,或在内存分配失败时返回NULL return vma; } // find_vma - find a vma (vma->vm_start <= addr <= vma_vm_end) +/** + * 查找给定地址对应的虚拟内存区域(VMA) + * + * @param mm 进程的内存描述符,包含所有VMA的列表 + * @param addr 要查找的地址 + * @return 返回包含给定地址的VMA指针,如果未找到则返回NULL + * + * 此函数首先检查mmap_cache是否包含所需的VMA,以加速查找过程 + * 如果mmap_cache未命中,则遍历VMA列表,直到找到包含给定地址的VMA或确定不存在这样的VMA + * 如果找到了合适的VMA,它将更新mmap_cache以供后续查找使用 + */ struct vma_struct * find_vma(struct mm_struct *mm, uintptr_t addr) { - struct vma_struct *vma = NULL; - if (mm != NULL) { + struct vma_struct *vma = NULL;// 初始化VMA指针为NULL + if (mm != NULL) {// 检查传入的内存描述符是否有效 + // 检查mmap_cache是否包含所需的VMA vma = mm->mmap_cache; if (!(vma != NULL && vma->vm_start <= addr && vma->vm_end > addr)) { - bool found = 0; + // 如果mmap_cache未命中,则开始遍历VMA列表 + bool found = 0;// 初始化找到标志为0 + // 获取VMA列表的头指针 list_entry_t *list = &(mm->mmap_list), *le = list; - while ((le = list_next(le)) != list) { - vma = le2vma(le, list_link); + while ((le = list_next(le)) != list) { // 遍历VMA列表 + vma = le2vma(le, list_link);// 将链表项转换为VMA结构 + // 检查当前VMA是否包含给定地址 if (vma->vm_start<=addr && addr < vma->vm_end) { - found = 1; - break; + found = 1;// 找到合适的VMA + break;// 结束循环 } } - if (!found) { - vma = NULL; + if (!found) {// 如果未找到合适的VMA + vma = NULL;// 将VMA指针设置为NULL } } + // 如果找到了合适的VMA,更新mmap_cache if (vma != NULL) { - mm->mmap_cache = vma; + mm->mmap_cache = vma;// 更新mmap_cache以加速后续查找 } } return vma; @@ -99,179 +143,265 @@ find_vma(struct mm_struct *mm, uintptr_t addr) { // check_vma_overlap - check if vma1 overlaps vma2 ? +/** + * 检查两个虚拟内存区域(VMA)是否有重叠 + * + * 此函数的目的是确保给定的两个虚拟内存区域(VMA)在内存中是正确排序且没有重叠的 + * 它通过断言来检查以下条件: + * 1. 前一个VMA的起始地址小于其结束地址,确保前一个VMA的地址范围是有效的 + * 2. 前一个VMA的结束地址小于等于下一个VMA的起始地址,确保两个VMA之间没有重叠 + * 3. 后一个VMA的起始地址小于其结束地址,确保后一个VMA的地址范围是有效的 + * + * @param prev 指向前一个虚拟内存区域(VMA)的结构体指针 + * @param next 指向后一个虚拟内存区域(VMA)的结构体指针 + */ static inline void check_vma_overlap(struct vma_struct *prev, struct vma_struct *next) { - assert(prev->vm_start < prev->vm_end); - assert(prev->vm_end <= next->vm_start); - assert(next->vm_start < next->vm_end); + assert(prev->vm_start < prev->vm_end);// 确保前一个VMA的地址范围是有效的 + assert(prev->vm_end <= next->vm_start);// 确保两个VMA之间没有重叠 + assert(next->vm_start < next->vm_end);// 确保后一个VMA的地址范围是有效的 } // insert_vma_struct -insert vma in mm's list link +/** + * 将VMA(虚拟内存区域)结构插入到内存描述符的链表中。 + * + * 此函数负责将新的VMA结构插入到进程的VMA链表中的正确位置,确保VMA结构按起始地址升序排列。 + * 它还检查与相邻VMA结构的重叠情况,以确保内存段管理的一致性。 + * + * @param mm 指向内存描述符结构 `struct mm_struct` 的指针,表示一个进程的内存空间。 + * @param vma 指向要插入的VMA结构 `struct vma_struct` 的指针,描述一个内存区域。 + */ void insert_vma_struct(struct mm_struct *mm, struct vma_struct *vma) { + // 断言VMA结构的起始地址小于结束地址,确保VMA结构的有效性。 assert(vma->vm_start < vma->vm_end); + // 指向内存描述符中的VMA链表。 list_entry_t *list = &(mm->mmap_list); + // 遍历链表以找到新VMA结构的正确插入位置。 list_entry_t *le_prev = list, *le_next; list_entry_t *le = list; + // 遍历链表以找到新VMA结构的正确插入位置 while ((le = list_next(le)) != list) { struct vma_struct *mmap_prev = le2vma(le, list_link); + // 如果当前VMA的起始地址大于新VMA的起始地址,则跳出循环 if (mmap_prev->vm_start > vma->vm_start) { break; } le_prev = le; } - + // 获取下一个链表项 le_next = list_next(le_prev); /* check overlap */ + // 检查前一个VMA结构是否与新VMA结构重叠 if (le_prev != list) { check_vma_overlap(le2vma(le_prev, list_link), vma); } + // 检查下一个VMA结构是否与新VMA结构重叠 if (le_next != list) { check_vma_overlap(vma, le2vma(le_next, list_link)); } - + // 设置VMA结构所属的内存描述符 vma->vm_mm = mm; + // 将新VMA结构插入链表 list_add_after(le_prev, &(vma->list_link)); - + // 增加内存描述符中的映射计数 mm->map_count ++; } // mm_destroy - free mm and mm internal fields +/** + * mm_destroy - 销毁一个内存管理结构(mm_struct)及其关联的虚拟内存区域(VMA) + * @mm: 指向要销毁的内存管理结构的指针 + * + * 此函数遍历并销毁与内存管理结构(mm_struct)关联的所有虚拟内存区域(VMA), + * 然后释放内存管理结构本身所占用的内存。这样做是为了确保在销毁内存管理结构之前, + * 所有相关的资源都被正确地释放。 + */ void mm_destroy(struct mm_struct *mm) { - + // 获取内存映射列表的头指针 list_entry_t *list = &(mm->mmap_list), *le; + // 遍历内存映射列表,直到回到起点 while ((le = list_next(list)) != list) { + // 从列表中删除当前虚拟内存区域的项 list_del(le); - kfree(le2vma(le, list_link)); //kfree vma + // 释放虚拟内存区域结构的内存 + kfree(le2vma(le, list_link),sizeof(struct vma_struct)); //kfree vma } - kfree(mm); //kfree mm + // 释放内存管理结构本身的内存 + kfree(mm, sizeof(struct mm_struct)); //kfree mm + // 将指针设置为NULL,表示该结构已被销毁 mm=NULL; } // vmm_init - initialize virtual memory management // - now just call check_vmm to check correctness of vmm +/** + * 初始化虚拟内存管理(VMM)系统。 + * 此函数通过执行一系列检查来确保VMM系统可以正确初始化和运行。 + */ void vmm_init(void) { + // 检查VMM系统的状态和环境,以确保其能够正常工作。 check_vmm(); } // check_vmm - check correctness of vmm +/** + * 检查虚拟内存管理(VMM)的完整性 + * + * 此函数的目的是确保虚拟内存管理系统的正确性通过检查内存区域结构(VMA)、页面故障处理以及免费页面计数的 consistency 来实现 + * 它首先保存当前的免费页面数量,然后执行与 VMA 和页面故障相关的检查,最后确认免费页面数量未发生变化 + * 这是为了确保在检查过程中,内存状态没有因为错误或意外的修改而改变,从而验证内存管理的正确性 + */ static void check_vmm(void) { + // 保存当前的免费页面数量,用于后续的 consistency 检查 size_t nr_free_pages_store = nr_free_pages(); - + // 检查虚拟内存区域(VMA)结构的正确性 check_vma_struct(); + // 检查页面故障处理的正确性 check_pgfault(); - + // 确保在检查过程中免费页面数量未发生变化,表明内存管理操作是正确的 + assert(nr_free_pages_store == nr_free_pages()); + // 如果所有检查都通过,输出成功信息 cprintf("check_vmm() succeeded.\n"); } +//测试虚拟内存区域(VMA)结构的创建、插入和查找功能。 static void check_vma_struct(void) { + // 记录当前空闲页面数量 size_t nr_free_pages_store = nr_free_pages(); - struct mm_struct *mm = mm_create(); - assert(mm != NULL); + struct mm_struct *mm = mm_create();// 创建内存管理结构 mm + assert(mm != NULL);// 确保 mm 不为 NULL - int step1 = 10, step2 = step1 * 10; + int step1 = 10, step2 = step1 * 10;// 定义两个步骤的步数 int i; - for (i = step1; i >= 1; i --) { + for (i = step1; i >= 1; i --) {// 第一步:创建并插入10个VMA + // 创建 VMA 结构 struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); - assert(vma != NULL); - insert_vma_struct(mm, vma); + assert(vma != NULL);// 确保 VMA 不为 NULL + insert_vma_struct(mm, vma); //将 VMA 插入到 mm 中 } - for (i = step1 + 1; i <= step2; i ++) { + for (i = step1 + 1; i <= step2; i ++) {// 第二步:创建并插入90个VMA + // 创建 VMA 结构 struct vma_struct *vma = vma_create(i * 5, i * 5 + 2, 0); - assert(vma != NULL); - insert_vma_struct(mm, vma); + assert(vma != NULL);// 确保 VMA 不为 NULL + insert_vma_struct(mm, vma);// 将 VMA 插入到 mm 中 } - + // 获取 VMA 链表的第一个节点 list_entry_t *le = list_next(&(mm->mmap_list)); - for (i = 1; i <= step2; i ++) { - assert(le != &(mm->mmap_list)); - struct vma_struct *mmap = le2vma(le, list_link); + for (i = 1; i <= step2; i ++) {// 验证插入顺序 + assert(le != &(mm->mmap_list));// 确保节点不为空 + struct vma_struct *mmap = le2vma(le, list_link);// 将链表节点转换为 VMA 结构 + // 确认 VMA 的起始和结束地址 assert(mmap->vm_start == i * 5 && mmap->vm_end == i * 5 + 2); - le = list_next(le); + le = list_next(le);// 移动到下一个节点 } - for (i = 5; i <= 5 * step2; i +=5) { - struct vma_struct *vma1 = find_vma(mm, i); - assert(vma1 != NULL); + for (i = 5; i <= 5 * step2; i +=5) {// 查找特定地址范围内的 VMA + struct vma_struct *vma1 = find_vma(mm, i);// 查找地址 i 处的 VMA + assert(vma1 != NULL);// 确保找到 VMA + // 查找地址 i + 1 处的 VMA struct vma_struct *vma2 = find_vma(mm, i+1); - assert(vma2 != NULL); + assert(vma2 != NULL);// 确保找到 VMA + // 查找地址 i + 2 处的 VMA struct vma_struct *vma3 = find_vma(mm, i+2); - assert(vma3 == NULL); + assert(vma3 == NULL);// 确保未找到 VMA + // 查找地址 i + 3 处的 VMA struct vma_struct *vma4 = find_vma(mm, i+3); - assert(vma4 == NULL); + assert(vma4 == NULL);// 确保未找到 VMA + // 查找地址 i + 4 处的 VMA struct vma_struct *vma5 = find_vma(mm, i+4); - assert(vma5 == NULL); - + assert(vma5 == NULL);// 确保未找到 VMA + // 确认 VMA1 的起始和结束地址 assert(vma1->vm_start == i && vma1->vm_end == i + 2); + // 确认 VMA2 的起始和结束地址 assert(vma2->vm_start == i && vma2->vm_end == i + 2); } - + // 检查小于5的地址范围内是否存在 VMA for (i =4; i>=0; i--) { + // 查找地址 i 处的 VMA struct vma_struct *vma_below_5= find_vma(mm,i); - if (vma_below_5 != NULL ) { + if (vma_below_5 != NULL ) {// 如果找到 VMA cprintf("vma_below_5: i %x, start %x, end %x\n",i, vma_below_5->vm_start, vma_below_5->vm_end); } - assert(vma_below_5 == NULL); + assert(vma_below_5 == NULL);// 确保未找到 VMA } - mm_destroy(mm); + mm_destroy(mm);// 销毁 mm 结构 + // 确保释放的页面数量与初始记录一致 + assert(nr_free_pages_store == nr_free_pages()); + // 输出成功信息 cprintf("check_vma_struct() succeeded!\n"); } struct mm_struct *check_mm_struct; // check_pgfault - check correctness of pgfault handler +// 检查页故障处理的正确性 static void check_pgfault(void) { + // 保存当前空闲页面的数量,用于后续检查 size_t nr_free_pages_store = nr_free_pages(); - + // 创建内存管理结构体 check_mm_struct = mm_create(); + // 确保内存管理结构体创建成功 assert(check_mm_struct != NULL); - + // 将新创建的内存管理结构体赋值给局部变量mm struct mm_struct *mm = check_mm_struct; + // 将引导程序的页目录复制到新创建的内存管理结构体中 pde_t *pgdir = mm->pgdir = boot_pgdir; + // 确保页目录的第0项是空的 assert(pgdir[0] == 0); - + // 创建一个虚拟内存区域结构体,具有写权限 struct vma_struct *vma = vma_create(0, PTSIZE, VM_WRITE); + // 确保虚拟内存区域结构体创建成功 assert(vma != NULL); - + // 将虚拟内存区域结构体插入到内存管理结构体中 insert_vma_struct(mm, vma); - + // 定义一个地址,用于访问虚拟内存 uintptr_t addr = 0x100; + // 确保通过该地址可以找到之前插入的虚拟内存区域 assert(find_vma(mm, addr) == vma); - + // 初始化一个累加器,用于校验写入的数据 int i, sum = 0; + // 写入数据到虚拟内存,并累加 for (i = 0; i < 100; i ++) { *(char *)(addr + i) = i; sum += i; } + // 读取虚拟内存中的数据,并减去,最终结果应为0 for (i = 0; i < 100; i ++) { sum -= *(char *)(addr + i); } + // 确保累加器的值为0,证明数据读写正确 assert(sum == 0); - + // 移除页目录中的相应页面 page_remove(pgdir, ROUNDDOWN(addr, PGSIZE)); + // 释放第0项页目录对应的页面 free_page(pde2page(pgdir[0])); + // 将页目录的第0项设置为空 pgdir[0] = 0; - + // 将内存管理结构体中的页目录设置为空 mm->pgdir = NULL; + // 销毁内存管理结构体 mm_destroy(mm); + // 将检查用的内存管理结构体设置为空 check_mm_struct = NULL; - + // 确保空闲页面的数量没有变化,证明内存管理正确 assert(nr_free_pages_store == nr_free_pages()); - + // 打印成功信息 cprintf("check_pgfault() succeeded!\n"); } //page fault number @@ -298,35 +428,51 @@ volatile unsigned int pgfault_num=0; * -- The U/S flag (bit 2) indicates whether the processor was executing at user mode (1) * or supervisor mode (0) at the time of the exception. */ +/** + * 处理给定内存管理上下文的页面错误。 + * + * @param mm 指向内存管理结构的指针。 + * @param error_code 页面错误发生时硬件提供的错误代码。 + * @param addr 引发页面错误的线性地址。 + * + * @return 成功返回0,失败返回负错误码。 + */ int do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { - int ret = -E_INVAL; + int ret = -E_INVAL;// 初始化返回值为无效错误 //try to find a vma which include addr + // 尝试找到包含 addr 的 vma struct vma_struct *vma = find_vma(mm, addr); - pgfault_num++; + pgfault_num++;// 增加页面错误计数 + // 检查 addr 是否在 mm 的 vma 范围内 //If the addr is in the range of a mm's vma? if (vma == NULL || vma->vm_start > addr) { cprintf("not valid addr %x, and can not find it in vma\n", addr); - goto failed; + goto failed;// 跳转到错误处理部分 } //check the error_code + // 检查错误代码 switch (error_code & 3) { default: + /* 默认错误代码标志:3 (W/R=1, P=1): 写操作,存在 */ /* error code flag : default is 3 ( W/R=1, P=1): write, present */ case 2: /* error code flag : (W/R=1, P=0): write, not present */ + /* 错误代码标志:(W/R=1, P=0): 写操作,不存在 */ if (!(vma->vm_flags & VM_WRITE)) { cprintf("do_pgfault failed: error code flag = write AND not present, but the addr's vma cannot write\n"); - goto failed; + goto failed;// 跳转到错误处理部分 } break; case 1: /* error code flag : (W/R=0, P=1): read, present */ + /* 错误代码标志:(W/R=0, P=1): 读操作,存在 */ cprintf("do_pgfault failed: error code flag = read AND present\n"); - goto failed; + goto failed;// 跳转到错误处理部分 case 0: /* error code flag : (W/R=0, P=0): read, not present */ + /* 错误代码标志:(W/R=0, P=0): 读操作,不存在 */ if (!(vma->vm_flags & (VM_READ | VM_EXEC))) { cprintf("do_pgfault failed: error code flag = read AND not present, but the addr's vma cannot read or exec\n"); - goto failed; + goto failed;// 跳转到错误处理部分 } } /* IF (write an existed addr ) OR @@ -335,13 +481,18 @@ do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { * THEN * continue process */ - uint32_t perm = PTE_U; + /* 如果 (写入已存在的地址) 或 + * (写入不存在的地址且地址可写) 或 + * (读取不存在的地址且地址可读) + * 则继续处理 + */ + uint32_t perm = PTE_U;// 初始化权限标志为用户可访问 if (vma->vm_flags & VM_WRITE) { - perm |= PTE_W; + perm |= PTE_W;// 如果 vma 可写,则设置写权限 } - addr = ROUNDDOWN(addr, PGSIZE); + addr = ROUNDDOWN(addr, PGSIZE);// 将地址对齐到页边界 - ret = -E_NO_MEM; + ret = -E_NO_MEM;// 初始化返回值为内存不足错误 pte_t *ptep=NULL; /*LAB3 EXERCISE 1: YOUR CODE @@ -361,12 +512,31 @@ do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { * mm->pgdir : the PDT of these vma * */ + /* LAB3 练习 1: 你的代码 + * 可能需要帮助的注释,以下注释可以帮助你完成代码 + * + * 一些有用的宏和定义,你可以在下面的实现中使用它们。 + * 宏或函数: + * get_pte : 获取一个页表项并返回该页表项的内核虚拟地址 + * 如果包含该页表项的页表不存在,则分配一个页表 (注意第三个参数 '1') + * pgdir_alloc_page : 调用 alloc_page 和 page_insert 函数分配一页大小的内存并设置 + * 地址映射 pa<--->la 与页目录表 pgdir + * 定义: + * VM_WRITE : 如果 vma->vm_flags & VM_WRITE == 1/0,则 vma 是可写/不可写的 + * PTE_W 0x002 // 页表/目录项标志位:可写 + * PTE_U 0x004 // 页表/目录项标志位:用户可访问 + * 变量: + * mm->pgdir : 这些 vma 的页目录表 + * + */ #if 0 /*LAB3 EXERCISE 1: YOUR CODE*/ + /* LAB3 练习 1: 你的代码*/ ptep = ??? //(1) try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + // (1) 尝试找到一个页表项,如果该页表项的页表不存在,则创建一个页表。 if (*ptep == 0) { //(2) if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr - + // (2) 如果物理地址不存在,则分配一页内存并映射物理地址与逻辑地址 } else { /*LAB3 EXERCISE 2: YOUR CODE @@ -379,22 +549,71 @@ do_pgfault(struct mm_struct *mm, uint32_t error_code, uintptr_t addr) { * find the addr of disk page, read the content of disk page into this memroy page * page_insert : build the map of phy addr of an Page with the linear addr la * swap_map_swappable : set the page swappable + */ + /* LAB3 练习 2: 你的代码 + * 现在我们认为这个页表项是一个交换项,我们应该从磁盘加载数据到一个具有物理地址的页面, + * 并映射物理地址与逻辑地址,触发交换管理器记录该页面的访问情况。 + * + * 一些有用的宏和定义,你可以在下面的实现中使用它们。 + * 宏或函数: + * swap_in(mm, addr, &page) : 分配一个内存页面,然后根据 addr 的页表项中的交换项, + * 找到磁盘页面的地址,将磁盘页面的内容读入该内存页面 + * page_insert : 根据 mm 和 addr 设置物理地址与逻辑地址的映射 + * swap_map_swappable : 设置页面可交换 */ if(swap_init_ok) { struct Page *page=NULL; //(1)According to the mm AND addr, try to load the content of right disk page + // (1) 根据 mm 和 addr,尝试从正确的磁盘页面加载内容到 page 管理的内存中 // into the memory which page managed. + // (2) 根据 mm、addr 和 page,设置物理地址与逻辑地址的映射 //(2) According to the mm, addr AND page, setup the map of phy addr <---> logical addr //(3) make the page swappable. + // (3) 使页面可交换 } else { cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); - goto failed; + goto failed;// 跳转到错误处理部分 } } + #endif - ret = 0; + // try to find a pte, if pte's PT(Page Table) isn't existed, then create a PT. + // (notice the 3th parameter '1') + // 尝试找到一个页表项 pte,如果包含该 pte 的页表不存在,则创建一个页表。 + // 注意第三个参数 '1' 表示如果需要,可以创建新的页表。 + if ((ptep = get_pte(mm->pgdir, addr, 1)) == NULL) { + cprintf("get_pte in do_pgfault failed\n");// 输出错误信息 + goto failed;// 跳转到错误处理部分 + } + // 如果页表项 pte 的物理地址不存在,则分配一页内存并映射物理地址与逻辑地址 + if (*ptep == 0) { // if the phy addr isn't exist, then alloc a page & map the phy addr with logical addr + if (pgdir_alloc_page(mm->pgdir, addr, perm) == NULL) { + cprintf("pgdir_alloc_page in do_pgfault failed\n");// 输出错误信息 + goto failed;// 跳转到错误处理部分 + } + } + else { // if this pte is a swap entry, then load data from disk to a page with phy addr + // and call page_insert to map the phy addr with logical addr + // 如果页表项 pte 是一个交换项,则从磁盘加载数据到 + //一个具有物理地址的页面,并映射物理地址与逻辑地址 + if(swap_init_ok) {// 检查交换初始化是否成功 + struct Page *page=NULL;// 声明一个页面指针 + if ((ret = swap_in(mm, addr, &page)) != 0) { + cprintf("swap_in in do_pgfault failed\n"); + goto failed; + } + page_insert(mm->pgdir, page, addr, perm);// 设置物理地址与逻辑地址的映射 + swap_map_swappable(mm, addr, page, 1);// 设置页面可交换 + page->pra_vaddr = addr;// 记录页面的虚拟地址 + } + else { + cprintf("no swap_init_ok but ptep is %x, failed\n",*ptep); + goto failed;// 跳转到错误处理部分 + } + } + ret = 0;// 设置返回值为成功 failed: - return ret; + return ret;// 返回结果 } diff --git a/labcodes/lab4/kern/process/proc.c b/labcodes/lab4/kern/process/proc.c index 7d57c6507a41e4bdb1c68f51d226976f51043a36..6050d41162e93289a3eaf74a4aea77dcc2dc82d8 100644 --- a/labcodes/lab4/kern/process/proc.c +++ b/labcodes/lab4/kern/process/proc.c @@ -102,6 +102,18 @@ alloc_proc(void) { * uint32_t flags; // Process flag * char name[PROC_NAME_LEN + 1]; // Process name */ + proc->state = PROC_UNINIT; // 初始状态为未初始化 + proc->pid = -1; // 初始进程ID为-1 + proc->runs = 0; // 初始运行次数为0 + proc->kstack = 0; // 初始内核栈指针为0 + proc->need_resched = 0; // 初始不需要重新调度 + proc->parent = NULL; // 初始父进程指针为NULL + proc->mm = NULL; // 初始内存管理结构指针为NULL + memset(&proc->context, 0, sizeof(struct context)); // 初始化上下文切换信息 + proc->tf = NULL; // 初始中断陷阱帧指针为NULL + proc->cr3 = boot_cr3; // 初始CR3寄存器值为0 + proc->flags = 0; // 初始进程标志为0 + memset(proc->name, 0, PROC_NAME_LEN); // 初始进程名称为空字符串 } return proc; } @@ -122,23 +134,45 @@ get_proc_name(struct proc_struct *proc) { } // get_pid - alloc a unique pid for process +/** + * 安全地获取下一个进程ID。 + * + * 此函数通过遍历进程列表来寻找一个未被使用的进程ID,以确保新分配的ID不会与现有进程冲突。 + * 它使用静态变量来跟踪最后一个分配的PID和下一个安全PID,以提高效率并避免PID冲突。 + * + * @return 返回一个未被使用的进程ID。 + */ static int get_pid(void) { + // 确保PID的最大值大于系统中允许的最大进程数,以避免PID不足的情况。 static_assert(MAX_PID > MAX_PROCESS); + + // 定义一个指向进程结构的指针,用于遍历进程列表。 struct proc_struct *proc; + // 初始化列表指针,从进程列表的头部开始。 list_entry_t *list = &proc_list, *le; + // 定义静态变量,next_safe用于记录下一个安全的PID值,last_pid用于记录上一个分配的PID。 static int next_safe = MAX_PID, last_pid = MAX_PID; + + // 尝试递增last_pid以查找下一个可用的PID,如果超过最大值则重置为1。 if (++ last_pid >= MAX_PID) { last_pid = 1; goto inside; } + + // 如果当前的last_pid大于等于next_safe,表示需要重新计算下一个安全的PID。 if (last_pid >= next_safe) { inside: next_safe = MAX_PID; repeat: + // 从进程列表的头部开始遍历。 le = list; while ((le = list_next(le)) != list) { + + // 将列表项转换为进程结构。 proc = le2proc(le, list_link); + + // 如果找到相同PID的进程,表示当前last_pid已被使用,需要继续寻找下一个可用的PID。 if (proc->pid == last_pid) { if (++ last_pid >= next_safe) { if (last_pid >= MAX_PID) { @@ -148,11 +182,14 @@ get_pid(void) { goto repeat; } } + + // 如果找到一个更大的PID,更新next_safe为当前进程的PID,以确保找到的PID是安全的。 else if (proc->pid > last_pid && next_safe > proc->pid) { next_safe = proc->pid; } } } + // 返回找到的可用PID。 return last_pid; } @@ -183,8 +220,17 @@ forkret(void) { } // hash_proc - add proc into proc hash_list +/** + * 将进程结构体添加到哈希表中 + * + * 当需要在哈希表中添加一个进程时,调用此函数将进程结构体中的哈希链接添加到相应的哈希表链表中 + * 这有助于在需要时快速查找进程 + * + * @param proc 指向进程结构体的指针,表示要添加到哈希表的进程 + */ static void hash_proc(struct proc_struct *proc) { + // 根据进程的PID计算哈希值,并将进程添加到相应哈希链表的末尾 list_add(hash_list + pid_hashfn(proc->pid), &(proc->hash_link)); } @@ -219,13 +265,25 @@ kernel_thread(int (*fn)(void *), void *arg, uint32_t clone_flags) { } // setup_kstack - alloc pages with size KSTACKPAGE as process kernel stack +/** + * 设置进程的内核栈 + * + * @param proc 指向进程结构的指针,用于初始化该进程的内核栈 + * @return 0表示成功,-E_NO_MEM表示内存分配失败 + * + * 此函数通过分配一页内存用作进程的内核栈,并将该内存页的虚拟地址设置为进程的内核栈地址 + * 如果内存分配成功,则返回0;如果内存分配失败,则返回-E_NO_MEM + */ static int setup_kstack(struct proc_struct *proc) { + // 分配KSTACKPAGE页内存用作内核栈 struct Page *page = alloc_pages(KSTACKPAGE); if (page != NULL) { + // 如果内存分配成功,将内存页的虚拟地址设置为进程的内核栈地址 proc->kstack = (uintptr_t)page2kva(page); return 0; } + // 如果内存分配失败,返回错误码 return -E_NO_MEM; } @@ -237,6 +295,17 @@ put_kstack(struct proc_struct *proc) { // copy_mm - process "proc" duplicate OR share process "current"'s mm according clone_flags // - if clone_flags & CLONE_VM, then "share" ; else "duplicate" +/** + * 复制内存管理信息到新进程。 + * + * 该函数负责将当前进程的内存管理信息复制到新进程中。 + * 它是进程创建的关键部分,确保新进程拥有独立的内存空间。 + * + * @param clone_flags 指定如何克隆进程的标志位,影响内存和其他资源的复制方式。 + * @param proc 指向新进程的进程结构体指针,用于存储复制的内存管理信息。 + * + * 断言当前进程没有内存管理信息。 + */ static int copy_mm(uint32_t clone_flags, struct proc_struct *proc) { assert(current->mm == NULL); @@ -246,15 +315,33 @@ copy_mm(uint32_t clone_flags, struct proc_struct *proc) { // copy_thread - setup the trapframe on the process's kernel stack top and // - setup the kernel entry point and stack of process +/** + * 复制当前线程的上下文到新进程。 + * + * 该函数初始化新进程的陷阱帧和上下文,使其作为当前线程的子进程执行。主要操作包括设置初始执行环境、栈指针,并启用子进程的中断标志。 + * + * @param proc 指向新进程的进程结构体,用于存储初始化后的陷阱帧和上下文信息。 + * @param esp 新进程的栈指针,表示新进程栈的初始位置。 + * @param tf 指向当前线程的陷阱帧结构体,用于将当前线程的执行状态复制到新进程中。 + */ static void copy_thread(struct proc_struct *proc, uintptr_t esp, struct trapframe *tf) { + // 初始化新进程的陷阱帧,位于其内核栈的顶部 proc->tf = (struct trapframe *)(proc->kstack + KSTACKSIZE) - 1; + + // 将当前线程的陷阱帧内容复制到新进程的陷阱帧 *(proc->tf) = *tf; + + // 设置子进程的返回值为0,表示fork成功 proc->tf->tf_regs.reg_eax = 0; + // 设置子进程的栈指针为指定的esp位置 proc->tf->tf_esp = esp; + // 启用子进程的中断标志 proc->tf->tf_eflags |= FL_IF; + // 设置子进程的初始指令指针为forkret函数的地址 proc->context.eip = (uintptr_t)forkret; + // 设置子进程的栈指针为其陷阱帧的地址 proc->context.esp = (uintptr_t)(proc->tf); } @@ -296,6 +383,43 @@ do_fork(uint32_t clone_flags, uintptr_t stack, struct trapframe *tf) { // 5. insert proc_struct into hash_list && proc_list // 6. call wakeup_proc to make the new child process RUNNABLE // 7. set ret vaule using child proc's pid + //调用alloc_proc,首先获得一块用户信息块 + if((proc = alloc_proc()) == NULL){ + goto fork_out; + } + + proc->parent = current; + + //为进程分配一个内核栈 + ret = setup_kstack(proc); + if (ret != 0) { + goto bad_fork_cleanup_proc; + } + + //复制原进程的内存管理信息到新进程 + ret = copy_mm(clone_flags, proc); + if (ret != 0) { + goto bad_fork_cleanup_kstack; + } + + //复制原进程上下文到新进程 + copy_thread(proc, stack, tf); + + bool intr_flag; + local_intr_save(intr_flag) + { + //为新进程分配一个pid + pid = get_pid(); + + //将新进程插入到进程列表 + hash_proc(proc); + list_add(&proc_init, &(proc->list_link)); + nr_process ++; + + } + local_intr_restore(intr_flag); + wakeup_proc(proc); + ret = proc->pid; fork_out: return ret; diff --git a/labcodes/lab4/kern/trap/trap.c b/labcodes/lab4/kern/trap/trap.c index 90f266e731c697bdd7a31d9a0e2169bc0d508b23..ce2599e65e19195b23c59437518570163123ed90 100644 --- a/labcodes/lab4/kern/trap/trap.c +++ b/labcodes/lab4/kern/trap/trap.c @@ -17,7 +17,7 @@ static void print_ticks() { cprintf("%d ticks\n",TICK_NUM); #ifdef DEBUG_GRADE cprintf("End of Test.\n"); - panic("EOT: kernel seems ok."); + panic("EOT: kernel seems ok.");//panic 是一个用于处理内核崩溃的函数,它会打印出错误信息并导致系统停止运行。 #endif } @@ -27,8 +27,11 @@ static void print_ticks() { * Must be built at run time because shifted function addresses can't * be represented in relocation records. * */ +//这行代码定义了一个名为 idt 的静态数组,其类型为 struct gatedesc,长度为 256。 +//gatedesc 通常表示中断门描述符,包含有关中断的各种信息 static struct gatedesc idt[256] = {{0}}; +//静态变量 idt_pd,它的类型为 struct pseudodesc。这个结构通常用于描述 IDT 的位置和大小,以便 CPU 知道如何加载 IDT。 static struct pseudodesc idt_pd = { sizeof(idt) - 1, (uintptr_t)idt }; @@ -48,6 +51,21 @@ idt_init(void) { * You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more. * Notice: the argument of lidt is idt_pd. try to find it! */ + + extern uintptr_t __vectors[];//声明了一个外部数组 __vectors,该数组存储中断服务例程(ISR)的地址。 + int i; + for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i ++) { + SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL); + } + //宏用于配置每个 IDT 条目.0 表示最高特权级(内核级)GD_KTEXT: 指向内核代码段的选择子,确保 ISR 在内核代码段中执行。 + //__vectors[i]: 对应中断的 ISR 地址,DPL_KERNEL: 描述符特权级,表示该中断只能由内核级代码触发。 + // set for switch from user to kernel + //SETGATE 这行代码特别设置了 T_SWITCH_TOK(一个特定的中断向量,用于用户态到内核态的切换)的 IDT 条目。 + //DPL_USER 表示该中断可以由用户态代码触发 + SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER); + // load the IDT + //使用 lidt 指令将 IDT 描述符加载到 CPU 中 + lidt(&idt_pd); } static const char * @@ -74,10 +92,11 @@ trapname(int trapno) { "Machine-Check", "SIMD Floating-Point Exception" }; - + //如果 trapno 小于数组长度,则返回对应的异常名称。 if (trapno < sizeof(excnames)/sizeof(const char * const)) { return excnames[trapno]; } + //如果 trapno 在 IRQ_OFFSET 和 IRQ_OFFSET + 16 之间,表示它是一个硬件中断 if (trapno >= IRQ_OFFSET && trapno < IRQ_OFFSET + 16) { return "Hardware Interrupt"; } @@ -88,54 +107,67 @@ trapname(int trapno) { bool trap_in_kernel(struct trapframe *tf) { return (tf->tf_cs == (uint16_t)KERNEL_CS); + //函数通过检查 tf 中的 tf_cs 字段来判断当前处于哪个特权级,tf_cs 存储了当前代码段选择子的值 + //当 tf->tf_cs 等于 KERNEL_CS 时,表示陷阱发生在内核模式下 } - +//数组中的字符串分别表示 IA-32 架构中的不同标志位: static const char *IA32flags[] = { "CF", NULL, "PF", NULL, "AF", NULL, "ZF", "SF", "TF", "IF", "DF", "OF", NULL, NULL, "NT", NULL, "RF", "VM", "AC", "VIF", "VIP", "ID", NULL, NULL, }; - +//struct trapframe *tf,一个指向 trapframe 结构的指针,包含有关陷阱发生时的 CPU 状态的信息。 void print_trapframe(struct trapframe *tf) { - cprintf("trapframe at %p\n", tf); - print_regs(&tf->tf_regs); + cprintf("trapframe at %p\n", tf); //打印陷阱框架地址 + print_regs(&tf->tf_regs); //打印寄存器状态 + //打印数据段(DS)、扩展段(ES)、文件段(FS)、通用段(GS)的值。 cprintf(" ds 0x----%04x\n", tf->tf_ds); cprintf(" es 0x----%04x\n", tf->tf_es); cprintf(" fs 0x----%04x\n", tf->tf_fs); cprintf(" gs 0x----%04x\n", tf->tf_gs); + // 打印陷阱号(trap number)及其对应的名称,通过调用 trapname 函数获取。 cprintf(" trap 0x%08x %s\n", tf->tf_trapno, trapname(tf->tf_trapno)); - cprintf(" err 0x%08x\n", tf->tf_err); - cprintf(" eip 0x%08x\n", tf->tf_eip); - cprintf(" cs 0x----%04x\n", tf->tf_cs); - cprintf(" flag 0x%08x ", tf->tf_eflags); - + cprintf(" err 0x%08x\n", tf->tf_err);// 如果有错误代码,打印该字段的值。 + cprintf(" eip 0x%08x\n", tf->tf_eip);//打印当前执行的指令指针(EIP),指向出错或中断的指令。 + cprintf(" cs 0x----%04x\n", tf->tf_cs);//打印代码段寄存器(CS)的值。 + cprintf(" flag 0x%08x ", tf->tf_eflags);// 打印标志寄存器(EFLAGS)的值 + //使用循环遍历 IA32flags 数组,j 表示当前标志位的位掩码。 int i, j; for (i = 0, j = 1; i < sizeof(IA32flags) / sizeof(IA32flags[0]); i ++, j <<= 1) { if ((tf->tf_eflags & j) && IA32flags[i] != NULL) { cprintf("%s,", IA32flags[i]); } } + //通过位掩码 FL_IOPL_MASK 获取和打印当前的 I/O 特权级别。 cprintf("IOPL=%d\n", (tf->tf_eflags & FL_IOPL_MASK) >> 12); - + //如果陷阱不是在内核中发生的(通过 trap_in_kernel 判断), + //则打印栈指针(ESP)和栈段(SS)寄存器的值。 if (!trap_in_kernel(tf)) { cprintf(" esp 0x%08x\n", tf->tf_esp); cprintf(" ss 0x----%04x\n", tf->tf_ss); } } - +//定义了一个名为 print_regs 的函数, +//打印出存储在 struct pushregs 结构体中的寄存器值。 void print_regs(struct pushregs *regs) { cprintf(" edi 0x%08x\n", regs->reg_edi); cprintf(" esi 0x%08x\n", regs->reg_esi); cprintf(" ebp 0x%08x\n", regs->reg_ebp); - cprintf(" oesp 0x%08x\n", regs->reg_oesp); + cprintf(" oesp 0x%08x\n", regs->reg_oesp);//打印旧的栈指针(OESP),这个寄存器通常在陷阱或中断发生时用于记录上一个栈指针。 cprintf(" ebx 0x%08x\n", regs->reg_ebx); cprintf(" edx 0x%08x\n", regs->reg_edx); cprintf(" ecx 0x%08x\n", regs->reg_ecx); cprintf(" eax 0x%08x\n", regs->reg_eax); } - +/** + * 打印关于页面故障的详细信息。 + * + * 此函数用于输出页面故障的详细信息,包括故障地址、访问类型(读/写)、访问模式(用户/内核)以及故障类型(未找到页面/保护故障)。 + * + * @param tf 指向 trapframe 结构的指针,包含故障发生时的寄存器状态和错误代码。 + */ static inline void print_pgfault(struct trapframe *tf) { /* error_code: @@ -143,33 +175,57 @@ print_pgfault(struct trapframe *tf) { * bit 1 == 0 means read, 1 means write * bit 2 == 0 means kernel, 1 means user * */ + /* error_code: + * bit 0 == 0 表示未找到页面,1 表示保护故障 + * bit 1 == 0 表示读操作,1 表示写操作 + * bit 2 == 0 表示内核模式,1 表示用户模式 + * */ cprintf("page fault at 0x%08x: %c/%c [%s].\n", rcr2(), (tf->tf_err & 4) ? 'U' : 'K', (tf->tf_err & 2) ? 'W' : 'R', (tf->tf_err & 1) ? "protection fault" : "no page found"); } +/** + * 页面故障处理函数 + * + * 此函数负责处理页面故障异常,它首先打印出页面故障的信息, + * 然后根据是否有有效的内存管理结构来决定是否进行页面故障处理, + * 如果没有有效的内存管理结构,则引发系统崩溃 + * + * @param tf 指向陷阱帧的指针,包含故障发生时的CPU状态信息 + * @return 返回页面故障处理的结果,或者在无法处理时引发系统崩溃 + */ static int pgfault_handler(struct trapframe *tf) { + // 声明一个外部变量,用于检查内存管理结构 extern struct mm_struct *check_mm_struct; + // 打印页面故障信息 print_pgfault(tf); + // 检查是否存在有效的内存管理结构 if (check_mm_struct != NULL) { + // 如果存在,调用页面故障处理函数 return do_pgfault(check_mm_struct, tf->tf_err, rcr2()); } + // 如果没有有效的内存管理结构,引发系统崩溃 panic("unhandled page fault.\n"); } static volatile int in_swap_tick_event = 0; extern struct mm_struct *check_mm_struct; +struct trapframe switchk2u, *switchu2k; +//定义了一个名为 trap_dispatch 的静态函数,根据发生的陷阱类型进行相应的处理。 +// 参数 tf 是指向陷阱帧的指针,包含了关于陷阱发生时的CPU状态信息。 static void trap_dispatch(struct trapframe *tf) { char c; int ret; - + //通过 switch 语句根据 tf->tf_trapno 的值来分发不同的陷阱处理逻辑。 switch (tf->tf_trapno) { case T_PGFLT: //page fault + // 处理页故障中断 if ((ret = pgfault_handler(tf)) != 0) { print_trapframe(tf); panic("handle pgfault failed. %e\n", ret); @@ -186,26 +242,62 @@ trap_dispatch(struct trapframe *tf) { * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks(). * (3) Too Simple? Yes, I think so! */ + ticks ++; //记录中断事件 + if (ticks % TICK_NUM == 0) + { + print_ticks(); + }//每经过 TICK_NUM 次周期时,调用 print_ticks() 打印信息。 break; + //处理串口中断,调用 cons_getc() 从串口读取字符并打印。 case IRQ_OFFSET + IRQ_COM1: c = cons_getc(); cprintf("serial [%03d] %c\n", c, c); break; + //处理键盘中断,调用 cons_getc() 读取键盘输入并打印。 case IRQ_OFFSET + IRQ_KBD: c = cons_getc(); cprintf("kbd [%03d] %c\n", c, c); break; //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes. - case T_SWITCH_TOU: - case T_SWITCH_TOK: - panic("T_SWITCH_** ??\n"); + case T_SWITCH_TOU://表示发生了从内核模式切换到用户模式的请求。 + if (tf->tf_cs != USER_CS) {//判断当前是否在内核模式下 + switchk2u = *tf; //保存当前陷阱框架 + switchk2u.tf_cs = USER_CS;//设置用户模式的段寄存器 + //将数据段和栈段寄存器 都设置为 USER_DS(用户数据段) + switchk2u.tf_ds = switchk2u.tf_es = switchk2u.tf_ss = USER_DS; + switchk2u.tf_esp = (uint32_t)tf + sizeof(struct trapframe) - 8; + + // set eflags, make sure ucore can use io under user mode. + // if CPL > IOPL, then cpu will generate a general protection. + switchk2u.tf_eflags |= FL_IOPL_MASK;//允许用户模式下进行 I/O 操作 + + // set temporary stack + // then iret will jump to the right stack + *((uint32_t *)tf - 1) = (uint32_t)&switchk2u; + } + break; + case T_SWITCH_TOK://T_SWITCH_TOK 表示发生了从用户模式切换到内核模式的请求。 + if (tf->tf_cs != KERNEL_CS) { //判断当前是否在用户模式下 + tf->tf_cs = KERNEL_CS; + tf->tf_ds = tf->tf_es = KERNEL_DS; + //设置内核模式的段寄存器 + tf->tf_eflags &= ~FL_IOPL_MASK; //清除 I/O 权限标志 + switchu2k = (struct trapframe *)(tf->tf_esp - (sizeof(struct trapframe) - 8)); + //使用 memmove 将当前的陷阱框架(除了最后8个字节)复制到新的陷阱框架位置 switchu2k + memmove(switchu2k, tf, sizeof(struct trapframe) - 8); + //将新的陷阱框架地址 switchu2k 存储到当前陷阱框架之前的一个栈位置 + *((uint32_t *)tf - 1) = (uint32_t)switchu2k; + } break; + //系统接收到这两个中断信号时,不执行任何操作,直接跳过。 case IRQ_OFFSET + IRQ_IDE1: case IRQ_OFFSET + IRQ_IDE2: /* do nothing */ break; default: // in kernel, it must be a mistake + //检查当前陷阱框架的代码段寄存器 tf->tf_cs 的特权级 + //(tf->tf_cs & 3) == 0 检查是否在内核模式中 if ((tf->tf_cs & 3) == 0) { print_trapframe(tf); panic("unexpected trap in kernel.\n"); @@ -221,6 +313,7 @@ trap_dispatch(struct trapframe *tf) { void trap(struct trapframe *tf) { // dispatch based on what type of trap occurred + //该行代码调用 trap_dispatch 函数,将陷阱帧传递给它。 trap_dispatch(tf); } diff --git a/labcodes/lab4/kern/trap/trapentry.S b/labcodes/lab4/kern/trap/trapentry.S index 3565ec8e9968768e106e11289eacf4634becbdba..ad9313d17911539b9c17ac9773341ededa4247a3 100644 --- a/labcodes/lab4/kern/trap/trapentry.S +++ b/labcodes/lab4/kern/trap/trapentry.S @@ -1,11 +1,13 @@ #include # vectors.S sends all traps here. +# 定义了一个全局标签 __alltraps,这是处理所有异常的入口点。 .text .globl __alltraps __alltraps: # push registers to build a trap frame # therefore make the stack look like a struct trapframe + # 通过 push 指令,将数据段寄存器和所有通用寄存器(使用 pushal)的值压入栈中,以保存当前状态。 pushl %ds pushl %es pushl %fs @@ -13,32 +15,39 @@ __alltraps: pushal # load GD_KDATA into %ds and %es to set up data segments for kernel + # 将常量 GD_KDATA 加载到 %eax 中,然后将其值复制到 %ds 和 %es 中,设置内核的数据段。 movl $GD_KDATA, %eax movw %ax, %ds movw %ax, %es # push %esp to pass a pointer to the trapframe as an argument to trap() + # 将 %esp 压栈,以将指向 trapframe 的指针作为参数传递给 trap() pushl %esp # call trap(tf), where tf=%esp + # 调用 trap(tf),其中 tf=%esp call trap - # pop the pushed stack pointer + # pop the pushed stack pointer弹出之前压入的栈指针 popl %esp # return falls through to trapret... + # 返回后继续执行到 trapret... .globl __trapret __trapret: # restore registers from stack + # 定义了返回的入口点 __trapret。 popal # restore %ds, %es, %fs and %gs + # 这里会恢复之前保存的寄存器 popl %gs popl %fs popl %es popl %ds # get rid of the trap number and error code + # 通过 iret 指令返回中断处理 addl $0x8, %esp iret