From e5f3ca61fabd3c451930bd3b58c0604ff1ce7dc2 Mon Sep 17 00:00:00 2001 From: shzhxh Date: Thu, 5 Dec 2019 20:39:30 +0800 Subject: [PATCH] =?UTF-8?q?update=20xv6=E7=9A=84=E5=90=AF=E5=8A=A8?= =?UTF-8?q?=E8=BF=87=E7=A8=8B.md=20:=20for=20riscv?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OS/XV6/xv6的启动过程.md | 201 +++++++++++++++++++++++++++-------- 1 file changed, 159 insertions(+), 42 deletions(-) diff --git a/OS/XV6/xv6的启动过程.md b/OS/XV6/xv6的启动过程.md index 273669d..24af534 100644 --- a/OS/XV6/xv6的启动过程.md +++ b/OS/XV6/xv6的启动过程.md @@ -16,73 +16,113 @@ bootblock->entry.S->main.c `main`函数在main.c,是内核的主控函数。 -1. 在`kalloc.c`里实现kinit1函数(不需要锁的物理页分配),其原理是把前4M的物理页放到一个单向链表里。kinit1和kinit2调用freerange将内存加入空闲链表中, freerange则是通过对每一页调用kfree实现该功能。一个PTE只能指向一个4096字节对齐的物理地址(即是4096的倍数),因此freerange用PGROUNDUP来保证分配器只会释放对齐的物理地址。分配器原本一开始没有内存可用,正是对kfree的调用将可用内存交给了分配器来管理。 +##### 4. kinit1 -2. 在`vm.c`里实现kvmalloc函数(建立内核的页表映射)。 +物理页分配 -3. mpinit侦测其它的处理器。 +在`kalloc.c`里实现kinit1函数(不需要锁的物理页分配),其原理是把前4M的物理页放到一个单向链表里。kinit1和kinit2调用freerange将内存加入空闲链表中, freerange则是通过对每一页调用kfree实现该功能。一个PTE只能指向一个4096字节对齐的物理地址(即是4096的倍数),因此freerange用PGROUNDUP来保证分配器只会释放对齐的物理地址。分配器原本一开始没有内存可用,正是对kfree的调用将可用内存交给了分配器来管理。 -4. lapicinit初始化LAPIC中断控制器。时钟芯片是在LAPIC中的,所以每一个处理器可以独立地接收时钟中断。 +##### 5. kvmalloc - `lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));`使LAPIC周期性地在IRQ_TIMER(也就是IRQ0)产生中断。 +内核页表 - `lapicw(TPR, 0);`打开CPU的LAPIC的中断,这使得LAPIC能够将中断传递给本地处理器。 +在`vm.c`里实现kvmalloc函数(建立内核的页表映射)。 -5. seginit初始化段描述符(在不同CPU上有不同值的per-CPU变量)。 +##### 6. mpinit -6. picinit使PIC失效。 +侦测其它的处理器。 - ```c - void picinit(void){ - outb(IO_PIC1+1, 0xFF); - outb(IO_PIC2+1, 0xFF); - } - ``` +##### 7. lapicinit -7. ioapicinit是对I/O APIC的初始化。它建立了中断到PRT的映射,然后把它们都屏蔽掉。随后由设备自己开启中断,并指定哪个处理器接收这个中断。 +初始化LAPIC中断控制器。时钟芯片是在LAPIC中的,所以每一个处理器可以独立地接收时钟中断。 - ```c - for(i = 0; i <= maxintr; i++){ - ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i)); - ioapicwrite(REG_TABLE+2*i+1, 0); - } - ``` +`lapicw(TIMER, PERIODIC | (T_IRQ0 + IRQ_TIMER));`使LAPIC周期性地在IRQ_TIMER(也就是IRQ0)产生中断。 -8. consoleinit初始化硬件:控制台。 +`lapicw(TPR, 0);`打开CPU的LAPIC的中断,这使得LAPIC能够将中断传递给本地处理器。 -9. uartinit初始化硬件:串口。 +##### 8. seginit -10. pinit初始化进程表。 +初始化段描述符(在不同CPU上有不同值的per-CPU变量)。 -11. 在`trap.c`里实现`tvinit`函数(建立中断向量表)。`tvinit`函数通过宏SETGATE建立了中断向量表的256个表项,详见下表: +##### 9. picinit - | SETGATE参数 | 值 | 意义 | - | ----------- | ------------ | -------------------------- | - | gate | idt[i] | 中断向量表第i项 | - | istrap | 0 | 中断类型,0:中断,1:陷阱 | - | sel | SEG_KCODE<<3 | 段选择子 | - | off | vectors[i] | 第i个中断例程的入口地址 | - | d | 0 | 描述符特权级 | +使PIC失效。 - 通过观察`vectors.S`可以发现所有的中断例程都指向了alltraps,所以个人认为在xv6里中断向量表并没有发挥它应有的作用,因为所有的中断都是在alltraps及其后的函数里处理的。中断向量表里每项默认的值:args是双字计数,只有在调用门中有意义,默认为0;rsvl是保留位,默认为0;s位固定为0;p描述符是否有效,默认为1。 +``` +void picinit(void){ + outb(IO_PIC1+1, 0xFF); + outb(IO_PIC2+1, 0xFF); +} +``` - 特别地,`tvinit`函数为系统调用T_SYSCALL进行了专门的设置:将istrap设置为1将它指定为一个陷阱门,陷阱门不会清除EFLAGS寄存器的IF位,这使得处理系统调用的时候也可以处理其它中断;将d设置为DPL_USER,这使得用户进程可以通过int指令产生一个内陷,因为xv6不允许进程用int来产生其它中断,否则会触发13号中断(通用保护异常)。 +##### 10. ioapicinit -12. binit 缓冲区缓存。binit会从一个静态数组buf中构建出一个有NBUF个元素的双向链表。所有对块缓冲的访问都通过链表而非静态数组。 +ioapicinit是对I/O APIC的初始化。它建立了中断到PRT的映射,然后把它们都屏蔽掉。随后由设备自己开启中断,并指定哪个处理器接收这个中断。 -13. fileinit初始化文件表。 +``` +for(i = 0; i <= maxintr; i++){ + ioapicwrite(REG_TABLE+2*i, INT_DISABLED | (T_IRQ0 + i)); + ioapicwrite(REG_TABLE+2*i+1, 0); + } +``` -14. ideinit初始化磁盘。首先,调用`ioapicenable`来打开最后一个CPU(ncpu-1)的IRQ_IDE中断。(在双处理器系统上,CPU1专门处理磁盘中断。)然后,调用`idewait`来等待磁盘接受命令。`idewait`通过0x1F7端口获取磁盘状态,直到IDE_BSY被清除以及IDE_DRDY被设置。最后,检查磁盘1是否存在。初始化完成后,xv6只能通过buffer cache调用`iderw`读写磁盘了。 +##### 11. consoleinit -15. startothers启动其它处理器。 +初始化硬件:控制台。 -16. kinit2函数是使用了锁的物理页分配,使更多的内存可用于物理页分配。 +##### 12. uartinit -17. userinit 初始化第一个用户进程。 +初始化硬件:串口。 -18. mpmain函数完成此处理器的设置,开启多处理器切换和进程调度。 +##### 13. pinit - `idtinit`载入idt寄存器。 +初始化进程表。 + +##### 14. tvinit + +在`trap.c`里实现`tvinit`函数(建立中断向量表)。`tvinit`函数通过宏SETGATE建立了中断向量表的256个表项,详见下表: + +| SETGATE参数 | 值 | 意义 | +| ----------- | ------------ | -------------------------- | +| gate | idt[i] | 中断向量表第i项 | +| istrap | 0 | 中断类型,0:中断,1:陷阱 | +| sel | SEG_KCODE<<3 | 段选择子 | +| off | vectors[i] | 第i个中断例程的入口地址 | +| d | 0 | 描述符特权级 | + +通过观察`vectors.S`可以发现所有的中断例程都指向了alltraps,所以个人认为在xv6里中断向量表并没有发挥它应有的作用,因为所有的中断都是在alltraps及其后的函数里处理的。中断向量表里每项默认的值:args是双字计数,只有在调用门中有意义,默认为0;rsvl是保留位,默认为0;s位固定为0;p描述符是否有效,默认为1。 + +特别地,`tvinit`函数为系统调用T_SYSCALL进行了专门的设置:将istrap设置为1将它指定为一个陷阱门,陷阱门不会清除EFLAGS寄存器的IF位,这使得处理系统调用的时候也可以处理其它中断;将d设置为DPL_USER,这使得用户进程可以通过int指令产生一个内陷,因为xv6不允许进程用int来产生其它中断,否则会触发13号中断(通用保护异常)。 + +##### 15. binit + +binit 缓冲区缓存。binit会从一个静态数组buf中构建出一个有NBUF个元素的双向链表。所有对块缓冲的访问都通过链表而非静态数组。 + +##### 16. fileinit + +初始化文件表。 + +##### 17. ideinit + +初始化磁盘。首先,调用`ioapicenable`来打开最后一个CPU(ncpu-1)的IRQ_IDE中断。(在双处理器系统上,CPU1专门处理磁盘中断。)然后,调用`idewait`来等待磁盘接受命令。`idewait`通过0x1F7端口获取磁盘状态,直到IDE_BSY被清除以及IDE_DRDY被设置。最后,检查磁盘1是否存在。初始化完成后,xv6只能通过buffer cache调用`iderw`读写磁盘了。 + +##### 18. startothers + +启动其它处理器。 + +##### 19. kinit2 + +使用了锁的物理页分配,使更多的内存可用于物理页分配。 + +##### 20. userinit + +初始化第一个用户进程。 + +##### 21. mpmain + +完成此处理器的设置,开启多处理器切换和进程调度。 + +`idtinit`载入idt寄存器。 #### riscv架构 @@ -106,5 +146,82 @@ bootblock->entry.S->main.c 文件位置:kernel/main.c,这是整个内核的总控函数。如果当前的核编号为0,则为主控核,执行一系列的硬件初始化工作,然后执行scheduler函数进入处理器调度。如果当前的核编号不为0,则初始化当前核的数据结构,然后执行scheduler函数进入处理器调度。 +##### 4. consoleinit函数 +文件位置:kernel/console.c,其作用是初始化控制台,它是printf函数的基础。 +首先初始化cons.lock。cons是控制台的数据结构,它包含了一个spinlock,一个128字节的buf,read、write、edit索引各一个。 + +然后初始化uart。**TODO**:请解释uart的寄存器用法。 + +最后设置devsw[CONSOLE]的read和write操作。consoleread和consolewrite实现对控制台的读和写,它们最终都是通过调用uart来实现其功能的。devsw[]是个数组,每一项都代表一个设备,比如devsw[CONSOLE]就代表了控制台,每项都是个数据结构,包含了read和write两种操作。 + +##### 5. printfinit函数 + +文件位置:kernel/printf.c,其作用是避免多个核调用printf函数引起输入输出的混乱,它是用自旋锁来实现这一功能的。 + +这个函数所做的就是初始化printf函数的自旋锁。pr就是printf函数使用自旋锁的数据结构,它包含了一个自旋锁lock和自旋锁的状态标志locking。 + +##### 6. printf函数 + +文件位置:kernel/printf.c,向屏幕输出带有格式的字符串。printf依赖于consputc,consputc依赖于uartputc。 + +##### 7. kinit函数 + +文件位置:kernel/kalloc.c,分配物理内存。 + +首先,初始化物理内存的自旋锁。kmem是物理内存的数据结构,它包含了一个自旋锁lock和一个单向链表freelist。 + +然后,执行freerange函数,把内核之后直到128M的内存空间放到单向链表里,这样就可以通过链表来管理这些空闲内存了。 + +##### 8. kvminit函数 + +文件位置:kernel/vm.c,为内核创建页表并开启分页。 + +##### 9. kvminithart + +开启分页。 + +##### 10. procinit + +进程表。 + +##### 11. trapinit + +trap向量。 + +##### 12. trapinithart + +安装内核trap向量 + +##### 13. plicinit + +设置中断控制器 + +##### 14. plicinithuart + +为设备中断申请PLIC + +##### 15. binit + +buffer cache + +##### 16. iinit + +inode cache + +##### 17. fileinit + +文件表 + +##### 18. virtio_disk_init + +虚拟出来的硬盘 + +##### 19. userinit + +第一个用户进程。 + +##### 20. __sync_synchronize + +##### 21. scheduler \ No newline at end of file