171 lines
7.0 KiB
C
171 lines
7.0 KiB
C
#include <assert.h>
|
||
#include <mmu.h>
|
||
#include <string.h>
|
||
#include <type.h>
|
||
#include <x86.h>
|
||
#include <elf.h>
|
||
|
||
#include <kern/fs.h>
|
||
#include <kern/kmalloc.h>
|
||
#include <kern/stdio.h>
|
||
#include <kern/pmap.h>
|
||
#include <kern/process.h>
|
||
#include <kern/protect.h>
|
||
#include <kern/trap.h>
|
||
|
||
// 标志着内核是否处理完成
|
||
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",
|
||
"DELAY BIN",
|
||
"DELAY BIN",
|
||
"DELAY 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, 1, 1};
|
||
// priority 预计给每个进程分配的时间片
|
||
// ticks 进程剩余的进程片
|
||
p_proc->pcb.priority = p_proc->pcb.ticks = priority_table[i];
|
||
p_proc->pcb.target_tick = 0;
|
||
}
|
||
|
||
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);
|
||
} |