LAB4 PartA 多处理器支持和协作多任务处理

目录

多处理器支持

应用处理器引导程序

每个CPU状态和初始化

锁定

循环调度

创建环境的系统调用(允许用户进程创建其他的进程)


在LAB4中,将在多个同时处于活动状态的用户模式环境中实施抢占式多任务处理。

首先分析一下新文件。

kern/cpu.h 多处理器支持的内核定义

        包括有处理器个数max,处理器状态;

        单个cpu信息定义:

struct CpuInfo {
	uint8_t cpu_id;                 // Local APIC ID; index into cpus[] below
	volatile unsigned cpu_status;   // The status of the CPU,cpu状态
	struct Env *cpu_env;            // The currently-running environment.cpu中运行的进程
	struct Taskstate cpu_ts;        // Used by x86 to find stack for interrupt,中断的栈,在上一节说过,会把中断信息送入内核
};
extern struct CpuInfo cpus[NCPU];    //所有cpu的数组,类似pages&anvs
extern int ncpu;                    // 系统中cpu个数
extern struct CpuInfo *bootcpu;     // 引导处理器
extern physaddr_t lapicaddr;        // 本地APIC(高级可编程中断处理器)的物理内存映射IO地址 
// Per-CPU kernel stacks每个cpu内核栈
extern unsigned char percpu_kstacks[NCPU][KSTKSIZE];

 Kern/mpconfig.c 读取多处理器配置

      多cpu的定义:

struct mp {             // floating pointer [MP 4.1]
	uint8_t signature[4];           // "_MP_"
	physaddr_t physaddr;            // phys addr of MP config table
	uint8_t length;                 // 1
	uint8_t specrev;                // [14]
	uint8_t checksum;               // all bytes must add up to 0
	uint8_t type;                   // MP system config type
	uint8_t imcrp;
	uint8_t reserved[3];
} __attribute__((__packed__));//告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐,是GCC特有的语法

        其中成员mp.physaddr指向mp config table的物理地址,多处理器配置表的定义如下,其中最后一个占位为0的数组,为数组头地址占位。

struct mpconf {         // configuration table header [MP 4.2]
	uint8_t signature[4];           // "PCMP"
	uint16_t length;                // total table length
	uint8_t version;                // [14]
	uint8_t checksum;               // all bytes must add up to 0
	uint8_t product[20];            // product id
	physaddr_t oemtable;            // OEM table pointer
	uint16_t oemlength;             // OEM table length
	uint16_t entry;                 // entry count
	physaddr_t lapicaddr;           // address of local APIC
	uint16_t xlength;               // extended table length
	uint8_t xchecksum;              // extended table checksum
	uint8_t reserved;
	uint8_t entries[0];             // table entries
} __attribute__((__packed__));
// Table entry types这些宏地址用于entries的索引
#define MPPROC    0x00  // One per processor
#define MPBUS     0x01  // One per bus
#define MPIOAPIC  0x02  // One per I/O APIC
#define MPIOINTR  0x03  // One per bus interrupt source
#define MPLINTR   0x04  // One per system interrupt source

        mpsearch1(a,len)在物理地址 addr 处的 len 字节中查找 MP 结构。

static struct mp *
mpsearch1(physaddr_t a, int len)
{
	struct mp *mp = KADDR(a), *end = KADDR(a + len);

	for (; mp < end; mp++)
		if (memcmp(mp->signature, "_MP_", 4) == 0 &&
		    sum(mp, sizeof(*mp)) == 0)//满足标记符号并且checksum==0
			return mp;
	return NULL;
}

        mpserach(),在mp可能出现的三个地址依次检查是否有mp结构。

        static struct mpconf * mpconfig(struct mp **pmp):寻找mp配置表,其中根据mp table的定义进行了一堆条件判断,最后返回找到的config table。

        mp_init():多处理器初始化:

void
mp_init(void)
{//初始信息
	struct mp *mp;
	struct mpconf *conf;
	struct mpproc *proc;
	uint8_t *p;
	unsigned int i;

	bootcpu = &cpus[0];//0号cpu为引导cpu
	if ((conf = mpconfig(&mp)) == 0)
		return;//寻找mp配置表
	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
				bootcpu = &cpus[ncpu];
			if (ncpu < NCPU) {
				cpus[ncpu].cpu_id = ncpu;
				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) {//不是多CPU则配置失败
		// 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.
	}
}

至此,多cpu的配置初始化完成。
 

Kern/lapic.c 驱动每个CPU中中断控制单元

Kern/Spinlock.c 自旋锁的实现

Kern/Sched.c 多进程调度程序

多处理器支持

      我们将让 JOS 支持“对称多处理”(SMP),这是一种多处理器模型,其中所有 CPU 对系统资源(如内存和 I/O 总线)具有同等访问权限。 尽管 SMP 中所有 CPU 的功能都相同,但在引导过程中它们可以分为两种类型:引导处理器 (BSP) 负责初始化系统和引导操作系统; 只有在操作系统启动并运行后,BSP 才会激活应用处理器(AP)。 哪个处理器是 BSP 由硬件和 BIOS 决定。 到目前为止,现有的所有 JOS 代码都已在 BSP 上运行。

         在 SMP 系统中,每个 CPU 都有一个伴随的本地 APIC (LAPIC) 单元。 LAPIC 单元负责在整个系统中传送中断。 LAPIC 还为其连接的 CPU 提供唯一标识符。 cpu_id

        在本实验中,我们使用 LAPIC 单元(在 kern/lapic.c 中)的以下基本功能:

        1. 读取 LAPIC 标识符 (APIC ID) 以判断我们的代码当前正在哪个 CPU 上运行(请参阅 cpunum())。

        2. 将 STARTUP 处理器间中断 (IPI) 从 BSP 发送到 AP 以启动其他 CPU(请参阅 lapic_startap())。

        3. 在 C 部分,我们对 LAPIC 的内置定时器进行编程以触发时钟中断以支持抢占式多任务处理(参见 apic_init())。  

        处理器使用内存映射 I/O (MMIO) 访问其 LAPIC。 在 MMIO 中,一部分物理内存硬连线到一些 I/O 设备的寄存器,因此通常用于访问内存的相同加载/存储指令可用于访问设备寄存器。在物理地址 0xA0000 处有一个 IO 孔(我们使用它来写入 VGA 显示缓冲区)。 LAPIC 位于从物理地址 0xFE000000(32MB )开始的一个洞中,所以它太高了,无法使用我们通常在 KERNBASE 的直接映射来访问。 JOS 虚拟内存映射在 MMIOBASE 上留下了 4MB 的空间,因此我们有一个地方可以映射这样的设备。 由于后面的实验会引入更多的 MMIO 区域,您将编写一个简单的函数来从该区域分配空间并将设备内存映射到它。

        关于MIMO的介绍:I/O设备被放置在内存空间而不是I/O空间。从处理器的角度看,内存映射I/O后系统设备访问起来和内存一样。内存映射IO (MMIO) 简介_Master-TJ的个人博客-CSDN博客_内存映射io

exercise1:

        mmio_map_region() :在 MMIO 区域[MMIOBASE, MMIOLIM}中保留 size 字节并在此位置映射 [pa,pa+size)。 返回保留区域的基址。 大小*不必*必须是 PGSIZE 的倍数。

void *
mmio_map_region(physaddr_t pa, size_t size)
{
	static uintptr_t base = MMIOBASE;//从哪里开始下一个区域。 最初,这是 MMIO 区域的开始。 
	//因为这是静态的,它的值将在调用 mmio_map_region 之间保留(就像 boot_alloc 中的 nextfree 一样)。 在退出声明它的代码块以后,它还能继续存在
	 uintptr_t result=base;
	size=ROUNDUP(size,PGSIZE);
	if(base+size>=MMIOLIM)
		panic("out of mmio limit!");
    boot_map_region(kern_pgdir,base,size,pa,PTE_PCD|PTE_PWT|PTE_W);//cache disable\write through
	base+=size;
	return (void*)(result);
	
}

        lapic_init()中使用了mmio_map_region(),将4K的地址映射到物理地址lapicaddr处,lapicaddr由MP初始化中给出。

应用处理器引导程序

        在启动 AP 之前,BSP 应首先收集有关多处理器系统的信息,例如 CPU 总数、它们的 APIC ID 和 LAPIC 单元的 MMIO 地址。 kern/mpconfig.c 中的 mp_init() 函数通过读取驻留在 BIOS 内存区域中的 MP 配置表来检索此信息。

        boot_aps() 函数(在 kern/init.c 中)驱动 AP 引导程序。 AP 以实模式启动,很像引导加载程序在 boot/boot.S 中的启动方式,因此 boot_aps() 将 AP 入口代码 (kern/mpentry.S) 复制到可在实模式下寻址的内存位置。与引导加载程序不同,可以控制 AP 开始执行代码的位置;我们将入口代码复制到 0x7000 (MPENTRY_PADDR),但任何未使用的、页面对齐的低于 640KB 的物理地址都可以使用。

exercise2:

ap引导的控制流程

        bootcpu在引导加载程序中已经被启动,因此剩下的被初始化的AP也需要被启动,先在实模式下启动,然后将0x7000作为入口地址,将AP入口代码复制到此处,与boot.s中的顺序相似,从16位实模式跳转到32位保护模式,然后调用mp_main(),一个一个的启动AP。为了0x7000这个地址始终能作为启动入口,该地址不存在空闲列表中。

        page_init():

size_t sign=MPENTRY_PADDR/PGSIZE;
if(i==sign){
			pages[i].pp_ref=1;
			
		}

Question1:

        MPBOOTPHYS宏:求得所给地址的物理地址。

        在boot.S中,由于尚没有启用分页机制,所以我们能够指定程序开始执行的地方以及程序加载的地址;但是,在mpentry.S的时候,由于主CPU已经处于保护模式下了,因此是不能直接指定物理地址的,给定线性地址,映射到相应的物理地址是允许的。

每个CPU状态和初始化

        在编写多处理器操作系统时,区分每个处理器私有的每个 CPU 状态和整个系统共享的全局状态很重要。 kern/cpu.h定义了大部分 per-CPU 状态,包括struct CpuInfo存储 per-CPU 变量的 。 cpunum()总是返回调用它的 CPU 的 ID,它可以用作数组的索引,cpus,宏thiscpu是当前 CPU 的struct CpuInfo.

        应该注意的每个 CPU 状态:

        每 CPU 内核堆栈
        由于多个 CPU 可以同时陷入内核,我们需要为每个处理器使用单独的内核堆栈,以防止它们干扰彼此的执行。该数组 percpu_kstacks[NCPU][KSTKSIZE]为 NCPU 的内核堆栈保留空间。

        在实验 2 中,映射了bootstack 位于KSTACKTOP 下方的称为 BSP 内核堆栈 的物理内存。类似地,在本实验中,您将每个 CPU 的内核堆栈映射到该区域,保护页面充当它们之间的缓冲区。CPU 0 的堆栈仍将从KSTACKTOP向下生长; CPU 1 的堆栈将从CPU 0 堆栈底部下方的KSTKGAP字节开始,依此类推。inc/memlayout.h显示映射布局。        

        每 CPU TSS 和 TSS 描述符
        每个 CPU 的任务状态段 (TSS) 也需要用于指定每个 CPU 的内核堆栈所在的位置。CPU i的 TSS存储在 中cpus[i].cpu_ts,相应的 TSS 描述符在 GDT 条目中定义gdt[(GD_TSS0 >> 3) + i]。kern/trap.c 中ts定义的全局变量将不再有用。

        每个 CPU 当前环境(进程)指针
        由于每个 CPU 可以同时运行不同的用户进程,我们重新定义了符号curenv来引用 cpus[cpunum()].cpu_env(或thiscpu->cpu_env),它指向当前当前CPU(运行代码的 CPU)上执行 的环境。

        每个 CPU 系统寄存器
        所有寄存器,包括系统寄存器,都是 CPU 私有的。因此,初始化这些寄存器的指令,例如lcr3(), ltr()lgdt()lidt()等,必须进行一次各CPU上执行。函数env_init_percpu() 和trap_init_percpu()就是为此目的而定义的。

exercise3:

        mem_init_mp():映射每个CPU的内核堆栈。

        在有多个cpu的情况下,可能存在两个及以上cpu进入内核态,因此需要设置内核锁以及不同的内核栈来避免混乱。

static void
mem_init_mp(void)
{
	uintptr_t va =KSTACKTOP-KSTKSIZE;
	for(int i=0;i<NCPU;i++){
		// cprintf("paddr:%x\n",PADDR(percpu_kstacks[i]));			           boot_map_region(kern_pgdir,va,KSTKSIZE,PADDR(percpu_kstacks[i]),PTE_W|PTE_P);
		va=va-(KSTKGAP+KSTKSIZE);
	}
}

exercise4:

        trap_init_percpu():使其能够初始化所有CPU的TSS段以及TSS描述符。

// Initialize and load the per-CPU TSS and IDT
void
trap_init_percpu(void)
{
	//   - 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:

	// Setup a TSS so that we get the right stack
	// when we trap to the kernel.

	thiscpu->cpu_ts.ts_esp0 = KSTACKTOP-(cpunum()*(KSTKGAP+KSTKSIZE));
	thiscpu->cpu_ts.ts_ss0 = GD_KD;
	thiscpu->cpu_ts.ts_iomb = sizeof(struct Taskstate); I/O map base address
	
	// Initialize the TSS slot of the gdt.
	gdt[(GD_TSS0 >> 3)+thiscpu->cpu_id] = SEG16(STS_T32A, (uint32_t) (&(thiscpu->cpu_ts)),
					sizeof(struct Taskstate) - 1, 0);
	gdt[(GD_TSS0 >> 3)+thiscpu->cpu_id].sd_s = 0;

	// Load the TSS selector (like other segment selectors, the
	// bottom three bits are special; we leave them 0)
	ltr(GD_TSS0+(thiscpu->cpu_id<<3));

	// Load the IDT
	lidt(&idt_pd);
}

锁定

        当前的代码在初始化 AP 在 mp_main()旋转。在让 AP 更进一步之前,我们需要首先解决多个 CPU 同时运行内核代码时的竞争条件实现这一点的最简单方法是使用大内核锁。大内核锁是一个全局锁,每当环境(进程)进入内核模式时都会持有,并在环境(进程)返回用户模式时释放。在这个模型中,用户态的环境可以在任何可用的 CPU 上并发运行,但内核态下只能运行一个环境;任何其他尝试进入内核模式的环境都被迫等待

        kern/spinlock.h声明了大内核锁,即 kernel_lock. 它还提供了lock_kernel() 和unlock_kernel(),获取和释放锁的快捷方式。您应该在四个位置应用大内核锁:

  • 在 中i386_init(),在 BSP 唤醒其他 CPU 之前获取锁。
	// Acquire the big kernel lock before waking up APs
	// Your code here:
	lock_kernel();
  • mp_main(),初始化AP后获取锁,然后调用sched_yield()在该AP上启动运行环境。
	// Now that we have finished some basic setup, call sched_yield()
	// to start running processes on this CPU.  But make sure that
	// only one CPU can enter the scheduler at a time!
	//
	// Your code here:
	lock_kernel();
	sched_yield();
  • trap(),从用户模式被困时获取锁。要确定陷阱发生在用户模式还是内核模式,请检查tf_cs.
	if ((tf->tf_cs & 3) == 3) {
		// Trapped from user mode.
		// Acquire the big kernel lock before doing any
		// serious kernel work.
		// LAB 4: Your code here.
		lock_kernel();
-----}
  • 在 中env_run() 切换到用户模式之前释放锁定。不要太早或太晚这样做,否则你会遇到竞争或僵局。
	lcr3(PADDR(e->env_pgdir));
	unlock_kernel();
	env_pop_tf(&(e->env_tf));

        关于大内核锁与自旋锁见单独分析文章。

Question2:

        在有了大内核锁的情况下,仍旧需要将不同CPU的内核栈放置在不同位置。

        『进程自旋的位置是在内核态里面的,而不是在用户态里面的,所以即使是用了大内核锁,CPU的内核栈也不能共享,而要单独独立出来。因为在_alltraps进入到trap函数的 lock_kernel()的过程中,进程已经切换到了内核态,但并没有上内核锁,此时如果有其他CPU进入内核,如果用同一个内核栈,则_alltraps中保存的上下文信息会被破坏,所以即使有大内核栈,CPU也不能用用同一个内核栈。同样的,解锁也是在内核态内解锁,在解锁到真正返回用户态这段过程中,也存在上述这种情况。』

        MIT6.828 Lab4 PartA_fang92的专栏-CSDN博客_6.828 lab4       

循环调度

        sched_yield()在新的kern/ sched.c中 负责选择一个新的环境中运行。它以循环方式顺序搜索数组envs[],从之前运行的环境之后开始(或者如果之前没有运行环境,则在数组的开头),选择它找到的第一个状态为ENV_RUNNABLE (参见inc/env.h)),并调用env_run()跳转到该环境。


// Choose a user environment to run and run it.
void
sched_yield(void)
{
	struct Env *idle;
	idle=thiscpu->cpu_env;

	// Implement simple round-robin scheduling.
	//
	// Search through 'envs' for an ENV_RUNNABLE environment in
	// circular fashion starting just after the env this CPU was
	// last running.  Switch to the first such environment found.
	//
	// If no envs are runnable, but the environment previously
	// running on this CPU is still ENV_RUNNING, it's okay to
	// choose that environment.
	//
	// Never choose an environment that's currently running on
	// another CPU (env_status == ENV_RUNNING). If there are
	// no runnable environments, simply drop through to the code
	// below to halt the cpu.

	// LAB 4: Your code here.
	envid_t next=-1;
	envid_t start=curenv?ENVX(curenv->env_id):0;
		for(int i=0;i<NENV;i++){
			next=(start+i)%NENV;
			if(envs[next].env_status==ENV_RUNNABLE){
				// thiscpu->cpu_env=&envs[next];
				env_run(&envs[next]);
					return;
			}
		}
			
	if(curenv&&(curenv->env_status==ENV_RUNNING)){//next=-1
		env_run(curenv);
			return;
	}

	sched_halt();//没有进程时停止调度
}

Question3:

        加载lcr3()寄存器后,其地址空间变为新进程的地址空间。但是因为envs是映射在内核中的,UTOP之上。所以在切换前后,对e的寻址都不会发生改变。

Question4:

       旧的进程寄存器不保存则进程无法恢复到切换前的状态。当发生进程切换时,调用sys_yield,进程从用户态转变为内核态,在_alltrap中压入当前tf的结构,然后在trap()中使curenv->env_tf = *tf。

创建环境的系统调用(允许用户进程创建其他的进程)

        尽管内核现在能够在多个用户级环境之间运行和切换,但它仍然仅限于内核最初设置的运行环境。

        Unix 提供fork()系统调用进程创建。Unix fork()复制调用进程(父进程)的整个地址空间以创建一个新进程(子进程)。用户空间中两个可观察对象之间的唯一区别是它们的进程 ID 和父进程 ID(由getpid和返回getppid)。在父fork()进程中, 返回子进程ID,而在子进程中,fork()返回0。默认情况下,每个进程都有自己的私有地址空间,并且两个进程对内存的修改对另一个都不可见。

        JOS增加新系统调用如下:

sys_exofork      创建了一个几乎空白的新环境 , 在父级中,sys_exofork 将返回envid_t新创建的环境(如果环境分配失败,则返回错误代码)。然而, sys_exofork子进程中,它将返回 0。通过eax实现子进程返回0。                                                          
sys_env_set_status        标记新进程准备好运行,一旦其地址空间和寄存器状态已完全初始化。
sys_page_alloc        分配一页物理内存并将其映射到给定进程空间中的给定虚拟地址。
sys_page_map        将页面映射(不是页面的内容!)从一个环境的地址空间复制到另一个环境,留下内存共享安排,以便新旧映射都引用物理内存的同一页面。
sys_page_unmap        取消映射在给定环境中的给定虚拟地址上映射的页面。
sys_exofork:

// Allocate a new environment.
// Returns envid of new environment, or < 0 on error.  Errors are:
//	-E_NO_FREE_ENV if no free environment is available.
//	-E_NO_MEM on memory exhaustion.
static envid_t
sys_exofork(void)
{
	// Create the new environment with env_alloc(), from kern/env.c.
	// It should be left as env_alloc created it, except that
	// status is set to ENV_NOT_RUNNABLE, and the register set is copied
	// from the current environment -- but tweaked so sys_exofork
	// will appear to return 0.
	struct Env *env_store;
	int ret=env_alloc(&env_store,thiscpu->cpu_env->env_id);//配置新进程
	if(ret<0)
		return ret;
	(env_store)->env_status=ENV_NOT_RUNNABLE;//新进程不可运行

	env_store->env_tf=thiscpu->cpu_env->env_tf;//copy父进程的陷阱帧
	
	env_store->env_tf.tf_regs.reg_eax=0;//return 0 for new env

	return (env_store)->env_id;//对于父进程返回子进程的id

}
sys_env_set_status:
// Set envid's env_status to status, which must be ENV_RUNNABLE
// or ENV_NOT_RUNNABLE.
//
// Returns 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if status is not a valid status for an environment.
static int
sys_env_set_status(envid_t envid, int status)
{
	// Hint: Use the 'envid2env' function from kern/env.c to translate an
	// envid to a struct Env.
	// You should set envid2env's third argument to 1, which will
	// check whether the current environment has permission to set
	// envid's status.
	if(status!=ENV_NOT_RUNNABLE&&status!=ENV_RUNNABLE)//||
		return -E_INVAL;
	struct Env * env_store;
	int ret=envid2env(envid,&env_store,1);
	if(ret<0)
		return -E_BAD_ENV;
	(env_store)->env_status=status;

	return 0;
}
sys_page_alloc:
// Allocate a page of memory and map it at 'va' with permission
// 'perm' in the address space of 'envid'.
// The page's contents are set to 0.
// If a page is already mapped at 'va', that page is unmapped as a
// side effect.
//
// perm -- PTE_U | PTE_P must be set, PTE_AVAIL | PTE_W may or may not be set,
//         but no other bits may be set.  See PTE_SYSCALL in inc/mmu.h.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if va >= UTOP, or va is not page-aligned.
//	-E_INVAL if perm is inappropriate (see above).
//	-E_NO_MEM if there's no memory to allocate the new page,
//		or to allocate any necessary page tables.
static int
sys_page_alloc(envid_t envid, void *va, int perm)
{
	// Hint: This function is a wrapper around page_alloc() and
	//   page_insert() from kern/pmap.c.
	//   Most of the new code you write should be to check the
	//   parameters for correctness.
	//   If page_insert() fails, remember to free the page you
	//   allocated!
	struct PageInfo* pp=page_alloc(1);//分配1页物理内存
	if(!pp)
	return -E_NO_MEM;
	struct Env* env_store;
	int ret=envid2env(envid,&env_store,1);
	if(ret<0)
		return -E_BAD_ENV;
	if((uintptr_t)va>=UTOP||(uintptr_t)va%PGSIZE!=0||(perm&(PTE_U|PTE_P))!=(PTE_U|PTE_P))//perm&(PTE_U|PTE_P))!=perm)
		return -E_INVAL;
	int ret1=page_insert((env_store)->env_pgdir,pp,va,perm);//建立物理页面与虚拟页面的映射
	if(ret1!=0)
		{page_free(pp);//映射失败需要释放物理页面
			return -E_NO_MEM;
		}

	return 0;

}

sys_page_map: 将页面映射(不是页面的内容!)从一个进程的地址空间复制到另一个进程,因此正确的做法是使两个虚拟地址中的页面映射到相同的物理地址。
// Map the page of memory at 'srcva' in srcenvid's address space
// at 'dstva' in dstenvid's address space with permission 'perm'.
// Perm has the same restrictions as in sys_page_alloc, except
// that it also must not grant write access to a read-only
// page.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if srcenvid and/or dstenvid doesn't currently exist,
//		or the caller doesn't have permission to change one of them.
//	-E_INVAL if srcva >= UTOP or srcva is not page-aligned,
//		or dstva >= UTOP or dstva is not page-aligned.
//	-E_INVAL is srcva is not mapped in srcenvid's address space.
//	-E_INVAL if perm is inappropriate (see sys_page_alloc).
//	-E_INVAL if (perm & PTE_W), but srcva is read-only in srcenvid's
//		address space.
//	-E_NO_MEM if there's no memory to allocate any necessary page tables.
static int
sys_page_map(envid_t srcenvid, void *srcva,
	     envid_t dstenvid, void *dstva, int perm)
{
	// Hint: This function is a wrapper around page_lookup() and
	//   page_insert() from kern/pmap.c.
	//   Again, most of the new code you write should be to check the
	//   parameters for correctness.
	//   Use the third argument to page_lookup() to
	//   check the current permissions on the page.
	struct Env* env_store1,*env_store2;
	struct PageInfo* ppsrc;
	pte_t* pte;
	if(envid2env(srcenvid,&env_store1,1)!=0||envid2env(dstenvid,&env_store2,1)!=0)
		return -E_BAD_ENV;
	if((uintptr_t)srcva>=UTOP||((uintptr_t)srcva%PGSIZE!=0))
		return -E_INVAL;
	if((uintptr_t)dstva>=UTOP||((uintptr_t)dstva%PGSIZE!=0))
		return -E_INVAL;
	if(!(ppsrc=page_lookup(env_store1->env_pgdir,srcva,&pte)))//找到va1映射的物理地址
		return -E_INVAL;
	if((perm&PTE_P)==0||(perm&PTE_U)==0||(perm&~PTE_SYSCALL)!=0)//permif((perm&(PTE_U|PTE_P))!=(PTE_U|PTE_P))//perm
		return -E_INVAL;
	if((perm&PTE_W)&&(*pte&PTE_W)==0)
		return -E_INVAL;
	int ret;
	if((ret=page_insert((env_store2)->env_pgdir,ppsrc,dstva,perm))<0)//将va2映射到对应的物理地址
		return -E_NO_MEM;

	return 0;
 }

sys_page_unmap:完成以后不要忘记在syscallno中添加调用。
// Unmap the page of memory at 'va' in the address space of 'envid'.
// If no page is mapped, the function silently succeeds.
//
// Return 0 on success, < 0 on error.  Errors are:
//	-E_BAD_ENV if environment envid doesn't currently exist,
//		or the caller doesn't have permission to change envid.
//	-E_INVAL if va >= UTOP, or va is not page-aligned.
static int
sys_page_unmap(envid_t envid, void *va)
{
	// Hint: This function is a wrapper around page_remove().
	struct Env* env_store;
	if(envid2env(envid,&env_store,1)<0)
		return -E_BAD_ENV;
	if((uintptr_t)va>=UTOP||(uintptr_t)va%PGSIZE!=0)
		return -E_INVAL;
	page_remove((env_store)->env_pgdir,va);
	// LAB 4: Your code here.
	// panic("sys_page_unmap not implemented");
	return 0;
}


以上就是LAB4partA的内容,主要实现了多CPU的支持以及多进程的调度处理。省去了对lapic.c以及大内核锁,自旋锁的分析,之后有机会补上。:)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值