xv6源码阅读

Talk is cheap. Show me the code.

调试篇

  • 执行make -nB qemu | vim -,在vim中执行:set nowrap,然后利用全局替换%s/ /\r /g将所有的空格替换为换行加空格,即可提取出编译时的选项,包括库的依赖等等
  • .gdbinit文件会在每次gdb时都会默认执行的命令,比如symbol-file都可以放在里面。在用vscode调试时,要将target remote注释掉,使用gdb调试时就恢复即可
  • sscratch中存的是trapframe的地址,在trampoline中与a0寄存器交换。最后会将sscratch中的a0的值赋给t0,然后存入对应a0在trapframe中的位置,然后再从trapframe中读取相关的内核信息(这一部分在trapframe中是不变的),比如读内核栈指针(将用户栈指针切换为内核栈指针)、读取coreid、读取系统调用的入口地址、读取内核页表地址然后和satp交换(用户页表切换为内核页表)。进入usertrap之后还需要保存用户的PC。
  • 就像trap一样,需要一个trampoline来保存现场。因此进程之间的上下文切换,每个进程的proc中就需要有一个trapframe结构保存当前进程的上下文

启程

CPU

  • 结构。noff指的是锁嵌套的层数,intena指的是push_off之前的中断状态,1表示开中断
    1
    2
    3
    4
    5
    6
    struct cpu {
    struct proc *proc; // The process running on this cpu, or null.
    struct context context; // swtch() here to enter scheduler().
    int noff; // Depth of push_off() nesting.
    int intena; // Were interrupts enabled before push_off()?
    };

    SpinLock

  • 自旋锁底层通过test_and_set不断地轮询是否能获取锁(占用CPU资源)
  • 结构
    1
    2
    3
    4
    5
    6
    7
    8
    // Mutual exclusion lock.
    struct spinlock {
    uint locked; // Is the lock held?

    // For debugging:
    char *name; // Name of lock.
    struct cpu *cpu; // The cpu holding the lock.
    };
  • initlock初始化传入的参数lk锁
    1
    2
    3
    4
    5
    6
    7
    void
    initlock(struct spinlock *lk, char *name)
    {
    lk->name = name;
    lk->locked = 0; // 1为持有锁
    lk->cpu = 0; // 初始化为NULL
    }
  • holding。检查是否当前CPU持有锁
    1
    2
    3
    4
    5
    6
    7
    int
    holding(struct spinlock *lk)
    {
    int r;
    r = (lk->locked && lk->cpu == mycpu());
    return r;
    }
  • acquire。获取锁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    void
    acquire(struct spinlock *lk)
    {
    push_off(); // 关中断并记录中断前的中断状态
    if(holding(lk)) // 若当前CPU已经持有该自旋锁,则报错
    panic("acquire");

    // (amoswap, atomic memory operation:swap doubleword)原子双字交换
    // On RISC-V, sync_lock_test_and_set turns into an atomic swap:
    // a5 = 1
    // s1 = &lk->locked
    // amoswap.w.aq a5, a5, (s1)
    while(__sync_lock_test_and_set(&lk->locked, 1) != 0) // 不断test直到锁未被持有(i.e. locked=0)结束while循环
    ;

    // On RISC-V, this emits a fence instruction.
    __sync_synchronize(); // 阻止编译器和处理器重排ld和st指令

    lk->cpu = mycpu(); // 记录获取锁的CPU
    }
  • release。释放锁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    void
    release(struct spinlock *lk)
    {
    if(!holding(lk))
    panic("release");

    lk->cpu = 0; // 把持有锁的CPU置为NULL

    // On RISC-V, this emits a fence instruction.
    __sync_synchronize();

    // Release the lock, equivalent to lk->locked = 0.
    // This code doesn't use a C assignment, since the C standard
    // implies that an assignment might be implemented with
    // multiple store instructions.
    // On RISC-V, sync_lock_release turns into an atomic swap:
    // s1 = &lk->locked
    // amoswap.w zero, zero, (s1)
    __sync_lock_release(&lk->locked); // 原子交换,将lk->locked置为0,C函数赋值不是原子的(会调用很多个st指令)

    pop_off(); // 减少锁的嵌套相关信息
    }
  • push_off。记录锁的嵌套信息,必须得调用intr_off()关中断避免死锁
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void
    push_off(void)
    {
    // 记录关中断前的中断状态
    int old = intr_get(); // 获取状态寄存器的值判断中断使能位是否为1,返回使能位

    intr_off(); // 关闭中断,对状态寄存器进行逻辑操作
    if(mycpu()->noff == 0) // 如果无锁嵌套则将关中断前的中断状态记录到当前core中
    mycpu()->intena = old;
    mycpu()->noff += 1; // 当前core的锁嵌套数加1
    }
  • pop_off。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void
    pop_off(void)
    {
    struct cpu *c = mycpu();
    if(intr_get())
    panic("pop_off - interruptible"); // 如果当前加锁状态下未关中断,则报错
    if(c->noff < 1)
    panic("pop_off"); // 如果未加锁嵌套状态下解锁,则报错
    c->noff -= 1;
    if(c->noff == 0 && c->intena) // 如果无锁嵌套,且在加锁之前是中断使能状态,则打开中断
    intr_on();
    }

    UART

  • QEMU模拟的UART中的控制寄存器的访问是存储器映射I/O方式,在xv6物理地址空间中的起始地址是0x10000000其中包括8个I/O寄存器顺序从放在该地址起始处,具体寄存器值得设置参见
  • uartinit
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    void
    uartinit(void)
    {
    // disable interrupts.
    WriteReg(IER, 0x00);

    // special mode to set baud rate.
    WriteReg(LCR, LCR_BAUD_LATCH);

    // LSB for baud rate of 38.4K.
    WriteReg(0, 0x03);

    // MSB for baud rate of 38.4K.
    WriteReg(1, 0x00);

    // leave set-baud mode,
    // and set word length to 8 bits, no parity.
    WriteReg(LCR, LCR_EIGHT_BITS);

    // reset and enable FIFOs.
    WriteReg(FCR, FCR_FIFO_ENABLE | FCR_FIFO_CLEAR);

    // enable transmit and receive interrupts.
    WriteReg(IER, IER_TX_ENABLE | IER_RX_ENABLE);

    initlock(&uart_tx_lock, "uart"); // 初始化uart串口的锁
    }

    CONSOLE

  • 结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    struct {
    struct spinlock lock;

    // input
    #define INPUT_BUF 128
    char buf[INPUT_BUF];
    uint r; // Read index
    uint w; // Write index
    uint e; // Edit index
    } cons;
  • consoleinit
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    void
    consoleinit(void)
    {
    initlock(&cons.lock, "cons"); // 初始化cons中的自旋锁

    uartinit(); // 初始化I/O控制寄存器

    // connect read and write system calls
    // to consoleread and consolewrite.
    // devsw结构体中存储着设备读写函数,devsw数组的索引为设备号,CONSOLE为1
    devsw[CONSOLE].read = consoleread;
    devsw[CONSOLE].write = consolewrite;
    }