mit 6.828 lab4-抢占式多任务

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值