216 lines
8.2 KiB
C
216 lines
8.2 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>
|
||
#include <kern/syscall.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);
|
||
// 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",
|
||
"SNAKE BIN",
|
||
"HACK 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; // high to low stack
|
||
p_proc->pcb.user_regs.eip = ehdr->e_entry; // refer to restart:
|
||
// 初始化用户寄存器
|
||
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};
|
||
// 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);
|
||
}
|
||
|
||
static inline void
|
||
sys_mmap(phyaddr_t cr3_src, phyaddr_t cr3_dst, uintptr_t va_src, uintptr_t va_dst)
|
||
{
|
||
uintptr_t *pde_ptr_src = (uintptr_t *)K_PHY2LIN(cr3_src);
|
||
uintptr_t *pde_ptr_dst = (uintptr_t *)K_PHY2LIN(cr3_dst);
|
||
// its quite okay, kernel space is mapped to all processes
|
||
pde_ptr_src += PDX(va_src);
|
||
pde_ptr_dst += PDX(va_dst);
|
||
assert((*pde_ptr_dst & PTE_P) && (*pde_ptr_src & PTE_P));
|
||
// it is not a map procedure, so pde should be present
|
||
uintptr_t *pte_ptr_src = (uintptr_t *)K_PHY2LIN(*pde_ptr_src & (~0xfff));
|
||
uintptr_t *pte_ptr_dst = (uintptr_t *)K_PHY2LIN(*pde_ptr_dst & (~0xfff));
|
||
pte_ptr_src += PTX(va_src);
|
||
pte_ptr_dst += PTX(va_dst);
|
||
assert((*pte_ptr_dst & PTE_P) && (*pte_ptr_src & PTE_P));
|
||
// make should page table entry also exists
|
||
*pte_ptr_dst = *pte_ptr_src;
|
||
// simple assign src to dst, no need to recycle, though should be
|
||
}
|
||
|
||
#define PG_MASK 0x0fff // 12bit mask
|
||
|
||
ssize_t do_mmap(int pid, void *src, void *dst, size_t length)
|
||
{
|
||
if ((length & PG_MASK) || ((u32)src & PG_MASK) || ((u32)dst & PG_MASK))
|
||
{
|
||
// make sure src,dst,len are 4K aligned
|
||
assert(0);
|
||
return -1;
|
||
}
|
||
if (pid > PCB_SIZE)
|
||
{
|
||
assert(0);
|
||
return -1;
|
||
}
|
||
disable_int();
|
||
for (int off = 0; off < length; off += PGSIZE)
|
||
{
|
||
sys_mmap(proc_table[pid].pcb.cr3, p_proc_ready->pcb.cr3, (uintptr_t)src + off, (uintptr_t)dst + off);
|
||
}
|
||
enable_int();
|
||
return 0;
|
||
} |