xv6内核boot流程(参考)

发布于 作者: Ethan

入口与分支条件

main()start() 在所有 CPU 的 supervisor 模式下进入; 根据 cpuid() 判断当前是否为引导 CPU(cpuid()==0)或其它 CPU(cpuid()!=0)。


引导 CPU 路径(cpuid()==0)

consoleinit()

  • 初始化控制台锁(cons.lock)。
  • 初始化 UART(uartinit()),为串口 I/O 做准备。
  • 将 console 读写函数挂接到设备表 devsw[CONSOLE],使得读写系统调用能够通过 console 实现。

statsinit()(仅 LAB_LOCK)

  • main() 中仅在 LAB_LOCK 条件编译下调用。
  • 仓库中只有函数声明(defs.h),未包含实现文件,因此无法从源码进一步展开具体逻辑。

printfinit()

  • 初始化 printf 相关的全局锁 pr.lock
  • 用于保护并发输出,避免多核打印互相交错。

打印启动信息

  • 打印空行与 "xv6 kernel is booting"
  • 属于纯日志提示,无副作用逻辑。

kinit()

  • 初始化物理内存分配器的自旋锁 kmem.lock
  • [end, PHYSTOP) 物理内存范围逐页释放到空闲链表(调用 freerange(),进而 kfree()),使页分配器可用。

kvminit()

  • 调用 kvmmake() 创建内核页表,并保存到全局 kernel_pagetable

kvmmake() 内部:

  • 为页表分配一页内存并清零。
  • 映射 UART、VIRTIO、PLIC 等 MMIO 设备寄存器区域。
  • 映射内核代码段(只读可执行)与内核数据段(读写)。
  • 映射 trampoline 页,用于用户态/内核态切换。
  • 为每个进程映射内核栈(proc_mapstacks())。

kvminithart()

  • 执行 sfence_vma(),等待页表写入完成并清除旧 TLB 条目。
  • 设置 satp 为内核页表并再次 sfence_vma(),从而在当前 CPU 上开启分页。

procinit()

  • 初始化 PID 锁与 wait 锁。
  • 遍历 proc[] 表,为每个 proc 初始化锁、状态为 UNUSED,并预计算内核栈虚拟地址。

trapinit()

  • 初始化全局时钟锁 tickslock,用于保护 ticks 计数。

trapinithart()

  • 设置 stvec 指向 kernelvec,建立当前 CPU 的内核态 trap 向量入口。

plicinit()

  • 配置 PLIC 中断优先级:启用 UART 与 VirtIO 中断(优先级设为非零)。
  • 如启用网络实验(LAB_NET),为 PCIe 中断范围设置优先级。

plicinithart()

  • 针对当前 CPU 设置 PLIC 的 S-mode 使能位,允许 UART 与 VirtIO 设备中断进入该 hart。
  • 设置 S-mode 中断阈值为 0,允许所有已启用优先级中断进入。
  • LAB_NET 下额外开启更多 IRQ 位供 e1000 使用。

binit()

  • 初始化 buffer cache 自旋锁 bcache.lock
  • 构建缓存链表并初始化每个缓冲区的 sleep lock,建立 LRU 链表结构。

iinit()

  • 初始化 inode 表锁 itable.lock
  • 为每个 inode 初始化 sleep lock,建立 inode 表并发保护结构。

fileinit()

  • 初始化全局文件表 ftable 的自旋锁。
  • 为文件描述符分配与回收做准备。

virtio_disk_init()

  • 初始化 VirtIO 磁盘驱动锁 vdisk_lock
  • 检查设备 MMIO 寄存器,确认存在并为 VirtIO 磁盘设备。
  • 重置设备并依次设置 ACKNOWLEDGEDRIVERFEATURES_OKDRIVER_OK 状态位。
  • 协商并配置设备特性(禁用若干不支持特性)。
  • 分配 / 初始化描述符环(desc / avail / used),配置队列地址并设置队列为 ready。

pci_init()(仅 LAB_NET)

  • 扫描 QEMU PCIe 配置空间,寻找 e1000 网卡(设备 ID 0x100e8086)。
  • 配置 BAR,将 e1000 寄存器映射到固定物理地址 0x40000000,并调用 e1000_init() 进行设备初始化。

netinit()(仅 LAB_NET)

  • 初始化 netlock
  • 为网络子系统后续并发操作提供互斥保护。

userinit()

  • 调用 allocproc() 分配一个新的进程结构,并保存到全局 initproc
  • cwd 设为根目录(namei("/"))。
  • 将进程状态设为 RUNNABLE,然后释放该进程锁,使其可被调度执行。

kcsaninit()(仅 KCSAN)

  • main() 中在 KCSAN 条件编译下调用。
  • 仅在 defs.h 看到声明,仓库中未包含实现文件,无法进一步展开具体逻辑。

__sync_synchronize(); started = 1;

  • 内存屏障保证前述初始化对其他 CPU 可见。
  • 然后置 started = 1,释放其它 CPU 从自旋等待中继续启动。

非引导 CPU 路径(cpuid()!=0)

等待 started

  • 自旋等待 started 变为 1,确保引导 CPU 完成全局初始化后再继续。

__sync_synchronize()

  • 内存屏障,确保读取到的所有共享状态是最新的(与引导 CPU 的写入同步)。

启动日志

  • printf("hart %d starting\n", cpuid());
  • 打印当前 hart 启动日志,用于多核启动可见性与调试。

kvminithart()

  • 对当前 CPU 设置 satp 为内核页表并刷新 TLB,从而开启分页。

trapinithart()

  • 设置当前 CPU 的 trap 向量入口 stveckernelvec
  • 确保内核态异常 / 中断能进入正确处理函数。

plicinithart()

  • 为当前 CPU 设置 PLIC 的 S-mode 使能位与阈值。
  • 允许接收 UART / VirtIO 中断(以及 LAB_NET 下的更多 IRQ)。

收尾:进入调度器

rwspinlock_test()(仅 LAB_LOCK)

  • main() 中条件调用。
  • 仓库中未发现该函数实现文件(同 statsinit)。
  • 因此只能确认它存在于 LAB_LOCK 相关配置里,但无法进一步展开实现细节。

scheduler()

  • 调度器进入无限循环。

  • 在每轮中打开 / 关闭中断以避免死锁与竞态。

  • 遍历进程表查找 RUNNABLE 进程。

  • 当找到可运行进程时:

    • 设置其状态为 RUNNING
    • 切换上下文到该进程执行。
    • 进程返回后继续扫描下一轮。
  • 如果没有可运行进程:

    • 执行 wfi 进入低功耗等待中断。