update xv6的启动过程.md : for riscv

This commit is contained in:
shzhxh 2019-12-05 20:39:30 +08:00
parent 209b3d9397
commit e5f3ca61fa
1 changed files with 159 additions and 42 deletions

View File

@ -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是双字计数只有在调用门中有意义默认为0rsvl是保留位默认为0s位固定为0p描述符是否有效默认为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是双字计数只有在调用门中有意义默认为0rsvl是保留位默认为0s位固定为0p描述符是否有效默认为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字节的bufread、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依赖于consputcconsputc依赖于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