持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天,点击查看活动详情
留了好多篇博客补起来真的很痛苦 今天接着写一下CPU初始化和状态的一些内容
每个CPU状态和初始化
当编写多处理器的OS时,区分每个处理器的CPU状态和整个系统共享的全局变量是十分重要的。在kern/cpu.h中定义了大多数的CPU状态,包括存储cpu变量的CpuInfo结构。cpunum()总是返回调用它的CPU的ID,这个ID可以用作cpus数组的索引。同时,注意thiscpu是当前CPU的结构CpuInfo的简写。
以下是应该注意的perCPU 状态:
-
perCPU 内核堆栈。
由于多个 CPU 可以同时捕获到内核中,因此我们需要为每个处理器提供单独的内核堆栈,以防止它们干扰彼此的执行。数组percpu_kstacks[NCPU][KSTKSIZE]为 NCPU 的内核堆栈保留空间。在lab2中,我们映射了bootstack下方称为 BSP 内核堆栈的物理内存。同样,在本练习中,您将每个 CPU 的内核堆栈映射到此区域,并使用保护页充当它们之间的缓冲区。CPU 0 的堆栈仍将从KSTACKTOP开始增长;而CPU 1 的堆栈将在 CPU 0 的堆栈底部下方启动KSTKGAP字节,依此类推。可以在inc/memlayout.h查看映射布局。
-
perCPU 的 TSS 和 TSS 描述符。
还需要一个每 CPU 任务状态段 (TSS) 来指定每个 CPU 的内核堆栈所在的位置。CPU i 的 TSS 存储在cpus[i].cpu_ts中,相应的 TSS 描述符在 GDT 条目gdt[(GD_TSS0 >> 3) + i]中定义。在 kern/trap.c中定义的全局变量ts将不再有用。 -
perCPU 当前环境指针。
由于每个 CPU 可以同时运行不同的用户进程,因此我们重新定义了符号curenv以引用 cpus[cpunum()].cpu_env(或 thiscpu->cpu_env),它指向当前 CPU(运行代码的 CPU)上当前执行的环境。 -
perCPU 系统寄存器。
所有寄存器(包括系统寄存器)都是 CPU 专用的。因此,初始化这些寄存器的指令(如lcr3()、ltr()、lgdt()、lidt() 等)必须在每个 CPU 上执行一次。为此定义了函数env_init_percpu()和trap_init_percpu()。 -
除此之外,如果您在解决方案中添加了任何额外的每 CPU 状态或执行了任何其他特定于 CPU 的初始化(例如,在 CPU 寄存器中设置新位)以挑战早期实验室中的问题,请确保在此处的每个 CPU 上复制它们!
Exercise 3. Modify mem_init_mp() (in kern/pmap.c) to map per-CPU stacks starting at KSTACKTOP, as shown in inc/memlayout.h. The size of each stack is KSTKSIZE bytes plus bytes of unmapped guard pages. Your code should pass the new check in check_kern_pgdir().
static void
mem_init_mp(void)
{
for (int i = 0; i < NCPU; i++) {
boot_map_region(kern_pgdir, KSTACKTOP - KSTKSIZE - i * (KSTKSIZE + KSTKGAP), KSTKSIZE, PADDR(percpu_kstacks[i]), PTE_W);
}
}
从KSTACKTOP开始映射每一个CPU的堆栈,最多支持NCPU个cpu。对于CPUi,使用percpu_kstacks[i]所指向的物理内存作为其内核堆栈,并每次向下扩展KSTKGAP。并且将内存分为两部分(就像设置单个堆栈一样):
[kstacktop_i - KSTKSIZE, kstacktop_i)由物理内存支持
[kstacktop_i - (KSTKSIZE + KSTKGAP), kstacktop_i - KSTKSIZE)无支持,如果溢出将出错而不是覆盖下一个CPU的页,被称为保护页
Exercise 4. The code in trap_init_percpu() (kern/trap.c) initializes the TSS and TSS descriptor for the BSP. It worked in Lab 3, but is incorrect when running on other CPUs. Change the code so that it can work on all CPUs. (Note: your new code should not use the global variable ts any more.)
void
trap_init_percpu(void)
{
// Hints:
// - The macro "thiscpu" always refers to the current CPU's
// struct CpuInfo;
// - The ID of the current CPU is given by cpunum() or
// thiscpu->cpu_id;
// - Use "thiscpu->cpu_ts" as the TSS for the current CPU,
// rather than the global "ts" variable;
// - Use gdt[(GD_TSS0 >> 3) + i] for CPU i's TSS descriptor;
// - You mapped the per-CPU kernel stacks in mem_init_mp()
// - Initialize cpu_ts.ts_iomb to prevent unauthorized environments
// from doing IO (0 is not the correct value!)
thiscpu->cpu_ts .ts_esp0 = (uintptr_t) percpu_kstacks[cpunum()];
thiscpu->cpu_ts.ts_ss0 = GD_KD;
thiscpu->cpu_ts.ts_iomb = sizeof(struct Taskstate);
// Initialize the TSS slot of the gdt.
gdt[(GD_TSS0 >> 3) + cpunum()] = SEG16(STS_T32A, (uint32_t) (&(thiscpu->cpu_ts)),
sizeof(struct Taskstate) - 1, 0);
gdt[(GD_TSS0 >> 3) + cpunum()].sd_s = 0;
// Load the TSS selector (like other segment selectors, the
// bottom three bits are special; we leave them 0)
ltr(GD_TSS0 + (cpunum() << 3));
// Load the IDT
lidt(&idt_pd);
}
这里的任务比较复杂,首先我们要对TSS进行初始化,对cpu_ts中的ts_esp0(指向内核堆栈),ts_ss0(特权级),ts_iomb(IO起始地址)进行初始化。根据提示,使用gdt[(GD_TSS0 >> 3) + i]作为第i个cpu的TSS槽。这里使用SEG16宏读取TSStype的thiscpu->cpu_ts并设置sd_s位为0用来指示是系统级的描述符表。然后加载TSS(使用ltr,会将TSS设置一个busy位,如果不小心将一个TSS指向多个CPU,你就会陷入”triple fault“),这里和其他段加载一样,把特殊的低三位空出,最后加载中断描述符表。
When you finish the above exercises, run JOS in QEMU with 4 CPUs using make qemu CPUS=4 (or make qemu-nox CPUS=4), you should see output like this:
...
Physical memory: 66556K available, base = 640K, extended = 65532K
check_page_alloc() succeeded!
check_page() succeeded!
check_kern_pgdir() succeeded!
check_page_installed_pgdir() succeeded!
SMP: CPU 0 found 4 CPU(s)
enabled interrupts: 1 2
SMP: CPU 1 starting
SMP: CPU 2 starting
SMP: CPU 3 starting