调用图#

初始化流程#

@startuml

sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package kernel.ld {
    rectangle OUTPUT_ARCH <<$aMacro>> #Application
    rectangle ENTRY <<$bFunction>> #Business
    rectangle etext <<$tGlobal>> #Technology
}
package entry.S {
    rectangle _entry <<$bFunction>> #Business
    rectangle spin <<$bFunction>> #Business
}
package start.c {
    rectangle start <<$bFunction>> #Business
    rectangle timerinit <<$bFunction>> #Business
}
package main.c {
    rectangle main <<$bFunction>> #Business
}


OUTPUT_ARCH -> ENTRY
ENTRY -down-> _entry
_entry -down-> start
start -down-> main

@enduml

@startuml

start
if (cpuid()==0) then (yes)
    :kernel.ld: 加载 entry.S 到 0x80000000,指定程序入口 ENTRY;
    :entry.S: 从 _entry 开始执行,最后跳转到 start();
    :start.S: 通过 asm 指令切换到 C 程序入口 main();
    :consoleinit() 初始化锁和 UART,并将 `consoleread` 和 `consolewrite` 函数与系统调用相关联;
    if (defined LAB_PGTBL or LAB_LOCK?) then (yes)
        :statsinit() 初始化统计信息设备,包括初始化锁和设置读写函数;
    else (no)
    endif
    :printfinit() 初始化打印锁,确保在多核环境下控制台输出的线程安全;
    :kinit() 初始化内存分配器,并调用 `freerange` 函数来将内核结束后的物理地址范围标记为可用;
    :kvminit() 创建一个直接映射的内核页表,并将 UART、VirtIO 磁盘接口、CLINT 和 PLIC 等设备的物理地址映射到虚拟地址空间中;
    :kvminithart() 切换硬件页表寄存器以使用内核页表,并刷新 TLB;
    :procinit() 初始化进程表,为每个进程分配一个内核栈页面;
    :trapinit() 初始化一个用于同步的自旋锁 `tickslock`,并将 `ticks` 变量设置为 0;
    :trapinithart() 设置中断向量表(stvec),使其指向 `kernelvec` 函数;
    :plicinit() 设置了一些中断优先级,以确保 UART0 和 VIRTIO0 设备的中断能够被启用;
    :plicinithart() 为当前处理器核心设置了中断使能位,并设置了该核心在 S 模式下的优先级阈值;
    :binit() 初始化缓冲区缓存,创建一个双向链表,并初始化每个缓冲区的锁;
    :iinit() 初始化 inode 缓存;
    :fileinit() 初始化文件表,包括一个自旋锁来保护对文件表的访问;
    :virtio_disk_init() 初始化虚拟磁盘设备,包括检查设备的魔数、版本号、设备 ID 和供应商 ID,以及设置设备的特性;
    if (defined LAB_NET?) then (yes)
        :pic_init();
        :sockinit();
    else (no)
    endif
    :userinit() 设置第一个用户进程;
else (no)
    :kvminithart() 切换硬件页表寄存器以使用内核页表,并刷新 TLB;
    :trapinithart() 设置中断向量表(stvec),使其指向 `kernelvec` 函数;
    :plicinithart() 为当前处理器核心设置了中断使能位,并设置了该核心在 S 模式下的优先级阈值;
endif
:scheduler() 是一个循环,不断选择一个可运行的进程并切换到它;
stop

@enduml

页表#

@startuml
sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package memlayout.h {
    rectangle TRAPFRAME <<$aMacro>> #Application
}
package vm.c {
    rectangle kvminit <<$bFunction>> #Business
    rectangle kvminithart <<$bFunction>> #Business
    rectangle walk <<$bFunction>> #Business
    rectangle walkaddr <<$bFunction>> #Business
    rectangle kvmmap <<$bFunction>> #Business
    rectangle kvmpa <<$bFunction>> #Business
    rectangle mappages <<$bFunction>> #Business
    rectangle kvmcreate <<$bFunction>> #Business
    rectangle uvmunmap <<$bFunction>> #Business
    rectangle uvmcreate <<$bFunction>> #Business
    rectangle uvminit <<$bFunction>> #Business
    rectangle uvmalloc <<$bFunction>> #Business
    rectangle uvmdealloc <<$bFunction>> #Business
    rectangle freewalk <<$bFunction>> #Business
    rectangle uvmfree <<$bFunction>> #Business
    rectangle kvmfree <<$bFunction>> #Business
    rectangle uvmcopy <<$bFunction>> #Business
    rectangle uvmclear <<$bFunction>> #Business
    rectangle copyout <<$bFunction>> #Business
    rectangle copyin <<$bFunction>> #Business
    rectangle copyinstr <<$bFunction>> #Business
    rectangle vmprint2 <<$bFunction>> #Business
    rectangle vmprint <<$bFunction>> #Business
    rectangle kernel_pagetable <<$tGlobal>> #Technology
}
package kalloc.c {
    rectangle kinit <<$bFunction>> #Business
    rectangle freerange <<$bFunction>> #Business
    rectangle kfree <<$bFunction>> #Business
    rectangle kalloc <<$bFunction>> #Business
    frame kmem {
        rectangle lock as kmem.lock <<$tGlobal>> #Technology
        rectangle freelist <<$tGlobal>> #Technology
    }
}
package riscv.h {
    rectangle w_satp <<$bFunction>> #Business
    rectangle sfence_vma <<$bFunction>> #Business
}
package exec.c {
    rectangle exec <<$bFunction>> #Business
    rectangle loadseg <<$bFunction>> #Business
}

/' kalloc.c '/
' kinit -> initlock
kinit -> freerange

/' vm.c '/
kvminit -up-> kalloc
walk -up-> kalloc
kvminit -> kvmmap
kvminithart -up-> w_satp
kvminithart -up-> sfence_vma

@enduml

系统调用#

@startuml

sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package riscv.h {
    rectangle w_satp <<$bFunction>> #Business
    rectangle sfence_vma <<$bFunction>> #Business
}
package trampoline.S {
    rectangle uservec <<$bFunction>> #Business
    rectangle userret <<$bFunction>> #Business
    rectangle trampoline <<$bFunction>> #Business
}
package kernelvec.S {
    rectangle kernelvec <<$bFunction>> #Business
    rectangle timervec <<$bFunction>> #Business
}
package trap.c {
    rectangle trapinit <<$bFunction>> #Business
    rectangle trapinithart <<$bFunction>> #Business
    rectangle usertrap <<$bFunction>> #Business
    rectangle usertrapret <<$bFunction>> #Business
    rectangle kerneltrap <<$bFunction>> #Business
    rectangle clockintr <<$bFunction>> #Business
    rectangle deintr <<$bFunction>> #Business
    rectangle tickslock <<$tGlobal>> #Technology
}
rectangle TRAPFRAME <<$tGlobal>> #Technology

/' trampoline.S '/
TRAPFRAME -down-> trampoline
trampoline - uservec
uservec -down-> usertrap

/' trap.c '/
usertrapret -up-> userret
trapinithart -up-> kernelvec

@enduml

中断#

@startuml

sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package kernelvec.S {
    rectangle kernelvec <<$bFunction>> #Business
    rectangle timervec <<$bFunction>> #Business
}
package plic.c {
    rectangle plicinit <<$bFunction>> #Business
    rectangle plicinithart <<$bFunction>> #Business
    rectangle plic_claim <<$bFunction>> #Business
    rectangle plic_complete <<$bFunction>> #Business
}
package console.c {
    rectangle consputc <<$bFunction>> #Business
    rectangle consolewrite <<$bFunction>> #Business
    rectangle consoleread <<$bFunction>> #Business
    rectangle consoleintr <<$bFunction>> #Business
    rectangle consoleinit <<$bFunction>> #Business
    frame cons {
        rectangle lock as cons.lock <<$tGlobal>> #Technology
        rectangle buf <<$tGlobal>> #Technology
        rectangle r <<$tGlobal>> #Technology
        rectangle w <<$tGlobal>> #Technology
        rectangle e <<$tGlobal>> #Technology
    }
}
package uart.c {
    rectangle uartinit <<$bFunction>> #Business
    rectangle uartputc <<$bFunction>> #Business
    rectangle uartputc_sync <<$bFunction>> #Business
    rectangle uartstart <<$bFunction>> #Business
    rectangle uartgetc <<$bFunction>> #Business
    rectangle uartintr <<$bFunction>> #Business
    rectangle uart_tx_lock <<$tGlobal>> #Technology
    rectangle uart_tx_buf <<$tGlobal>> #Technology
    rectangle uart_tx_w <<$tGlobal>> #Technology
    rectangle uart_tx_r <<$tGlobal>> #Technology
}
package printf.c {
    rectangle printfint <<$bFunction>> #Business
    rectangle printptr <<$bFunction>> #Business
    rectangle printf <<$bFunction>> #Business
    rectangle panic <<$bFunction>> #Business
    rectangle printfinit <<$bFunction>> #Business
    frame pr {
        rectangle lock as pr.lock <<$tGlobal>> #Technology
        rectangle locking <<$tGlobal>> #Technology
    }
}

/' console.c '/
consoleinit -> uartinit

@enduml

多核处理器与锁#

@startuml

sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package spinlock.h {
    frame spinlock {
        rectangle locked as spinlock.locked <<$tGlobal>> #Technology
        rectangle name as spinlock.name <<$tGlobal>> #Technology
        rectangle cpu as spinlock.cpu <<$tGlobal>> #Technology
    }
}
package spinlock.c {
    rectangle initlock <<$bFunction>> #Business
    rectangle acquire <<$bFunction>> #Business
    rectangle release <<$bFunction>> #Business
    rectangle holding <<$bFunction>> #Business
    rectangle push_off <<$bFunction>> #Business
    rectangle pop_off <<$bFunction>> #Business
}

/' spinlock.c '/
' pr.lock -up-> initlock
' cons.lock -up-> initlock
' kmem.lock -up-> initlock
' bcache.lock -up-> initlock
' icache.lock -up-> initlock
' ftable.lock -up-> initlock
' vdisk_lock -up-> initlock
' pid_lock -up-> initlock
' tickslock -up-> initlock
' uart_tx_lock -up-> initlock

@enduml

进程间切换#

@startuml
sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package proc.h {
    frame proc {
        rectangle lock as proc.lock <<$tGlobal>> #Technology
        rectangle state as proc.state <<$tGlobal>> #Technology
        rectangle chan as proc.chan <<$tGlobal>> #Technology
        rectangle parent as proc.parent <<$tGlobal>> #Technology
        rectangle killed as proc.killed <<$tGlobal>> #Technology
        rectangle xstate as proc.xstate <<$tGlobal>> #Technology
        rectangle pid as proc.pid <<$tGlobal>> #Technology
        rectangle kstack as proc.kstack <<$tGlobal>> #Technology
        rectangle sz as proc.sz <<$tGlobal>> #Technology
        rectangle pagetable as proc.pagetable <<$tGlobal>> #Technology
        rectangle trapframe as proc.trapframe <<$tGlobal>> #Technology
        rectangle context as proc.context <<$tGlobal>> #Technology
        rectangle ofile as proc.ofile <<$tGlobal>> #Technology
        rectangle cmd as proc.cmd <<$tGlobal>> #Technology
        rectangle name as proc.name <<$tGlobal>> #Technology
    }
}
package proc.c {
    rectangle cpus <<$tGlobal>> #Technology
    rectangle proc as proc.c.proc <<$tGlobal>> #Technology
    rectangle initpid <<$tGlobal>> #Technology
    rectangle nextpid <<$tGlobal>> #Technology
    rectangle pid_lock <<$tGlobal>> #Technology
    rectangle procinit <<$bFunction>> #Business
    rectangle cpuid <<$bFunction>> #Business
    rectangle mycpu <<$bFunction>> #Business
    rectangle myproc <<$bFunction>> #Business
    rectangle allocpid <<$bFunction>> #Business
    rectangle allocproc <<$bFunction>> #Business
    rectangle freeproc <<$bFunction>> #Business
    rectangle proc_pagetable <<$bFunction>> #Business
    rectangle proc_freepagetable <<$bFunction>> #Business
    rectangle userinit <<$bFunction>> #Business
    rectangle growproc <<$bFunction>> #Business
    rectangle fork <<$bFunction>> #Business
    rectangle reparent <<$bFunction>> #Business
    rectangle exit <<$bFunction>> #Business
    rectangle wait <<$bFunction>> #Business
    rectangle scheduler <<$bFunction>> #Business
    rectangle sched <<$bFunction>> #Business
    rectangle yield <<$bFunction>> #Business
    rectangle forkret <<$bFunction>> #Business
    rectangle sleep <<$bFunction>> #Business
    rectangle wakeup <<$bFunction>> #Business
    rectangle kill <<$bFunction>> #Business
    rectangle either_copyout <<$bFunction>> #Business
    rectangle either_copyin <<$bFunction>> #Business
    rectangle procdump <<$bFunction>> #Business
}
package swtch.S {
    rectangle swtch <<$bFunction>> #Business
}

/' proc.c '/
proc.c.proc .up.> proc
procinit -up-> kalloc
procinit -up-> kvmmap
procinit -up-> kvminithart
procinit ..> pid_lock : initlock
scheduler .> pid_lock : acquire/release
scheduler -up-> swtch
sched -up-> swtch
swtch .> pid_lock : release
sleep .down.> pid_lock : acquire
sleep -> sched
wakeup .> pid_lock : acquire/release

@enduml

休眠唤醒#

@startuml
sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package sleeplock.h {
    frame sleeplock {
        rectangle locked as sleeplock.locked <<$tGlobal>> #Technology
        rectangle lk as sleeplock.lk <<$tGlobal>> #Technology
        rectangle name as sleeplock.name <<$tGlobal>> #Technology
        rectangle pid as sleeplock.pid <<$tGlobal>> #Technology
    }
}
package sleeplock.c {
    rectangle initsleeplock <<$bFunction>> #Business
    rectangle acquiresleep <<$bFunction>> #Business
    rectangle releasesleep <<$bFunction>> #Business
    rectangle holdingsleep <<$bFunction>> #Business
}
package proc.h {
    frame proc {
        rectangle lock as proc.lock <<$tGlobal>> #Technology
        rectangle state as proc.state <<$tGlobal>> #Technology
        rectangle chan as proc.chan <<$tGlobal>> #Technology
        rectangle parent as proc.parent <<$tGlobal>> #Technology
        rectangle killed as proc.killed <<$tGlobal>> #Technology
        rectangle xstate as proc.xstate <<$tGlobal>> #Technology
        rectangle pid as proc.pid <<$tGlobal>> #Technology
        rectangle kstack as proc.kstack <<$tGlobal>> #Technology
        rectangle sz as proc.sz <<$tGlobal>> #Technology
        rectangle pagetable as proc.pagetable <<$tGlobal>> #Technology
        rectangle trapframe as proc.trapframe <<$tGlobal>> #Technology
        rectangle context as proc.context <<$tGlobal>> #Technology
        rectangle ofile as proc.ofile <<$tGlobal>> #Technology
        rectangle cmd as proc.cmd <<$tGlobal>> #Technology
        rectangle name as proc.name <<$tGlobal>> #Technology
    }
}
package proc.c {
    rectangle cpus <<$tGlobal>> #Technology
    rectangle proc as proc.c.proc <<$tGlobal>> #Technology
    rectangle initpid <<$tGlobal>> #Technology
    rectangle nextpid <<$tGlobal>> #Technology
    rectangle pid_lock <<$tGlobal>> #Technology
    rectangle procinit <<$bFunction>> #Business
    rectangle cpuid <<$bFunction>> #Business
    rectangle mycpu <<$bFunction>> #Business
    rectangle myproc <<$bFunction>> #Business
    rectangle allocpid <<$bFunction>> #Business
    rectangle allocproc <<$bFunction>> #Business
    rectangle freeproc <<$bFunction>> #Business
    rectangle proc_pagetable <<$bFunction>> #Business
    rectangle proc_freepagetable <<$bFunction>> #Business
    rectangle userinit <<$bFunction>> #Business
    rectangle growproc <<$bFunction>> #Business
    rectangle fork <<$bFunction>> #Business
    rectangle reparent <<$bFunction>> #Business
    rectangle exit <<$bFunction>> #Business
    rectangle wait <<$bFunction>> #Business
    rectangle scheduler <<$bFunction>> #Business
    rectangle sched <<$bFunction>> #Business
    rectangle yield <<$bFunction>> #Business
    rectangle forkret <<$bFunction>> #Business
    rectangle sleep <<$bFunction>> #Business
    rectangle wakeup <<$bFunction>> #Business
    rectangle kill <<$bFunction>> #Business
    rectangle either_copyout <<$bFunction>> #Business
    rectangle either_copyin <<$bFunction>> #Business
    rectangle procdump <<$bFunction>> #Business
}

/' proc.c '/
proc.c.proc .up.> proc

@enduml

Lost wakeup 的过程解析

@startuml
autonumber
"sleep (Runnign on core 1)" -> "sleep lock" : acquire
"sleep (Runnign on core 1)" -> "sleep lock" : release
"wakeup (Runnign on core 2)" -> "sleep lock" : acquire
"wakeup (Runnign on core 2)" -[#red]> "sleep (Runnign on core 1)" : set to RUNNABLE (lost wakeup)
"sleep (Runnign on core 1)" -> "sleep (Runnign on core 1)" : set to SLEEPING
@enduml

运行在 core 1 上的 sleep 函数将当前进程设置为 SLEEPING 状态前,被运行在 core 2 上的 wakeup 函数抢先设置成了 RUNNABLE 状态,并因此丢失了 wakeup 操作。

因为按照正常的顺序,调度器必须发现有一个 SLEEPING 状态的函数,才可以将其设置为 RUNNABLE 状态,而现在的顺序是颠倒的。

根本原因在于 sleep 函数将进程设置为 SLEEPINGrelease sleep lock 不是原子性的,这给了其他进程(在两个操作的时隙间)有了趁虚而入的机会。我们需要加一个 condition locksleeprelase lockSLEEPING 包装成原子性的操作。进而控制多线程的执行顺序。

修复后的结果应该是如下所示:

@startuml
autonumber
"sleep (Runnign on core 1)" -> "sleep lock" : acquire
group condition lock
    "sleep (Runnign on core 1)" -> "sleep lock" : release
    "sleep (Runnign on core 1)" -> "sleep (Runnign on core 1)" : set to SLEEPING
end
"wakeup (Runnign on core 2)" -> "sleep lock" : acquire
"wakeup (Runnign on core 2)" -> "sleep (Runnign on core 1)" : set to RUNNABLE
@enduml

文件系统#

@startuml
sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package bio.c {
    rectangle binit <<$bFunction>> #Business
    rectangle bget <<$bFunction>> #Business
    rectangle bread <<$bFunction>> #Business
    rectangle bwrite <<$bFunction>> #Business
    rectangle brelse <<$bFunction>> #Business
    rectangle bpin <<$bFunction>> #Business
    rectangle bunpin <<$bFunction>> #Business
    frame bcache {
        rectangle lock as bcache.lock <<$tGlobal>> #Technology
        rectangle buf <<$tGlobal>> #Technology
        rectangle head <<$tGlobal>> #Technology
    }
}
package fs.c {
    rectangle readsb <<$bFunction>> #Business
    rectangle fsinit <<$bFunction>> #Business
    rectangle bzero <<$bFunction>> #Business
    rectangle balloc <<$bFunction>> #Business
    rectangle bfree <<$bFunction>> #Business
    rectangle iinit <<$bFunction>> #Business
    rectangle iget <<$bFunction>> #Business
    rectangle ialloc <<$bFunction>> #Business
    rectangle iupdate <<$bFunction>> #Business
    rectangle idup <<$bFunction>> #Business
    rectangle ilock <<$bFunction>> #Business
    rectangle iunlock <<$bFunction>> #Business
    rectangle iput <<$bFunction>> #Business
    rectangle iunlockput <<$bFunction>> #Business
    rectangle bmap <<$bFunction>> #Business
    rectangle itrunc <<$bFunction>> #Business
    rectangle stati <<$bFunction>> #Business
    rectangle readi <<$bFunction>> #Business
    rectangle writei <<$bFunction>> #Business
    rectangle namecmp <<$bFunction>> #Business
    rectangle dirlookup <<$bFunction>> #Business
    rectangle dirlink <<$bFunction>> #Business
    rectangle skipelem <<$bFunction>> #Business
    rectangle namex <<$bFunction>> #Business
    rectangle namei <<$bFunction>> #Business
    rectangle nameiparent <<$bFunction>> #Business
    frame icache {
        rectangle lock as icache.lock <<$tGlobal>> #Technology
        rectangle inode as fs.c.inode <<$tGlobal>> #Technology
    }
    rectangle sb <<$tGlobal>> #Technology
}
package sysfile.c {
    rectangle argfd <<$bFunction>> #Business
    rectangle fdalloc <<$bFunction>> #Business
    rectangle sys_dup <<$bFunction>> #Business
    rectangle sys_read <<$bFunction>> #Business
    rectangle sys_write <<$bFunction>> #Business
    rectangle sys_close <<$bFunction>> #Business
    rectangle sys_fstat <<$bFunction>> #Business
    rectangle sys_link <<$bFunction>> #Business
    rectangle isdirempty <<$bFunction>> #Business
    rectangle sys_unlink <<$bFunction>> #Business
    rectangle create <<$bFunction>> #Business
    rectangle sys_open <<$bFunction>> #Business
    rectangle sys_mkdir <<$bFunction>> #Business
    rectangle sys_mknod <<$bFunction>> #Business
    rectangle sys_chdir <<$bFunction>> #Business
    rectangle sys_exec <<$bFunction>> #Business
    rectangle sys_pipe <<$bFunction>> #Business
}
package file.c {
    rectangle fileinit <<$bFunction>> #Business
    rectangle filealloc <<$bFunction>> #Business
    rectangle filedup <<$bFunction>> #Business
    rectangle fileclose <<$bFunction>> #Business
    rectangle filestat <<$bFunction>> #Business
    rectangle fileread <<$bFunction>> #Business
    rectangle filewrite <<$bFunction>> #Business
    frame ftable {
        rectangle lock as ftable.lock <<$tGlobal>> #Technology
        rectangle f <<$tGlobal>> #Technology
    }
}
package virtio_disk.c {
    rectangle virtio_disk_init <<$bFunction>> #Business
    rectangle alloc_desc <<$bFunction>> #Business
    rectangle free_desc <<$bFunction>> #Business
    rectangle free_chain <<$bFunction>> #Business
    rectangle alloc3_desc <<$bFunction>> #Business
    rectangle virtio_disk_rw <<$bFunction>> #Business
    rectangle virtio_disk_intr <<$bFunction>> #Business
    frame disk {
        rectangle vdisk_lock <<$tGlobal>> #Technology
        rectangle pages <<$tGlobal>> #Technology
        rectangle desc <<$tGlobal>> #Technology
        rectangle avail <<$tGlobal>> #Technology
        rectangle used <<$tGlobal>> #Technology
        rectangle free <<$tGlobal>> #Technology
        rectangle used_idx <<$tGlobal>> #Technology
        rectangle info <<$tGlobal>> #Technology
    }
}
package file.h {
    frame inode as file.h.inode {
        rectangle dev <<$tGlobal>> #Technology
        rectangle inum <<$tGlobal>> #Technology
        rectangle ref <<$tGlobal>> #Technology
        rectangle lock as inode.lock <<$tGlobal>> #Technology
        rectangle valid <<$tGlobal>> #Technology
        rectangle type <<$tGlobal>> #Technology
        rectangle major <<$tGlobal>> #Technology
        rectangle minor <<$tGlobal>> #Technology
        rectangle nlink <<$tGlobal>> #Technology
        rectangle size as inode.size <<$tGlobal>> #Technology
    }
}

/' bio.c '/
bread -> bget
bwrite -> virtio_disk_rw
fs.c.inode .up.> file.h.inode

@enduml

日志系统#

@startuml
sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package log.c {
    rectangle initlog <<$bFunction>> #Business
    rectangle install_trans <<$bFunction>> #Business
    rectangle read_head <<$bFunction>> #Business
    rectangle write_head <<$bFunction>> #Business
    rectangle recover_from_log <<$bFunction>> #Business
    rectangle begin_op <<$bFunction>> #Business
    rectangle end_op <<$bFunction>> #Business
    rectangle write_log <<$bFunction>> #Business
    rectangle commit <<$bFunction>> #Business
    rectangle log_write <<$bFunction>> #Business
    frame log {
        rectangle lock as log.lock <<$tGlobal>> #Technology
        rectangle start as log.start <<$tGlobal>> #Technology
        rectangle size as log.size <<$tGlobal>> #Technology
        rectangle outstanding as log.outstanding <<$tGlobal>> #Technology
        rectangle dev as log.dev <<$tGlobal>> #Technology
        rectangle lh as log.lh <<$tGlobal>> #Technology
    }
}

@enduml

制作文件系统#

@startuml
sprite $bFunction jar:archimate/process
sprite $aMacro jar:archimate/service
sprite $tGlobal jar:archimate/node

package PC {
    frame CPU {
        rectangle register <<$tGlobal>> #Technology
        frame memory {
            rectangle "buffer cache" <<$tGlobal>> #Technology
        }
    }
    frame disk {
        rectangle "root block" <<$tGlobal>> #Technology
        rectangle "super block" <<$tGlobal>> #Technology
        rectangle "log block" <<$tGlobal>> #Technology
        rectangle "inode blocks" <<$tGlobal>> #Technology
        rectangle "bitmap block" <<$tGlobal>> #Technology
        rectangle "data blocks" <<$tGlobal>> #Technology
    }
}

@enduml

@startuml

package mkfs.c {
    rectangle xshort <<$bFunction>> #Business
    rectangle xint <<$bFunction>> #Business
    rectangle main <<$bFunction>> #Business
    rectangle wsect <<$bFunction>> #Business
    rectangle winode <<$bFunction>> #Business
    rectangle rinode <<$bFunction>> #Business
    rectangle rsect <<$bFunction>> #Business
    rectangle ialloc <<$bFunction>> #Business
    rectangle balloc <<$bFunction>> #Business
    rectangle iappend <<$bFunction>> #Business
    rectangle nbitmap <<$tGlobal>> #Technology
    rectangle ninodeblocks <<$tGlobal>> #Technology
    rectangle nlog <<$tGlobal>> #Technology
    rectangle nmeta <<$tGlobal>> #Technology
    rectangle fsfd <<$tGlobal>> #Technology
    rectangle sb <<$tGlobal>> #Technology
    rectangle zeroes <<$tGlobal>> #Technology
    rectangle freeinodes <<$tGlobal>> #Technology
    rectangle freeblock <<$tGlobal>> #Technology
}

@enduml

@startuml

start
:检查命令行参数;
if (参数数量正确?) then (yes)
    :打开/创建 fs.img 文件;
    if (文件打开成功?) then (yes)
        :初始化变量和数据结构;
        :分配根目录 inode;
        :添加 '.' 和 '..' 条目;
        :遍历输入文件列表;
        while (有更多文件?)
            :为文件分配 inode;
            :写入文件内容到 inode;
        endwhile (yes)
        :更新根目录 inode 大小;
        :写入超级块到正确位置;
        :关闭 fs.img 文件;
        stop
    else (no)
        :打印错误信息;
        stop
    endif
else (no)
    :打印错误信息;
    stop
endif

@enduml

Makefile#

首先分析 Makefile 文件,查看它会编译哪些文件,程序入口在哪里:

这段代码是一个 Makefile 文件,用于编译和运行 xv6 操作系统的内核和用户空间程序。它定义了一系列的目标和依赖关系,以及一些变量和规则。

  1. 首先,它包含了名为 conf/lab.mk 的文件,这个文件可能包含了一些与实验相关的设置。

  2. 然后,它定义了一系列的对象文件 (OBJS),这些文件是内核的一部分,包括了不同的功能模块。

  3. 接下来,根据实验的不同,它可能会添加一些特定的对象文件到 OBJS 列表中。例如,如果实验是关于页表的,那么 vmcopyin.o 对象文件就会被添加到 OBJS 列表中。

  4. 然后,它定义了一些工具链的前缀 (TOOLPREFIX) 和一些编译器、汇编器、链接器等工具的路径。如果没有设置 TOOLPREFIX,那么它会尝试自动推断出正确的工具链前缀。

  5. 接下来,它定义了一些编译选项 (CFLAGS) 和链接选项 (LDFLAGS),这些选项包括了一些与实验相关的宏定义 (XCFLAGS)。

  6. 然后,它定义了一些用户空间程序 (UPROGS),这些程序是用户空间的一部分,包括了一些测试程序。

  7. 接下来,它定义了一些用户空间库文件 (ULIB),这些文件是用户空间程序运行时需要的库文件。

  8. 然后,它定义了一些目标,这些目标用于编译内核和用户空间程序。例如, $K/kernel 目标用于编译内核程序,而 _ % 目标用于编译用户空间程序。

  9. 最后,它定义了一些清理和打包的目标,用于删除生成的文件和创建提交的 tarball。

Kernel#

kernel.ld#

这段代码是一个链接器脚本,用于定义 RISC-V 架构的内核的内存布局和段。它使用了 GNU 链接器的语法,并包含了一系列的段定义和指令。

  1. 首先, OUTPUT_ARCH("riscv") 指定了输出文件的目标架构是 RISC-V。然后, ENTRY(_entry) 将入口点设置为 _entry 函数。

  2. 接下来, SECTIONS 块定义了各个段的属性。每个段都有一个起始地址(通过 . = <address> 指定),并且可以包含一组输入段(通过 *(.sectionname) 指定)。

  3. .text 段包含了代码段,包括 .text.text.* 两个输入段。它还包括了一个名为 trampoline 的特殊段,用于存放跳转到内核的代码。这个段的大小被设定为 0x1000 字节,以确保它正好占据一页(4KB)的空间。

  4. .rodata 段包含只读数据段,包括 .srodata.rodata 两个输入段。

  5. .data 段包含可读写数据段,包括 .sdata.data 两个输入段。

  6. .bss 段包含未初始化的全局变量,包括 .sbss.bss 两个输入段。

  7. 最后, PROVIDE(end = .) 指令将 end 符号设置为当前位置,即所有段结束的位置。

总的来说,这段代码定义了内核的内存布局,并确保了关键的段如代码段、只读数据段、可读写数据段和 BSS 段都按照预期的地址和大小进行排列。

entry.S#

这段代码是用汇编语言编写的,主要用于在 QEMU 模拟器中设置内核的启动和初始化。

_entry 标签定义了一个函数,这个函数是程序的入口点。当 QEMU 启动时,它会加载内核到 0x80000000 地址处,并让每个 CPU 跳转到这个地址。 kernel.ld 文件将这个函数放在了 0x80000000 地址处。

在这个函数中,首先设置了一个栈指针 (sp),它指向一个名为 stack0 的变量,这是在 start.c 文件中声明的,每个 CPU 都有 4096 字节的栈空间。然后,根据当前 CPU 的 ID (通过 mhartid 寄存器获取),计算出栈指针应该偏移的值,并更新栈指针。

最后,调用 start 函数,这个函数是在 start.c 文件中定义的,负责进一步的系统初始化工作。如果 start 函数返回,那么程序会进入一个无限循环,即 spin 标签所指向的位置,防止程序退出。

总的来说,这段代码的作用是初始化 QEMU 模拟器中的内核环境,并调用 start 函数来完成系统的启动和初始化。

swtch.S#

这段代码是用 RISC-V 汇编语言编写的,定义了一个名为 swtch 的函数,用于实现上下文切换。

swtch 函数接受两个参数: oldnew,它们都是指向 context 结构体的指针。这个结构体可能包含了寄存器的值,以便在上下文切换时保存和恢复这些寄存器的状态。

swtch 函数内部,首先将当前寄存器的值保存在 old 指向的内存位置中。然后,从 new 指向的内存位置加载新的寄存器值。最后,通过 ret 指令返回,使得 CPU 从新加载的寄存器状态继续执行。

注意,这里使用的寄存器是特定于 RISC-V 架构的,如 rasps0s11 等。此外, a0a1 是函数调用的第一个和第二个参数,分别对应 oldnew

kernelvec.S#

这段代码是用 RISC-V 汇编语言编写的,主要用于处理中断和异常。它定义了两个函数: kernelvectimervec

kernelvec 函数的作用是在内核模式下处理中断和异常。当发生中断或异常时,该函数会被调用。它首先将寄存器保存在栈中,然后调用 kerneltrap 函数来处理中断或异常,最后从栈中恢复寄存器并返回。

timervec 函数的作用是处理机器模式下的定时器中断。它首先读取保存在 mscratch 寄存器中的数据,然后设置下一次定时器中断的时间,并触发一个软件中断。最后,它从栈中恢复寄存器并返回。

这段代码还包含一些宏定义和其他辅助函数,但它们在此处没有给出。这些宏定义和函数可能在其他部分的代码中定义。

trampoline.S#

这段代码是用 RISC-V 汇编语言编写的,主要用于在用户空间和内核空间之间进行上下文切换。它定义了几个函数,包括 trampolineuservecuserret

  1. trampoline 函数是中断处理的入口点,当用户空间程序发生中断时,CPU 会跳转到这个函数。在这个函数中,它保存了用户寄存器的状态,并将控制权交给 usertrap 函数。

  2. uservec 函数是用户空间的中断处理函数。它首先将 a0 寄存器和 sscratch 寄存器的内容交换,然后将用户寄存器的值保存在 TRAPFRAME 中,并设置了一些必要的参数,如内核堆栈指针、内核线程 ID 等。最后,它调用 usertrap 函数来处理中断。

  3. userret 函数是从内核空间返回用户空间的函数。它首先切换到用户页表,然后恢复用户寄存器的状态,并将 TRAPFRAME 的地址保存在 sscratch 寄存器中。最后,它通过 sret 指令返回到用户空间,继续执行被中断的程序。

这段代码还包含一些宏定义和其他辅助函数,但它们在此处没有给出。这些宏定义和函数可能在其他部分的代码中定义。

start.c#

这段代码是用 C 语言编写的,主要用于初始化 RISC-V 架构的内核。它包括了一些头文件和函数定义。

  1. 首先,stack0 是一个字符数组,用于为每个 CPU 分配一个栈空间。mscratch0 是一个 64 位整型数组,用于存储定时器中断的临时数据。

  2. timervec 函数是在 kernelvec.S 文件中定义的,用于处理机器模式下的定时器中断。

  3. start 函数是程序的入口点,它设置了一些寄存器的值,以便在进入内核后能够正确地处理中断和异常。它还调用了 timerinit 函数来初始化定时器中断。最后,它通过 asm volatile("mret") 指令切换到监督模式并跳转到 main 函数。

  4. timerinit 函数负责配置定时器中断。它设置了 CLINT 定时器的比较值,并将相关信息保存在 mscratch0 数组中。然后,它设置了机器模式的中断向量表 (mtvec),并启用了机器模式的中断。

总的来说,这段代码的主要任务是初始化 RISC-V 架构的内核,包括设置中断处理、分配栈空间等。

main.c#

这段代码是用 C 语言编写的,主要用于初始化 xv6 操作系统的内核部分。它包括了一些头文件和函数定义。

  1. main 函数是程序的入口点,它在所有 CPU 上以监督模式启动。首先,它检查当前 CPU 的 ID(通过 cpuid() 函数获取)是否为 0,如果是,则表示这是第一个启动的 CPU。在这个情况下,它会执行一系列的初始化操作,如初始化控制台、内存分配器、进程表、中断处理等。然后,它设置了一个标记 started,表示内核已经启动。

  2. 如果当前 CPU 的 ID 不是 0,那么它会等待直到 started 被设置为 1,这意味着第一个 CPU 已经完成了初始化。之后,它会执行与第一个 CPU 类似的初始化操作,但跳过了那些只需要在第一个 CPU 上执行的步骤。最后,它调用 scheduler 函数来启动调度器,开始处理进程和中断。

这段代码还包含了一些条件编译指令,根据不同的实验需求,可能会启用或禁用某些功能。例如,如果定义了 LAB_PGTBLLAB_LOCK 宏,那么会初始化统计信息;如果定义了 LAB_NET 宏,那么会初始化网络相关的功能。

console.c#

这段代码是用 C 语言编写的,主要用于处理控制台输入输出。它实现了对 UART(通用异步接收/发送器)的控制,包括读取和写入字符到控制台。代码中定义了一些特殊的输入字符,如回车、退格、删除整行等,并提供了相应的处理逻辑。

  1. consputc 函数用于将一个字符发送到 UART,如果字符是回退键,则会先发送两个字符:退格和空格,然后再发送另一个退格,以实现覆盖的效果。

  2. consolewrite 函数用于将数据从用户空间复制到内核空间,并通过调用 uartputc 函数将数据发送到 UART。

  3. consoleread 函数用于从控制台读取数据,它会等待直到有输入数据到达,然后将数据复制到用户提供的缓冲区中。如果读取到了换行符或文件结束符,则表示一行输入已经完成,函数会返回。

  4. consoleintr 函数是一个中断处理函数,当 UART 接收到输入字符时会被调用。它根据不同的输入字符执行相应的处理逻辑,例如处理回退键、删除整行等操作。

  5. consoleinit 函数用于初始化控制台,包括初始化锁和 UART,并将 consolereadconsolewrite 函数与系统调用相关联。

stats.c#

这段代码是用 C 语言编写的,主要用于处理统计信息。它包括了一些头文件和函数定义。

  1. stats 结构体定义了一个缓冲区,用于存储统计信息。statscopyinstatslock 函数负责将统计信息复制到缓冲区中。

  2. statswrite 函数是一个空函数,它返回 -1,表示不允许向这个设备写入数据。

  3. statsread 函数用于从缓冲区读取统计信息。如果缓冲区为空,它会调用 statscopyinstatslock 函数来填充缓冲区。然后,它会尝试将缓冲区的内容复制到用户提供的缓冲区中,并更新偏移量。如果没有更多的数据可读,它会重置缓冲区的大小和偏移量。

  4. statsinit 函数用于初始化统计信息设备,包括初始化锁和设置读写函数。

这段代码还包含一些条件编译指令,根据不同的实验需求,可能会启用或禁用某些功能。例如,如果定义了 LAB_PGTBL 宏,那么会使用 statscopyin 函数来获取统计信息;如果定义了 LAB_LOCK 宏,那么会使用 statslock 函数来获取统计信息。

printf.c#

这段代码是用 C 语言编写的,主要用于格式化控制台输出。它包括了一些头文件和函数定义。

  1. printint 函数用于将整数转换为字符串并打印到控制台。

  2. printptr 函数用于将指针地址转换为十六进制形式并打印到控制台。

  3. printf 函数是程序中最常用的打印函数,它接受一个格式化字符串和一系列参数。它会根据格式化字符串中的占位符(如 %d%x%p%s 等)来决定如何处理后续的参数。如果遇到未知的格式化字符,它会原样打印出来。

  4. panic 函数用于在发生错误或异常时打印错误信息,并进入无限循环,以防止程序继续执行。

  5. printfinit 函数用于初始化打印锁,确保在多核环境下控制台输出的线程安全。

kalloc.c#

这段代码是用 C 语言编写的,主要用于管理物理内存。它包括了一些头文件和函数定义。

  1. kinit 函数初始化内存分配器,并调用 freerange 函数来将内核结束后的物理地址范围标记为可用。

  2. freerange 函数接受一个起始物理地址和一个结束物理地址,然后将这个范围内的所有物理页面标记为可用。

  3. kfree 函数释放一个已经分配的物理页面。它首先检查传入的地址是否符合要求(即是否为 4096 字节对齐且在有效范围内),然后将其加入到空闲列表中。

  4. kalloc 函数从空闲列表中分配一个物理页面。如果空闲列表不为空,它会返回一个可用的物理页面的地址;否则,它会返回 0,表示没有可用的物理内存。

这段代码还包含了一些宏定义和其他辅助函数,但它们在此处没有给出。这些宏定义和函数可能在其他部分的代码中定义。

vm.c#

这段代码是用 C 语言编写的,主要用于管理虚拟内存和页表。它包括了一些头文件和函数定义。

  1. kvminit 函数创建了一个直接映射的内核页表,并将 UART、VirtIO 磁盘接口、CLINT 和 PLIC 等设备的物理地址映射到虚拟地址空间中。

  2. kvminithart 函数切换硬件页表寄存器以使用内核页表,并刷新 TLB。

  3. walk 函数遍历页表以找到与给定虚拟地址对应的页表项(PTE)。如果需要分配新的页表页,则会调用 kalloc 函数来分配内存。

  4. walkaddr 函数类似于 walk 函数,但只返回与给定虚拟地址对应的物理地址。

  5. kvmmapkvmmap2 函数分别用于将内核的虚拟地址映射到物理地址。

  6. kvmpa 函数将内核的虚拟地址转换为物理地址。

  7. mappages 函数用于将一系列连续的虚拟地址映射到一系列连续的物理地址。

  8. uvmunmap 函数用于解除一系列虚拟地址的映射,并可选择释放对应的物理内存。

  9. uvmcreate 函数用于创建一个空的用户页表。

  10. kvmcreate 函数用于复制内核页表,并返回一个新的页表。

  11. uvminit 函数用于将用户初始化代码加载到用户页表的起始地址 0 处。

  12. uvmallocuvmdealloc 函数分别用于分配和释放用户内存页。

  13. uvmfree 函数用于释放用户页表及其所有关联的物理内存页。

  14. kvmfree 函数用于释放内核页表及其所有关联的物理内存页。

  15. uvmcopy 函数用于将一个父进程的页表内容复制到子进程的页表中。

  16. uvmclear 函数用于清除用户访问权限的页表项。

  17. copyoutcopyin 函数分别用于从内核向用户复制数据和从用户向内核复制数据。

  18. copyinstr 函数用于从用户向内核复制字符串。

  19. vmprint 函数用于打印页表的内容。

proc.c#

这段代码是操作系统内核的一部分,用于管理进程。它定义了一些结构体和函数来创建、销毁、复制和切换进程。

  1. procinit 函数初始化进程表,为每个进程分配一个内核栈页面。

  2. cpuid 函数返回当前 CPU 的 ID。

  3. mycpu 函数返回当前 CPU 的 cpu 结构体指针。

  4. myproc 函数返回当前进程的 proc 结构体指针。

  5. allocpid 函数分配一个新的进程 ID。

  6. allocproc 函数在进程表中查找一个未使用的进程,并对其进行初始化。

  7. freeproc 函数释放一个进程及其相关资源。

  8. proc_pagetable 函数为进程创建一个用户页表。

  9. proc_freepagetable 函数释放进程的用户页表及其关联的物理内存。

  10. userinit 函数设置第一个用户进程。

  11. growproc 函数根据需要增长或缩小进程的用户内存。

  12. fork 函数创建一个新进程,复制父进程的用户内存。

  13. reparent 函数将子进程的父进程设置为 initproc。

  14. exit 函数退出当前进程,将其状态设置为 ZOMBIE,并唤醒其父进程。

  15. wait 函数等待一个子进程退出,并返回其进程 ID。

  16. scheduler 函数是一个循环,不断选择一个可运行的进程并切换到它。

  17. sched 函数让出 CPU 给调度器。

  18. yield 函数让出 CPU,允许其他进程运行。

  19. sleep 函数让进程睡眠,直到被其他进程唤醒。

  20. wakeup 函数唤醒所有正在等待特定通道的进程。

  21. kill 函数杀死具有特定 PID 的进程。

  22. either_copyouteither_copyin 函数分别用于从用户空间或内核空间复制数据。

  23. procdump 函数打印当前所有进程的状态。

proc.h#

这段代码定义了一些结构体和全局变量,用于在 xv6 操作系统中管理进程的上下文和状态。

  1. struct context 结构体包含了保存的寄存器状态,用于在内核上下文切换时保存和恢复寄存器的值。

  2. struct cpu 结构体表示每个 CPU 的状态,包括当前运行的进程、上下文信息以及一些标志位。

  3. extern struct cpu cpus[NCPU] 声明了一个数组,用于存储每个 CPU 的状态。

  4. struct trapframe 结构体用于保存用户空间程序陷入内核时的寄存器状态,以便在内核中进行处理。

  5. enum procstate 是一个枚举类型,定义了进程可能的状态:未使用、睡眠、可运行、正在运行和僵尸状态。

  6. struct proc 结构体表示一个进程的状态和信息,包括锁、状态、父进程、等待的通道、是否被杀死、退出状态、进程 ID、内核栈地址、进程内存大小、用户页表、陷阱帧指针、上下文信息、打开的文件描述符、当前工作目录和进程名称。

这些结构体和全局变量共同构成了 xv6 操作系统中进程管理的基础。它们用于跟踪和控制进程的生命周期和执行环境。

trap.c#

这段代码是操作系统内核的一部分,用于处理中断和异常。它定义了一些函数来初始化中断处理、设置中断向量表、处理用户空间的陷阱、以及处理时钟中断等。

  1. trapinit 函数初始化一个用于同步的自旋锁 tickslock,并将 ticks 变量设置为 0。

  2. trapinithart 函数设置中断向量表 (stvec),使其指向 kernelvec 函数。

  3. usertrap 函数处理来自用户空间的陷阱,包括系统调用、中断和异常。它根据不同的原因执行不同的操作,比如系统调用、设备中断或其他异常。

  4. usertrapret 函数返回到用户空间,恢复用户寄存器的值,并跳转到 trampoline.S 中的 userret 函数。

  5. kerneltrap 函数处理来自内核代码的陷阱,包括外部中断和软件中断。它根据不同的原因执行不同的操作,比如设备中断或其他异常。

  6. clockintr 函数处理时钟中断,增加 ticks 变量的值,并唤醒等待该事件的进程。

  7. devintr 函数检查是否有外部设备中断发生,如果是,则调用相应的处理函数。如果不是,则返回 0 表示未识别的中断。

plic.c#

这段代码是操作系统内核的一部分,用于初始化和管理 RISC-V 架构的 PLIC(Platform Level Interrupt Controller)。PLIC 是一个硬件组件,用于管理中断请求 (IRQ),并将它们分配给处理器核心。

  1. plicinit 函数设置了一些中断优先级,以确保 UART0 和 VIRTIO0 设备的中断能够被启用。

  2. plicinithart 函数为当前处理器核心设置了中断使能位,并设置了该核心在 S 模式下的优先级阈值。

  3. plic_claim 函数从 PLIC 获取当前处理器核心应该服务的中断号。

  4. plic_complete 函数通知 PLIC 已经完成了对特定中断的服务。

bio.c#

这段代码实现了一个缓冲区缓存(buffer cache),用于缓存磁盘块的内容。缓冲区缓存是一个链表,包含了 buf 结构体的缓存副本。缓存减少了对磁盘的读取次数,并为使用多个进程的磁盘块提供了一个同步点。

  1. binit 函数初始化缓冲区缓存,创建一个双向链表,并初始化每个缓冲区的锁。

  2. bget 函数在缓冲区缓存中查找特定设备和块号的缓冲区。如果找到,则增加其引用计数并返回;如果未找到,则回收最近最少使用的(LRU)未使用的缓冲区。

  3. bread 函数调用 bget 来获取一个缓冲区,然后从磁盘读取数据到缓冲区中。如果缓冲区已经有效,则直接返回。

  4. bwrite 函数将缓冲区的内容写入磁盘。必须持有缓冲区的锁。

  5. brelse 函数释放一个已锁定的缓冲区。它会将缓冲区移动到最常使用的列表的头部。

  6. bpinbunpin 函数分别增加和减少缓冲区的引用计数。

fs.c#

这段代码是操作系统内核的一部分,用于实现文件系统的各种功能。它包括了对块、日志、文件和目录的管理。以下是对代码中各个函数的解释:

  1. readsb: 读取超级块,将磁盘上的超级块信息加载到内存中的 sb 结构体中。

  2. fsinit: 初始化文件系统,包括读取超级块并初始化日志。

  3. bzero: 将指定设备上的指定块清零。

  4. balloc: 分配一个新的数据块,如果没有可用的块,则会抛出错误。

  5. bfree: 释放指定设备上的指定块。

  6. ialloc: 分配一个新的 inode,并将其类型设置为指定的类型。

  7. iupdate: 将修改后的 inode 写回磁盘。

  8. iget: 获取指定设备上指定 inode 编号的 inode。

  9. idup: 增加 inode 的引用计数。

  10. ilock: 锁定指定的 inode。

  11. iunlock: 解锁指定的 inode。

  12. iput: 减少 inode 的引用计数,并在必要时释放 inode。

  13. iunlockput: 解锁并释放 inode。

  14. bmap: 获取指定 inode 的第 n 个块的地址,如果不存在则分配一个新块。

  15. itrunc: 截断指定 inode 的内容。

  16. stati: 从 inode 复制状态信息到 stat 结构体。

  17. readi: 从指定 inode 的指定偏移量读取数据。

  18. writei: 向指定 inode 的指定偏移量写入数据。

  19. namecmp: 比较两个文件名是否相等。

  20. dirlookup: 在目录中查找指定文件名的 inode。

  21. dirlink: 在目录中创建一个新的目录项。

  22. skipelem: 从路径中提取下一个路径元素。

  23. namex: 根据路径名查找或创建 inode。

  24. namei: 根据路径名查找 inode。

  25. nameiparent: 根据路径名查找父目录的 inode。

file.c#

这段代码是关于文件描述符相关系统调用的支持函数。它包括了初始化文件表、分配文件结构、复制文件结构引用、关闭文件、获取文件元数据、读取文件和写入文件的功能。

  1. fileinit 函数用于初始化文件表,包括一个自旋锁来保护对文件表的访问。

  2. filealloc 函数用于分配一个新的文件结构,如果没有可用的文件结构,则返回 0。

  3. filedup 函数用于增加文件结构的引用计数。

  4. fileclose 函数用于关闭文件,减少引用计数,并在引用计数为 0 时释放资源。

  5. filestat 函数用于获取文件的元数据,并将其复制到用户空间的地址 addr

  6. fileread 函数用于从文件中读取数据,将数据复制到用户空间的地址 addr

  7. filewrite 函数用于向文件中写入数据,从用户空间的地址 addr 读取数据。

这些函数提供了对文件描述符的基本操作,包括分配、复制、关闭、读取和写入等。它们通过操作文件表来管理文件描述符,确保了对文件的正确管理和操作。

virtio_disk.c#

这段代码是操作系统内核的一部分,用于驱动 QEMU 的 virtio 磁盘设备。它使用 QEMU 的 MMIO 接口与 virtio 设备进行通信。代码中定义了一个 disk 结构体,其中包含了虚拟磁盘的相关信息和状态。

  1. virtio_disk_init 函数用于初始化虚拟磁盘设备,包括检查设备的魔数、版本号、设备 ID 和供应商 ID,以及设置设备的特性。它还初始化了队列 0,并分配了必要的内存空间。

  2. alloc_descfree_desc 函数分别用于分配和释放描述符。

  3. alloc3_desc 函数用于分配三个连续的描述符。

  4. virtio_disk_rw 函数用于执行磁盘读写操作。它首先分配三个描述符,然后根据读写标志来填充描述符的内容。接着,它将描述符的地址和长度写入到相应的寄存器中,并通知设备开始处理请求。最后,它会等待设备完成请求后再返回。

  5. virtio_disk_intr 函数是一个中断处理函数,用于处理来自虚拟磁盘设备的中断。当设备完成一个请求时,它会调用这个函数来处理中断。在该函数中,它会检查每个请求的状态,并唤醒等待该请求完成的进程。

uart.c#

这段代码是针对 16550a UART(通用异步接收/发送器)的低级驱动程序。它包含了初始化 UART、向输出缓冲区添加字符、从输入缓冲区读取字符以及处理 UART 中断的功能。

  1. uartinit 函数用于初始化 UART,包括禁用中断、设置波特率、配置线控制寄存器和使能传输和接收中断。

  2. uartputc 函数用于将一个字符添加到输出缓冲区,并在必要时启动 UART 的发送过程。如果输出缓冲区已满,该函数会阻塞直到有空间可用。

  3. uartputc_sync 函数是一个同步版本的 uartputc,用于在不使用中断的情况下向 UART 发送字符。它会等待直到 UART 的传输保持寄存器为空,然后发送字符。

  4. uartstart 函数检查输出缓冲区是否有待发送的字符,并尝试将它们发送到 UART。如果输出缓冲区为空或 UART 忙于发送,则函数不会执行任何操作。

  5. uartgetc 函数用于从 UART 读取一个字符。如果没有字符可读,则返回 -1。

  6. uartintr 函数是一个中断处理函数,用于处理 UART 中断。它会读取接收到的字符并调用 consoleintr 来处理这些字符,同时也会调用 uartstart 来处理输出缓冲区中的字符。

这些函数共同实现了对 16550a UART 的基本操作,包括初始化、数据传输和中断处理。

spinlock.c#

这段代码实现了一个自旋锁(spinlock),用于在多核处理器环境中实现互斥访问。自旋锁是一种忙等待的同步机制,它通过不断检查锁的状态来避免线程上下文切换的开销。

  1. initlock 函数用于初始化一个自旋锁,设置其名称、初始状态为未锁定,以及记录当前持有锁的 CPU 编号。(备注:如果只有一个核且只有一个进程,根本不需要锁)

  2. acquire 函数用于获取自旋锁。它首先禁用中断以避免死锁,然后使用原子操作 __sync_lock_test_and_set 来尝试获取锁。如果锁已经被其他线程持有,则该函数将循环等待直到锁可用。成功获取锁后,它会使用 __sync_synchronize 来确保内存操作的顺序性。

  3. release 函数用于释放自旋锁。它首先检查当前线程是否持有锁,然后使用 __sync_synchronize 来确保内存操作的顺序性,最后使用 __sync_lock_release 来释放锁。

  4. holding 函数用于检查当前线程是否持有锁。它返回一个布尔值,表示锁是否被当前线程持有。

  5. push_offpop_off 函数用于管理中断的启用/禁用状态。 push_off 函数禁用中断并保存当前的中断状态,而 pop_off 函数根据嵌套层级的计数决定是否重新启用中断。

这些函数提供了一种简单且有效的同步机制,适用于需要保护共享资源的多线程程序。它们通过自旋等待而不是阻塞线程来减少线程调度和上下文切换的开销,但可能会导致 CPU 资源浪费。

sysfile.c#

这段代码是 xv6 操作系统中文件系统相关的系统调用的实现。它包括了对文件描述符的管理、文件的读写、关闭、获取状态信息、创建和删除链接、打开文件、创建目录、创建特殊设备节点、改变当前工作目录、执行程序和管道操作等功能。

  1. argfd 函数用于获取用户空间传递的文件描述符,并返回对应的文件结构体。

  2. fdalloc 函数用于为一个文件分配一个新的文件描述符。

  3. sys_dup 函数用于复制一个现有的文件描述符。

  4. sys_read 函数用于从文件中读取数据。

  5. sys_write 函数用于向文件中写入数据。

  6. sys_close 函数用于关闭一个文件描述符。

  7. sys_fstat 函数用于获取文件的状态信息。

  8. sys_link 函数用于创建一个指向同一 inode 的新链接。

  9. isdirempty 函数用于检查目录是否为空。

  10. sys_unlink 函数用于删除一个链接。

  11. create 函数用于创建一个新的文件或目录。

  12. sys_open 函数用于打开一个文件或设备。

  13. sys_mkdir 函数用于创建一个新目录。

  14. sys_mknod 函数用于创建一个特殊的设备节点。

  15. sys_chdir 函数用于改变当前工作目录。

  16. sys_exec 函数用于执行一个程序。

  17. sys_pipe 函数用于创建一个管道。

这些系统调用提供了对文件系统的基本操作,使得用户可以通过系统调用来进行文件的读写、管理文件描述符、执行程序等操作。

exec.c#

这段代码是 xv6 操作系统中执行程序的核心部分。它负责加载和执行一个 ELF 格式的可执行文件。

  1. exec 函数是执行程序的入口点。它首先打开指定的文件,并检查其 ELF 头部是否有效。然后,它为程序分配内存,并将程序的各个段加载到内存中。接着,它设置用户栈,并准备传递给程序的参数。最后,它更新进程的页表、堆栈指针和程序名称,并返回参数数量。如果在任何步骤发生错误,它会释放已分配的资源并返回 -1。

  2. loadseg 函数用于将程序的一个段加载到内存中。它遍历段中的每一页,并从文件中读取数据以填充这些页。如果成功,它返回 0;否则返回 -1。

  3. loadseg 函数中,使用了 walkaddr 函数来获取虚拟地址对应的物理地址。这是通过页表查找实现的。

  4. exec 函数中,使用了 safestrcpy 函数来安全地复制字符串,避免缓冲区溢出。

  5. exec 函数中,还包含了一些调试信息的打印,比如当进程 ID 为 1 时,打印进程的页表信息。

这个代码片段实现了从文件系统中加载程序并将其执行的功能。它涉及到了内存管理、文件 I/O 和进程控制等多个方面。

sleeplock.c#

这段代码定义了用于实现睡眠锁(sleeplock)的数据结构和相关函数。睡眠锁是一种同步机制,它允许进程在等待某个条件成立时进入睡眠状态,而不是忙等待。

  1. struct sleeplock 结构体包含了一个基本的自旋锁(spinlock)和一些额外的字段,如锁的名称、是否被持有以及持有锁的进程 ID。

  2. initsleeplock 函数用于初始化一个睡眠锁,设置其名称、初始状态为未锁定,以及记录当前持有锁的进程 ID。

  3. acquiresleep 函数用于获取一个睡眠锁。如果锁已经被其他进程持有,则当前进程将进入睡眠状态,直到锁被释放。

  4. releasesleep 函数用于释放一个睡眠锁。它会唤醒因等待该锁而处于睡眠状态的进程。

  5. holdingsleep 函数用于检查当前进程是否持有指定的睡眠锁。

这些函数提供了对睡眠锁的基本操作,使得进程可以安全地等待特定条件的成立,而不会浪费 CPU 资源。

log.c#

这段代码实现了一个简单的日志系统,用于在文件系统中记录更改。它允许并发执行的文件系统系统调用。

  1. struct logheader 结构体定义了日志头部,包含了块的数量和块号数组。

  2. struct log 结构体定义了日志本身,包括自旋锁、起始块号、大小、正在进行的系统调用数量、是否正在提交以及设备 ID 等信息。

  3. initlog 函数初始化日志,读取超级块中的日志信息,并从日志中恢复数据。

  4. install_trans 函数将已提交的块从日志复制到它们的实际位置。

  5. read_head 函数从磁盘读取日志头部到内存。

  6. write_head 函数将内存中的日志头部写入磁盘。

  7. recover_from_log 函数用于从日志中恢复数据,如果有未提交的更改,则将其应用到磁盘上。

  8. begin_op 函数在每个文件系统系统调用的开始处被调用,用于增加正在进行的系统调用数量,并在必要时等待提交。

  9. end_op 函数在每个文件系统系统调用的结束处被调用,用于减少正在进行的系统调用数量,并在所有操作完成后提交更改。

  10. write_log 函数将修改后的块从缓存复制到日志。

  11. commit 函数负责将日志中的更改写入磁盘。

  12. log_write 函数用于记录缓冲区中的更改,并将其添加到日志中。

这个日志系统的主要功能是确保文件系统的更改是原子的,即使在系统崩溃的情况下也能保持一致性。它通过将更改记录到日志中,并在需要时将这些更改应用到磁盘上来实现这一目标。

mkfs.c#

这段代码是一个简单的文件系统创建工具,它接受一个或多个文件作为输入,并将这些文件的内容写入到一个新的文件系统镜像中。这个文件系统镜像可以被操作系统加载和使用。

代码首先检查命令行参数的数量,如果没有提供足够的参数,则打印出错误信息并退出程序。然后,代码尝试打开一个名为 fs.img 的文件,用于存储文件系统镜像。如果打开失败,则打印出错误信息并退出程序。

接下来,代码初始化了一些变量和数据结构,包括超级块、空白数据块等。然后,代码遍历所有的磁盘块,并将它们写入到文件系统镜像中。

在主函数中,代码首先分配了一个 inode 给根目录,并为其添加了 “.” 和 “..” 两个条目。然后,代码遍历所有输入的文件,为每个文件分配一个 inode,并将文件内容写入到对应的 inode 中。最后,代码更新根目录的 inode 大小,并将超级块写入到文件系统镜像的正确位置。

整个过程中,代码通过调用一系列函数来实现文件系统的创建和写入操作。这些函数包括读写磁盘块、分配和释放磁盘块、读写 inode 等。

以下是按照函数功能对代码进行的分解:

  1. xshortxint 函数:这两个函数用于将主机字节序转换为小端字节序,以便在不同的平台之间传输数据时保持一致性。

  2. main 函数:这是程序的入口点。它首先检查命令行参数的数量,然后打开或创建一个文件系统镜像文件。接着,它初始化超级块和其他必要的数据结构,并将它们写入到磁盘中。然后,它遍历输入的文件列表,为每个文件分配一个 inode,并将其内容写入到磁盘中。最后,它更新根目录的 inode 大小,并将超级块写入到磁盘的正确位置。

  3. wsect 函数:这个函数用于将一个磁盘块的内容写入到文件系统镜像中。它接受一个块号和一个指向数据的指针作为参数。

  4. winode 函数:这个函数用于将一个 inode 的内容写入到文件系统镜像中。它接受一个 inode 号和一个指向 inode 数据的指针作为参数。

  5. rinode 函数:这个函数用于从文件系统镜像中读取一个 inode 的内容。它接受一个 inode 号和一个指向 inode 数据的指针作为参数。

  6. rsect 函数:这个函数用于从文件系统镜像中读取一个磁盘块的内容。它接受一个块号和一个指向数据的指针作为参数。

  7. ialloc 函数:这个函数用于分配一个新的 inode。它接受一个 inode 类型作为参数,并返回新分配的 inode 号。

  8. balloc 函数:这个函数用于分配一个新的磁盘块。它接受一个整数作为参数,表示已经使用的磁盘块数量。

  9. iappend 函数:这个函数用于将数据追加到一个 inode 中。它接受一个 inode 号、一个指向数据的指针和数据长度作为参数。