XV6源代码阅读--进程与内存管理

2019-07-14 12:09发布

由于工作和兴趣爱好的关系,接触了不少实时操作系统, 一般来说实时操作系统基本没有进程的概念了,无非是任务堆栈的切换。 一直对Linux,Windows这种带有进程的OS,很好奇,无奈,LINUX代码很庞大,很难整体把握。 所以去年一直在寻找带支持进程的OS, 要求简单,易懂,确实真找不到。最后找到了MIT教学用VX6,便深深的着迷了。 自从调试了VX6的源代码,发现用MMU来管理进程真是复杂,怪不得很少能找到支持进程的OS了。 通过参考了很多关于XV6的中文的blog,发现几乎没有对进程管理和MMU做阐述的。 于是我将去年的笔记部分公开了出来了(直接从word中黏贴出来的)。

 

进程

      进程的线程  线程堆栈/用户堆栈  进程的内核线程 内核堆栈 一个进程可能在内核线程中等待IO而导致block P->pgdir 指向进程的页表       为什么不加载到物理的0地址而是0x100000处,因为0xa0000:0x100000IO设备区    
第一个进程initcode创建
main          callkvmalloc 设置CPU内核线程的页表kpgdir          Calluserinit                    Callallocproc 初始化 用于进程内核线程执行的 PCB部分数据结构                    调用setupkvm 给进程分配页表pgdir                    调用inituvm 调用kalloc分配一个物理页mem 将虚拟地址0映射到该物理页 mappages(0, v2p(mem));为什么V2P请看内存管理 initcode拷贝该物理页                    设置PCB Tf->eip=0,表示从0地址开始执行 tf->cs 设置为SEG_UCODEDPL_USER. tf->ds ES SS设置为 SEG_UDATA DPL_USER          设置的cs会使代码段的CPL3,这样用户代码段只可以访问PTE_U的页 tf->eflags = FL_IF允许中断 tf->esp = PGSIZE  4K   用户堆栈的空间最大只有4K??? 由于堆栈由高地址向地址(即由4K0地址), 用户代码和数据都需要占据空间呀,(代码从0地址开始)
allocproc        
allocproc 创建进程都会调用allocproc 1          ptable.proc[NPROC]找到一个可用的PCB p 2          调用kalloc给进程内核线程分配物理页作为内核堆栈p->kstack,为什么是物理内存?此时和逻辑地址什么关系? 3          设置pid 4          设置内核堆栈 4.1         sp = p->kstack + KSTACKSIZE- sizeof (struct trapframe);      p->tf= sp 设置tf的地址 4.2         sp -= 4;  *(uint*)sp = trapret 设置堆栈内容 4.3         sp -= sizeof *p->context;   p->context= sp 设置context的地址 4.4         p->context->eip = forkret设置堆栈内容   注意整个过程没有记录内核的ESP? 从下图可以看到p->context就是指向ESP,而且 swtch(&cpu->scheduler,proc->context); 参数就是进程ESP     核堆 -----------  <-- p->kstack  低地址 |           | |  (empty) | |           | |    edi   | <-- 第一次p->context (核寄存器)第一元素 |    esi   | |    ebx    | |    ebp   | |    eip   | 第一次forkret函数地址, p->context的最后一元素 |           | 第一次此处为trapret(fortret束后的返回的地址).如果是alltraps,处压ESP |    edi   |<--p->tf(寄存器).alltraps压栈结(pushal的最后一reg).trapret手动开始弹出 |    esi   | |    ebp   | |    oesp  | |    ebx   | |    edx   | |    ecx   | |    eax   | 由于pushal入的第一寄存器 |    GS    | |    FS    | |    ES    | |    DS    | 处开始由alltraps   |   trapno | vectorX入的X编号,比如由vector64入的是64 |    errno | vectorX入的错误号0    trapret手动结束弹出 |    EIP   | 产生trap CPU自动压入     trapretiret弹出 |    CS    | 产生trap CPU自动压入     trapretiret弹出 |   EFLAGS | 产生trapCPU自动压入     trapretiret弹出 |    ESP   | 模式的ESP.产生trapCPU自动压入 如果已经在内核模式,就不会压入 |    SS    |<-- p->tf结构体最后.模式的SS.产生trapCPU自动压入,如果已经在内核模式,就不会压入 |---------- | 地址p->kstack +KSTACKSIZE  高地址    问题:p->context中为什么要保存这些寄存器? 注意这些寄存器是calleesave register? 被调用函数需要保存这些寄存器,所以需要压栈
第一个进程initcode运行
main          Userinit          mpmain                    Scheduler                             switchuvm(p);                             swtch(&cpu->scheduler,proc->context); 切换到用户进程的内核堆栈堆栈                               forkret                    trapret          用户空间   调度器scheduler()调用的swtch函数首先esp=p->context,然后从进程内核堆栈恢复进程的内核寄存器,即返回到forkret 函数(p->context->eip)forkret函数返回到trapret (注意fortret函数中没有局部变量,即没有堆栈操作,所以allocproc在设置内核堆栈时候将trapret放在forkret的后面)Trapret模拟trap(中断/异常/系统调用)的返回,进而返回到用户模式(trapret中会弹出EIP)0地址,寄存器值如下
eax            0x0  ecx            0x0  edx            0x0  ebx            0x0  esp            0x1000   ebp            0x0  esi            0x0  edi            0x0  eip            0x0  eflags         0x202    cs             0x23 ss             0x2b ds             0x2b es             0x2b fs             0x0  gs             0x0 
可见代码段,数据段和堆栈都位于4K之内
第一次系统调用exec
Initcode.s 首先会系统调用exec加载init程序来替换initcode进程的内存,代码 char init[] = "/init