#include #include #include #include #include #include #include #include #include #include #include #include #include // 标志着内核是否处理完成 bool init_kernel; // 指向当前进程pcb的指针 PROCESS *p_proc_ready; // pcb表 PROCESS proc_table[PCB_SIZE]; static inline void init_segment_regs(PROCESS *p_proc) { p_proc->pcb.user_regs.cs = (SELECTOR_FLAT_C & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_USER; p_proc->pcb.user_regs.ds = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_USER; p_proc->pcb.user_regs.es = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_USER; p_proc->pcb.user_regs.fs = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_USER; p_proc->pcb.user_regs.ss = (SELECTOR_FLAT_RW & SA_RPL_MASK & SA_TI_MASK) | SA_TIL | RPL_USER; p_proc->pcb.user_regs.gs = (SELECTOR_VIDEO & SA_RPL_MASK & SA_TI_MASK) | RPL_USER; } static inline void lml_map(phyaddr_t cr3, uintptr_t vaddr, phyaddr_t paddr, u32 flags) { uintptr_t *pde_ptr = (uintptr_t *)K_PHY2LIN(cr3); // pde_ptr += PDX(3 * GB); // get page table base addr // get pdx item, pde_ptr + PDX(vaddr) pde_ptr += PDX(vaddr); if (!(*pde_ptr & PTE_P)) { // pdx ot exists, allocate a page and assign to pde *pde_ptr = phy_malloc_4k() | PTE_P | flags; } uintptr_t *ptx_ptr = (uintptr_t *)K_PHY2LIN(*pde_ptr & (~0xfff)); // get ptx item ptx_ptr += PTX(vaddr); // assume paddr is allocated, just set it to paddr[31:12] | FLAGS *ptx_ptr = (paddr & (~0xfff)) | PTE_P | flags; } /* * 内核的main函数 * 用于初始化用户进程,然后将执行流交给用户进程 */ void kernel_main(void) { kprintf("---start kernel main---\n"); PROCESS *p_proc = proc_table; for (int i = 0 ; i < PCB_SIZE ; i++, p_proc++) { // 初始化进程段寄存器 init_segment_regs(p_proc); // 为进程分配cr3物理内存 p_proc->pcb.cr3 = phy_malloc_4k(); // 将3GB~3GB+128MB的线性地址映射到0~128MB的物理地址 map_kern(p_proc->pcb.cr3); // 在map_kern之后,就内核程序对应的页表已经被映射了 // 就可以直接lcr3,于此同时执行流不会触发page fault // 如果不先map_kern,执行流会发现执行的代码的线性地址不存在爆出Page Fault // 当然选不选择看个人的想法,评价是都行,各有各的优缺点 // lcr3(p_proc->pcb.cr3); static char filename[PCB_SIZE][12] = { "TESTPID BIN", "TESTKEY BIN", }; // 从磁盘中将文件读出,需要注意的是要满足短目录项的文件名长度11, // 前八个为文件名,后三个为后缀名,跟BootLoader做法一致 // 推荐将文件加载到3GB + 48MB处,应用程序保证不会有16MB那么大 read_file(filename[i], (void *)K_PHY2LIN(48 * MB)); // 现在你就需要将从磁盘中读出的ELF文件解析到用户进程的地址空间中 // panic("unimplement! load elf file"); // ---------------------------- load elf -------------------------------------- void * elf_start = (void *)K_PHY2LIN(48 * MB); assert(*(u32*)elf_start == ELF_MAGIC); Elf32_Ehdr* ehdr = (Elf32_Ehdr*) elf_start; for (int i = 0; i < ehdr->e_phnum; ++ i) { Elf32_Phdr* phdr = elf_start + i * ehdr->e_phentsize + ehdr->e_phoff; if (phdr->p_type == PT_LOAD) { u32 vpage_fst = phdr->p_va & (~0xfff); // clear low 12 bit, start of the first pg u32 vpage_lst = (phdr->p_va + phdr->p_memsz - 1) & (~0xfff); // start of the last pg // in fact, we allocate 4k aligned vpage to every segment; its ok because elf32 segment align is 0x1000 u32 page_num = ((vpage_lst - vpage_fst) >> 12) + 1; phyaddr_t newpage = 0; // useless initialization for (int j = 0; j < page_num; ++ j) { newpage = phy_malloc_4k(); lml_map(p_proc->pcb.cr3, vpage_fst + (j << 12), newpage, PTE_P | PTE_W | PTE_U); } phyaddr_t page_start = newpage - (page_num - 1) * 4 * KB; memcpy((void*)K_PHY2LIN(page_start), (void*)(elf_start + phdr->p_offset), phdr->p_filesz); memset((void*)K_PHY2LIN(page_start) + phdr->p_filesz, 0, phdr->p_memsz - phdr->p_filesz); } } // 上一个实验中,我们开栈是用内核中的一个数组临时充当栈 // 但是现在就不行了,用户是无法访问内核的地址空间(3GB ~ 3GB + 128MB) // 需要你自行处理给用户分配用户栈。 // ---------------------------- alloc user stack ------------------------------ phyaddr_t ustack_low = phy_malloc_4k(); for (int i = 0; i < 6; ++ i) phy_malloc_4k(); phyaddr_t ustack_high = phy_malloc_4k(); // alloc 8 pages, 32KB stack for each program for (int i = 0; i < 8; ++ i) { lml_map(p_proc->pcb.cr3, (3*GB - (8-i)*PGSIZE), ustack_low + i*PGSIZE, PTE_P | PTE_W | PTE_U); } p_proc->pcb.user_regs.esp = 3*GB; // p_proc->pcb.user_regs.esp -= 4; // uintptr_t* ustack_top = (uintptr_t*)K_PHY2LIN(ustack_high + PGSIZE); // ustack_top --; // *ustack_top = ehdr->e_entry; p_proc->pcb.user_regs.eip = ehdr->e_entry; // 初始化用户寄存器 p_proc->pcb.user_regs.eflags = 0x1202; /* IF=1, IOPL=1 */ // panic("unimplement! init user stack and esp"); // 接下来初始化内核寄存器, // 为什么需要初始化内核寄存器原因是加入了系统调用后 // 非常有可能出现系统调用执行过程中插入其余中断的情况, // 如果跟之前一样所有进程共享一个内核栈会发生不可想象的结果, // 为了避免这种情况,就需要给每个进程分配一个进程栈。 // 当触发时钟中断发生调度的时候,不再是简单的切换p_proc_ready, // 而是需要将内核栈进行切换,而且需要切换执行流到另一个进程的内核栈。 // 所以需要一个地方存放当前进程的寄存器上下文,这是一个富有技巧性的活,深入研究会觉得很妙, // 如果需要深入了解,去查看kern/process.c中的schedule函数了解切换细节。 p_proc->pcb.kern_regs.esp = (u32)(p_proc + 1) - 8; // 保证切换内核栈后执行流进入的是restart函数。 *(u32 *)(p_proc->pcb.kern_regs.esp + 0) = (u32)restart; // 这里是因为restart要用`pop esp`确认esp该往哪里跳。 *(u32 *)(p_proc->pcb.kern_regs.esp + 4) = (u32)&p_proc->pcb; // 初始化其余量 p_proc->pcb.pid = i; static int priority_table[PCB_SIZE] = {1, 2}; // priority 预计给每个进程分配的时间片 // ticks 进程剩余的进程片 p_proc->pcb.priority = p_proc->pcb.ticks = priority_table[i]; } p_proc_ready = proc_table; // 切换进程页表和tss lcr3(p_proc_ready->pcb.cr3); tss.esp0 = (u32)(&p_proc_ready->pcb.user_regs + 1); init_kernel = true; // 开个无用的kern_context存当前执行流的寄存器上下文(之后就没用了,直接放在临时变量中) struct kern_context idle; switch_kern_context(&idle, &p_proc_ready->pcb.kern_regs); assert(0); }