深入理解操作系统,学习内核。

正文

从应用程序员的观念转换到系统程序员,软硬件高度耦合。

Booting

Init

kinit1()

  • phys page allocator
  • 采取链表管理物理页,但实际上不需要使用物理地址,一切都用虚拟地址,在内核address space中,物理地址 = 虚拟地址 - KERNBASE。
  • 从 “first address after kernel loaded from ELF file” 开始,对齐,然后分配最多到4MB位置的页。

kvmalloc()

  • kernel page table
  • 初始化page table,xv6使用tow-level Page Table ,巧妙之处在于先实现一些小函数(walk、mappage)。
  • end使用0,非常精彩,因为是unsigned,0 - x等同于max - x + 1
  • xv6中没有使用大页,比较简单。

mpinit(); // detect other processors

lapicinit(); // interrupt controller

seginit(); // segment descriptors

picinit(); // disable pic

ioapicinit(); // another interrupt controller

consoleinit(); // console hardware

uartinit(); // serial port

pinit(); // process table

tvinit(); // trap vectors

binit(); // buffer cache

fileinit(); // file table

ideinit(); // disk

startothers(); // start other processors

kinit2(P2V(410241024), P2V(PHYSTOP)); // must come after startothers()

userinit(); // first user process

mpmain(); // finish this processor’s setup

附录

主引导扇区

  • 512 Bytes, 结尾两个字节0x55 0xAA。
  • BIOS加电后会读取引导扇区,确认有效性,然后转移控制权。

从实模式到保护模式

  • 段寄存器,有cs、ss等,每个段寄存器还跟着隐藏的描述符高速缓存器,每次对地址进行转换(其实就是段地址+偏移量),都是直接使用缓存里的值。
  • 实模式下,cs里存段地址,没有保护位和界限。(从offset的位数来讲,段空间有一个上限)
  • 保护模式下,cs里存段描述符的偏移量,比如0x8,gdt寄存器则存段描述符的起始地址,两者相加得到描述符地址。描述符里记录了段地址、界限和属性。
  • 总的来说,描述符的结构非常复杂,知道保护模式的保护来源于此即可,并不是所有人都能修改描述符。

Interrupt Descriptor Table

  • IDTR指向IDT,在分页内存下为虚地址。
  • CPU会根据异常的编号获取不同的表项,表中存的依然是虚地址,指向handler入口。
  • (Xv6)handler并不处理异常,只是将参数入栈,统一跳转到alltraps,由traps函数处理。由于某些异常里,cpu保存的栈不相同(没有error code),所以需要多压栈一个0。

Exception、Interrupt、Traps

  • 能够预知的就是Exception,不能预知的是Interrupt。
  • Trap通常指用户态到内核态。

分段和分页

  • 分段针对的是物理地址,而且出现的原因是历史上,16位寄存器为了兼容20位地址线,从而使用了段寄存器。
  • 到寄存器和地址线一样大之后,段寄存器的意义就不大了。在保护模式下增加了额外功能,即段描述符,可以增加一定的权限保护。
  • 分页机制出现后,权限保护也可以由Page Table实现,从而分段变得毫无意义,现代操作系统中关闭分段。

CPL、DPL、RPL

INT和IRET

  • 弄懂INT和IRET两个指令做的事情,就能知道异常是怎么处理的了,只是,INT指令的action和很多标志位相关,不能简单概括。
  • 总的来说,这两条指令会负责保存和恢复CS:IP,状态位寄存器,调用IDT。
  • 至于其他寄存器,则由软件自行处理,粗暴一点的可以直接pushal。

Trible Interrupt

  • Trible Interrupt被认为是error,OS正常运行中不会引发三次以上的Nested Interrupt。
  • 从Xv6的代码中,其实看不出来Nested Interrupt有什么影响,Switch函数总是能将最新的context保存在proc结构中。