1.系统理解
第二章
2.1、xv6代码结构
代码主要有三个部分组成:
kernel
: 我们可以ls kernel
,里面包含了所有的内核文件。因为XV6是一个宏内核结构,这里所有的文件会被编译成一个叫做kernel
的二进制文件,然后这个二进制文件会被运行在kernel mode
中。use
: 基本上是运行在user mode
的程序。这也是为什么一个目录称为kernel
,另一个目录称为user
的原因。mkfs
: 它会创建一个空的文件镜像,我们会将这个镜像存在磁盘上,这样我们就可以直接使用一个空的文件系统。
2.2、xv6如何切换不同模式(user/kernel..)
2.3、xv6启动流程
从entry.S
开始启动,在末尾跳转到start.c
,
start.c
主要完成以下功能:
-
在寄存器
mstatus
中把之前的特权模式设置为监督者模式 -
通过把
main
的地址写入寄存器mepc
来设置返回地址为main
, -
通过在页表寄存器
satp
中写入0来禁止监督者模式下的虚拟地址转换 -
把所有中断和异常委托给监督者模式
-
对时钟芯片进行编程以产生时钟中断
-
在完成这些工作后,
start
通过调用mret
"返回"到监督者模式。之后程序会跳转到main(kernel/main.c:11)
main.c
中完成以下设置:
consoleinit();
printfinit();
printf("\n");
printf("xv6 kernel is booting\n");
printf("\n");
kinit(); // physical page allocator
kvminit(); // create kernel page table
kvminithart(); // turn on paging
procinit(); // process table
trapinit(); // trap vectors
trapinithart(); // install kernel trap vector
plicinit(); // set up interrupt controller
plicinithart(); // ask PLIC for device interrupts
binit(); // buffer cache
iinit(); // inode table
fileinit(); // file table
virtio_disk_init(); // emulated hard disk
userinit(); // first user process
__sync_synchronize();
之后跳转到userinit()
通过userinit()
中的initcode.s
跳转到user/init.c
,(initcode
是一段翻译好的机器码,汇编格式见user/initcode.S
)
init.c
中调用执行sh.c
,启动完成,
2.4 调用系统调用
的流程
在用户态中,所有的系统接口都定义在user.h
中,通过usys.pl
将头文件中的系统接口与真正的系统接口实现连接起来,内核态的系统调用接口定义在syscall.h
中,具体实现分布在内核各个文件中,通过数组下标索引在syscall.c
中引用和调用
第三章
3.1 页表工作流程总结
-
三级页表
-
同一个物理页可映射到多个虚拟地址
-
将根页表物理地址放到satp寄存器中供cpu翻译地址使用
-
内核和每个进程都有自己的页表
3.2 地址空间
每一页4KB,需要12位进行索引
虚拟地址
虚拟地址64位,高25位不使用,低12位时页内索引,中间27位索引三级页表,每一级页表使用9位,所以每级页表最多\(2^9 = 512\)个页表项,又因为页大小为4KB = \(2^{12}\)B,所以每一个页表项大小为\(2^3B=8B=64bit\),
物理地址
内核页表
进程页表
进程页表和虚拟地址空间之间的关系
每个进程的页表是何时被创建的
虚拟地址空间何时被创建
那些事是CPU指令完成的,那些事是操作系统完成的?