xv6的多核启动过程

两种代码的差异性(public无法启用多核)

xv6-public的代码我无法启用多核,即使修改了CPUS数量也无法使用。

下面第一张图便是public在CPUS=4下运行的情况。
Public CPUS=4

第二张图为RISC-V的多核启动CPUS=3的情况。
RISC-V CPUS=3

在我修改public的Makefile中关于qemu的参数后(增添了-smp cores=$(CPUS)),依旧未成功。

关于startothers的解析

startothers的作用

在多处理器系统中,操作系统启动时通常只有一个处理器(称为bootstrap processor,BSP)是活跃的,它负责初始化系统,包括内核和硬件的基本设置。一旦BSP完成了基础的初始化工作,它就会通过startothers函数来启动其他的处理器(称为application processors,APs)。

  1. 定位内核入口startothers首先需要确定内核的入口点,这样每个处理器在启动时都可以从相同的位置开始执行。
  2. 为每个AP设置栈空间:由于每个处理器都需要自己的栈来执行函数调用和处理中断,startothers会为每个AP分配并设置栈空间。
  3. 发送启动信号:使用启动内核(Startup Inter-Processor Interrupt,SIPI)通过APIC(Advanced Programmable Interrupt Controller)向每个AP发送信号,告诉它们开始执行。
  4. 等待APs启动startothers可能会等待所有APs确认它们已经启动并且准备好接收调度任务。

public中的startothers

使用grep在public的代码中查找startothers(下面发现这个函数的定义在main.c中)

❯ grep -rnw '.' -e 'startothers'
grep: ./main.o: binary file matches
./entryother.asm:87:  # Switch to the stack allocated by startothers()
./kernel.asm:6071:  xchg(&(mycpu()->started), 1); // tell startothers() we're up
./kernel.asm:6212:  kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
grep: ./xv6.img: binary file matches
./main.c:9:static void startothers(void);
./main.c:34:  startothers();   // start other processors
./main.c:35:  kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
./main.c:56:  xchg(&(mycpu()->started), 1); // tell startothers() we're up
./main.c:64:startothers(void)
grep: ./kernel: binary file matches
./entryother.S:70:  # Switch to the stack allocated by startothers()

startothers的代码解析

// Start the non-boot (AP) processors.
static void
startothers(void)
{
  extern uchar _binary_entryother_start[], _binary_entryother_size[];
  uchar *code;
  struct cpu *c;
  char *stack;

  // Write entry code to unused memory at 0x7000.
  // The linker has placed the image of entryother.S in
  // _binary_entryother_start.
  code = P2V(0x7000);
  memmove(code, _binary_entryother_start, (uint)_binary_entryother_size);

  for(c = cpus; c < cpus+ncpu; c++){
    if(c == mycpu())  // We've started already.
      continue;

    // Tell entryother.S what stack to use, where to enter, and what
    // pgdir to use. We cannot use kpgdir yet, because the AP processor
    // is running in low  memory, so we use entrypgdir for the APs too.
    stack = kalloc();
    *(void**)(code-4) = stack + KSTACKSIZE;
    *(void(**)(void))(code-8) = mpenter;
    *(int**)(code-12) = (void *) V2P(entrypgdir);

    lapicstartap(c->apicid, V2P(code));

    // wait for cpu to finish mpmain()
    while(c->started == 0)
      ;
  }
}

疑惑:why do we need to use entrypgdir for APs?(上文蓝色段)

entryother.S中的关于startothers部分(完成启动过程中的初始化)

  # Switch to the stack allocated by startothers()
  movl    (start-4), %esp
  # Call mpenter()
  call   *(start-8)

startothers函数:这是在操作系统的主C代码(main.c)中定义的一个函数,负责逐个发送STARTUP IPI给每个AP,并且将特定的启动代码复制到内存地址0x7000处。此外,它还在启动代码的前几个字节处设置了一些关键的地址信息:

  • start-4:新分配的每核栈的地址。
  • start-8:跳转目标地址(mpenter函数的地址)。
  • start-12:入口页目录(entrypgdir)的物理地址。

PS: 0x7000 用于存放操作系统在初始化多处理器系统时用到的特定代码

riscv中的多核启动

xv6-riscv的代码无法追踪到多核的启动函数,类似于public中的startothers。

说实话并不知道具体多核是怎样启动的,但是使用gdb调试可以发现,在运行sheduler之前多核并未启动,运行后,多核启动,操作系统正常运行。

image

Hey!

If you have any non-algorithmic questions about the code, send me a message and I will be happy to help. I also hope to make more like-minded friends.