Introduction
实验分为三个部分,
partA:实现多处理器的支持,实现轮转调度,增加基本的进程管理系统调用。
partB:实现unix-like的fork(),允许用户进程去创建它自己的拷贝。
partC:实现进程间通信的支持,允许不同的进程进行通信和同步,增加对clock interrupts和抢占式的支持。
Part A
在进程初始化的时候我们,我们要把每个cpu也初始化一下
void
env_init_percpu(void)
{
lgdt(&gdt_pd);
// The kernel never uses GS or FS, so we leave those set to
// the user data segment.
asm volatile("movw %%ax,%%gs" : : "a" (GD_UD|3));
asm volatile("movw %%ax,%%fs" : : "a" (GD_UD|3));
// The kernel does use ES, DS, and SS. We'll change between
// the kernel and user data segments as needed.
asm volatile("movw %%ax,%%es" : : "a" (GD_KD));
asm volatile("movw %%ax,%%ds" : : "a" (GD_KD));
asm volatile("movw %%ax,%%ss" : : "a" (GD_KD));
// Load the kernel text segment into CS.
asm volatile("ljmp %0,$1f\n 1:\n" : : "i" (GD_KT));
// For good measure, clear the local descriptor table (LDT),
// since we don't use it.
lldt(0);
}
初始化每个cpu的gdt寄存器,gs,fs,都设置位GD_UD(用户数据段);
es,ds,ss都设置位GD_KD(内核数据段);
cs设置为GD_KT,(N内核代码段);
最后将ldtr清零。
同样给进行trap初始化的时候,我们使用trap_init_percpu,
void
trap_init_percpu(void)
{
// The example code here sets up the Task State Segment (TSS) and
// the TSS descriptor for CPU 0. But it is incorrect if we are
// running on other CPUs because each CPU has its own kernel stack.
// Fix the code so that it works for all CPUs.
//
// 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!)
//
// ltr sets a 'busy' flag in the TSS selector, so if you
// accidentally load the same TSS on more than one CPU, you'll
// get a triple fault. If you set up an individual CPU's TSS
// wrong, you may not get a fault until you try to return from
// user space on that CPU.
//
// LAB 4: Your code here:
size_t i=cpunum();
thiscpu->cpu_ts.ts_esp0=KSTACKTOP-i*(KSTKSIZE+KSTKGAP);
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)+i]= SEG16(STS_T32A, (uint32_t) (&thiscpu->cpu_ts),
sizeof(struct Taskstate) - 1, 0);
gdt[(GD_TSS0 >>3)+i].sd_s = 0;
// Setup a TSS so that we get the right stack
// when we trap to the kernel.
// Load the TSS selector (like other segment selectors, the
// bottom three bits are special; we leave them 0)
ltr(GD_TSS0+sizeof(struct Segdesc)*i);
// Load the IDT
lidt(&idt_pd);
}
GD_TSS0是0x28,每个Segdesc的大小是8字节,TSS0的偏移量是5个段,最开始有个null段,内核代码段,内核数据段,用户代码段,用户数据段,5*8=32=0x28,
我们接下来调用mp_init()->mpconfg();
void
mp_init(void)
{
struct mp *mp;
struct mpconf *conf;
struct mpproc *proc;
uint8_t *p;
unsigned int i;
bootcpu = &cpus[0];
if ((conf = mpconfig(&mp)) == 0)//从BIOS'S的区域内存读取mpconfig表,将其保存在conf,
return;
ismp = 1;
lapicaddr = conf->lapicaddr;
for (p = conf->entries, i = 0; i < conf->entry; i++) {
switch (*p) {
case MPPROC:
proc = (struct mpproc *)p;
if (proc->flags & MPPROC_BOOT)
bootcpu = &cpus[ncpu];//全局变量ncpu默认初始化为0
if (ncpu < NCPU) {
cpus[ncpu].cpu_id = ncpu;//每次+1,并且把对应id设置为ncpu,如0,1,2,3
ncpu++;
} else {
cprintf("SMP: too many CPUs, CPU %d disabled\n",
proc->apicid);
}
p += sizeof(struct mpproc);
continue;
case MPBUS:
case MPIOAPIC:
case MPIOINTR:
case MPLINTR:
p += 8;
continue;
default:
cprintf("mpinit: unknown config type %x\n", *p);
ismp = 0;
i = conf->entry;
}
}
bootcpu->cpu_status = CPU_STARTED;
if (!ismp) {
// Didn't like what we found; fall back to no MP.
ncpu = 1;
lapicaddr = 0;
cprintf("SMP: configuration not found, SMP disabled\n");
return;
}
cprintf("SMP: CPU %d found %d CPU(s)\n", bootcpu->cpu_id, ncpu);
if (mp->imcrp) {
// [MP 3.2.6.1] If the hardware implements PIC mode,
// switch to getting interrupts from the LAPIC.
cprintf("SMP: Setting IMCR to switch from PIC mode to symmetric I/O mode\n");
outb(0x22, 0x70); // Select IMCR
outb(0x23, inb(0x23) | 1); // Mask external interrupts.
}
}
我们通过mp_init()返回后,设置了lapicaddr(address of local APIC),知道了有几个cpu,
接着调用lapic_init(),将[lapicaddr,lapicaddr+4096)映射到虚拟地址。将返回的虚拟地址保存在lapic里,通过设置LAPIC的一系列寄存器的值初始化LAPIC.
然后调用pic_init(),这个函数的作用是初始化8259A中断控制器,
最后bootcpu接着调用boot_aps()去启动各个应用处理器,将处理器设置为运行mpentry.S的入口代码,
static void
boot_aps(void)
{
extern unsigned char mpentry_start[], mpentry_end[];
void *code;
struct CpuInfo *c;
// Write entry code to unused memory at MPENTRY_PADDR
code = KADDR(MPENTRY_PADDR);
memmove(code, mpentry_start, mpentry_end - mpentry_start);
// Boot each AP one at a time
for (c = cpus; c < cpus + ncpu; c++) {
if (c == cpus + cpunum()) // We've started already.
continue;
// Tell mpentry.S what stack to use
mpentry_kstack = percpu_kstacks[c - cpus] + KSTKSIZE;
// Start the CPU at mpentry_start
lapic_startap(c->cpu_id, PADDR(code));
// Wait for the CPU to finish some basic setup in mp_main()
while(c->cpu_status != CPU_STARTED)
;
}
}
会告诉每个cpu它的mpentry_kstack在哪,然后调用lapic_startap
上面的暂时一知半解****************************
下次再看*****************************************
自旋锁
这个简单,先看看struct spinlock
struct spinlock {
unsigned locked;
} // Is the lock held?
是不是非常简单,结构体里面就只有一个unsigned变量。
再看看内核锁的定义
struct spinlock kernel_lock;
如何上锁呢?!!!
lock_kernel(void)
{
spin_lock(&kernel_lock);
}
void
spin_lock(struct spinlock *lk)
{
while (xchg(&lk->locked, 1) != 0)
asm volatile ("pause");
}
static inline uint32_t
xchg(volatile uint32_t *addr, uint32_t newval)
{
uint32_t result;
// The + in "+m" denotes a read-modify-write operand.
asm volatile("lock; xchgl %0, %1"
: "+m" (*addr), "=a" (result)
: "1" (newval)
: "cc");
return result;
}
xchg会返回原来的lockedd的值,1,表明锁被别人拿了,使用pause指令,pause指令是一条在x86处理器中引入的特殊指令,它用于在自旋等待的过程中降低CPU的功耗和温度。在自旋等待的过程中,CPU会不断地检查某个条件是否满足,这会导致CPU频繁地访问内存和执行指令,从而产生大量的功耗和热量。pause指令的作用是让CPU暂停执行一段时间,以便其他线程有机会占用CPU资源,同时也能降低CPU的功耗和温度。 具体来说,pause指令会将处理器的执行状态设置为暂停状态,并在一段时间后恢复执行状态。这个时间通常是一个时钟周期(一般为几十纳秒),可以通过处理器的特定寄存器进行调整。在执行pause指令期间,处理器会进入一个低功耗状态,从而降低功耗和温度。 总之,pause指令是一种优化程序性能和降低系统功耗的方法,在多线程程序中使用较为常见。
在xchg函数里面的内联汇编代码有两条指令**,一条lock指令,在x86架构中,lock指令是一种特殊的前缀指令,用于对内存地址执行原子操作。它可以与其他指令一起使用,例如add、sub、inc、dec等,将这些指令变成原子操作,以确保在多核CPU中不会出现竞态条件。 具体来说,当一个CPU核心执行带有lock前缀的指令时,它会向总线发送一个锁定信号,告诉其他CPU核心在这段时间内不能访问同一内存地址。然后该核心执行指令,并在执行完成后释放锁定信号。这样可以避免多个CPU核心同时访问同一内存地址而导致的数据不一致问题。
LOCK指令前缀功能如下:
被修饰的汇编指令成为“原子的”
与被修饰的汇编指令一起提供内存屏障效果。
另外一条就是xchgl指令**,xchgl指令会将第一个操作数和第二个操作数的值进行交换,然后将交换后的值存储回第一个操作数中。在执行过程中,xchgl指令会将内存锁定,以确保操作的原子性。
解释:我们上锁的时候,用1跟锁里面的内容进行交换,并且把锁里面的内容返回回去。
假如锁没有被上,那么锁里面是0,result里面保存的就是0,我们返回的就是0,就不会运行asm(“pause”)指令,
假如上锁的时候,锁已经被上了,锁里面就是1,这个时候1跟1交换,result里面存储的还是1,返回的就是1,就会运行asm(“pause”)等待锁的持有者释放锁。
[1]Multitasking