process_xv6

process_xv6

2023年8月2日发(作者:)

第二次代码阅读报告——process    一、 重要函数或语句的代码分析和注释 1. kfree()——将v指向的页加入到list中     void kfree(char *v) {   struct run *r;//页list的元素 //当v不再1MB和PHYSTOP范围内时报错。   if(((uint) v) % PGSIZE || (uint)v < 1024*1024 || (uint)v >= PHYSTOP)      panic("kfree");  //将内存中的byte都赋值为1,使得当读垃圾的时候,避免读//到上一个程序的有效值。最好是代码就此崩溃。   memset(v, 1, PGSIZE);   acquire(&); //将释放的页表加入list中,并且在第一个位置。   r = (struct run *) v;   r‐>next = st;   st = r;   release(&); } 2. kalloc()——分配页表 char* kalloc() {   struct run *r;   acquire(&); //返回list中的第一个页,将kmem赋值为第二个页   r = st;   if(r)     st = r‐>next;   release(&);   return (char*) r; } 3. kinit()——初始化free list的物理页 //系统假设机器有16MB的物理内存,使用内核的end到//PHYSTOP(16MB)作为初始化的空闲内存。 kinit(void) { //end是内核数据段之后的物理页   extern char end[];   initlock(&, "kmem"); //#define PGROUNDUP(sz) (((sz)+PGSIZE−1) & ~(PGSIZE−1)) //作用是将sz转化为所对应的页表的下一个页表起始地址。 //PGROUNDUP确保仅释放以页为单位的空间。   char *p = (char*)PGROUNDUP((uint)end); //将1MB‐16MB的物理地址加入到list中   for( ; p + PGSIZE ‐ 1 < (char*) PHYSTOP; p += PGSIZE)     kfree(p); } 4. kvmalloc()—创建供内核使用的页表。 kvmalloc(void) { //调用setupkvm()并且用kpgdir储存返回的页表的指针   kpgdir = setupkvm(); } 5. setupkvm()——创建供内核使用的页表。 setupkvm(void) {   pde_t *pgdir;   //创建页目录,为页目录分配内存,失败返回0.   if(!(pgdir = (pde_t *) kalloc()))     return 0; //将页目录内存初始化为0   memset(pgdir, 0, PGSIZE); //将内核将会使用的内存进行映射。这些映射均将每个虚拟//地址映射到相同的物理地址。映射包括内核的指令,数据,//和到PHYSTOP的物理内存,还有IO设备。但是并不对进 //程的内存进行映射。   if( //640K B‐1MB映射IO设备      !mappages(pgdir, USERTOP, PTE_W) ||      //映射内核和空闲内存      !mappages(pgdir, (void *)0x100000, (void *)USERTOP, 0x60000, PHYSTOP‐0x100000, 0x100000, PTE_W) ||      // 映射设备.      !mappages(pgdir, (void *)0xFE000000, 0x2000000, 0xFE000000, PTE_W))     return 0;   return pgdir; } 6. mappages()——将一个页表的处于某个范围的虚拟地址映射到相应的物理地址。 static int mappages(pde_t *pgdir, void *la, uint size, uint pa, int perm) { //

#define PGROUNDDOWN(a) (((a)) & ~(PGSIZE−1)) //将a转化为a所在页表的起始地址。   char *a = PGROUNDDOWN(la);   char *last = PGROUNDDOWN(la + size ‐ 1); //以页为单位,对每个范围中的虚拟地址进行映射   while(1){ //调用walkpgdir()找到应该对应到的PTE的地址。     pte_t *pte = walkpgdir(pgdir, a, 1);     if(pte == 0)       return 0;     if(*pte & PTE_P)       panic("remap"); //初始化PTE。     *pte = pa | perm | PTE_P;     if(a == last)       break;     a += PGSIZE;     pa += PGSIZE;   }   return 1; } 7. walkpgdir()——找到应该对应到的PTE的地址。 walkpgdir(pde_t *pgdir, const void *va, int create) {   uint r;   pde_t *pde;   pte_t *pgtab; //

#define PDX(la) ((((uint) (la)) >> PDXSHIFT) & 0x3FF) // #define PDXSHIFT 22  //PDX()的作用是取la的高10位虚拟地址,用来找到PDE   pde = &pgdir[PDX(va)];   if(*pde & PTE_P){   //如果PDE是有效的 //#define PTE_ADDR(pte) ((uint) (pte) & ~0xFFF) //PTE_ADDR()是将后10位的bit置为0 //pgtab中储存指向页表的指针     pgtab = (pte_t*) PTE_ADDR(*pde);   } else if(!create || !(r = (uint) kalloc()))  //给r分配一页     return 0;   else {     pgtab = (pte_t*) r;     // 将pgtab指向的页均初始化为0     memset(pgtab, 0, PGSIZE);         *pde = PADDR(r) | PTE_P | PTE_W | PTE_U;   } //// #define PTX(la) ((((uint) (la)) >> PTXSHIFT) & 0x3FF) // #define PTXSHIFT 12 //取的是la虚拟地址的11‐20位,用来找到PTE。 //返回的是对应的PTE的地址。   return &pgtab[PTX(va)]; } 8. struct proc——进程的数据结构 struct proc {   uint sz;                    // 进程内存大小(字节)   pde_t* pgdir;                //进程的页表指针   char *kstack;       // 进程的内核栈,指向栈的底部   enum procstate state;        // 进程的状态   volatile int pid;            //进程的ID   struct proc *parent;         // 父进程   struct trapframe *tf;    //当前系统调用的trap frame   struct context *context;   // 运行进程的时候转到这   void *chan;                  // 如果不为0,休眠   int killed;                  // 如果不为0,则死亡   struct file *ofile[NOFILE];  struct inode *cwd;              char name[16];              }; 9. allocproc()——在进程表中分配一个proc,初始化内核线程执行所需要的某些状态 static struct proc* allocproc(void) {   struct proc *p;   char *sp;   acquire(&); //搜索进程表,找到一个状态是UNUSED的进程。   for(p = ; p < &[NPROC]; p++)     if(p‐>state == UNUSED)       goto found;   release(&);   return 0;  found: //将进程的状态设置为EMBRYO   p‐>state = EMBRYO; //给予一个唯一的ID   p‐>pid = nextpid++;   release(&);    // 分配内存的分的栈空间  if((p‐>kstack> = kalloc()) === 0){     p‐>state> = UNUSED;     retturn 0;   }  //KSTACCKSIZE 内核栈的大大小。   sp = p‐>kstack + KSTACKSSIZE;      //为trapt framee预留空间间   sp ‐= sizeof *p‐>tf; trapframee*)sp;   p‐>tf = (struct t   //建立新的context来执行forkret //返回到trapret   sp ‐= 4;   *(uint*)sp = (uint)trapret;    sp ‐= sizeof *p‐>context;   p‐>context = (struct context*)sp;   memset(p‐>context, 0, sizeof *p‐>context);   p‐>context‐>eip = (uint)forkret;   return p; } 10. userinit()——执行第一个进程 userinit(void) {   struct proc *p; //第一个程序保存在这里,储存二进制代码的位置和大小。  extern char_ binary_initcode_start[], _binary_initcode_size[];     p = allocproc();   initproc = p; //调用setupkvm()用来创建一个仅仅内核使用的页表。   if(!(p‐>pgdir = setupkvm()))     panic("userinit: out of memory?"); // 调用inituvm()分配一页物理内存,将虚拟地址0映射 //到该内存,将二进制代码拷贝到该页中。 inituvm(p‐>pgdir, _binary_initcode_start, (int)_binary_initcode_size);   p‐>sz = PGSIZE;   memset(p‐>tf, 0, sizeof(*p‐>tf)); //将trap frame赋值为原始的用户模式状态 //%cs包含一个SEG_UCODE段选择器,优先级是//DPL_USER,其他的类似。   p‐>tf‐>cs = (SEG_UCODE << 3) | DPL_USER;   p‐>tf‐>ds = (SEG_UDATA << 3) | DPL_USER;   p‐>tf‐>es = p‐>tf‐>ds;   p‐>tf‐>ss = p‐>tf‐>ds;   p‐>tf‐>eflags = FL_IF;   p‐>tf‐>esp = PGSIZE;   p‐>tf‐>eip = 0;  // initcode.S的开头    safestrcpy(p‐>name, "initcode", sizeof(p‐>name));   p‐>cwd = namei("/"); //将进程的状态赋值为RUNNABLE。   p‐>state = RUNNABLE; } 11. scheduler()——执行进程 void scheduler(void) {   struct proc *p;    for(;;){        sti();       acquire(&); //找到一个状态是RUNNABLE的进程(只能是initproc)     for(p = ; p < &[NPROC]; p++){       if(p‐>state != RUNNABLE)         continue; //将per‐cpu变量proc设置为p。       proc = p; //调用switchuvm()使硬件开始使用目标进程的页表。       switchuvm(p);       p‐>state = RUNNING; //context转换到目标进程的内核线程。Swtch保存当前 //寄存器并且将保存的目标内核线程(proc‐>context)的寄 //存器装载到x86的硬件寄存器中,包括栈指针和指令指针。       swtch(&cpu‐>scheduler, proc‐>context);       switchkvm();        proc = 0;    } );;     release(&pta   } } 二、

内在页氏管理和和进程的生生命周期流流程分析 表存储的two‐levell tree结构构 1. 页表 A. 目的的:page hardwaree用页表将将线性地址址转换成物物理地址 B. 逻辑辑结构:页表在逻辑页辑上是2^220个PTE组成的数数组。每个PTE(32bit3)包含20bit的的物理地址,和一一些flag(见图)。 C. 具体体实现:页表的具体页体实现是是two‐level tree结构构。树的根根是40996byte的页目录,页目录包包含1024个PDE((类似PTEE的结构构)。PDEE指向页表表页(40096byte)。每个页表表含有1024个PTE。Page hardwaree将虚拟地地址高位的的10bit选选择一个PDE,用PPN替代。用接替接下来的101个选择择一个PTEE,并用PPNP并用替代代。如果PDE或PPTE中的任任何一个PTE_P没没有设置,则Pagge hardwaare将返回回错误。D. 细节:细PTE__P 设置后后代表有效效PTE_W 控制是否否允许指令令对页进行行写操作PTE_UP是允允许进程程使用页。 2. 进程程内存的实实现  A. 进程的结结构:每个进进程都有有一个独立立的页表进进程的内存存从0开始并且可以有160页。 B. 低位内存映射:xv6设立PTE将虚拟地址和所分配的实际地址连接起来。并且设立flag,对于少于160页的进程,xv6将未使用的页的PTE_P清空。 C. 高位内存映射:对于高于160页的虚拟地址,进程的页表将其直接映射到物理地址(这使得找到物理内存变的简单)。但是xv6没有设置PTE_U,所以只有内核可以使用它们。例如:内核可以使用它自己的指令和数据(虚拟地址和物理地址都起始在1MB处)。而且内核还可以读写数据段之后的物理内存。 D. 高位映射的具体实现:进程所用的内存从0开始,可以最高达到KERNBASE(2GB)。Xv6将高于KERNBASE:KERNBASE+PHYSTOP的虚拟地址映射到0:PHYSTOP(16MB)。 E. 细节:每个进程的页表都同时包含进程的内存和内核的内存的映射。这使得系统在进程和内核之间切换的时候不用转换页表。所以,大多数内核是没有自己的页表的。 3. 进程的内存分配 A. 内存管理:xv6维护物理内存使得可以在程序运行时动态分配内存。 B. 管理内存的位置:使用在内核数据段之后的一页物理内存。 C. 管理方式:xv6对页进行内存管理。Xv6存储一个可用页的线性链链表。分配配页时,将页从线性将性链表中删删除,清空空页时,将页页加入到线线性链表中中。 D. 动态分配配内存:当当运行sbrrk时,假假设现在进进程的大小小为12MB(0x30000)。假设Xv66找到空空闲的物理理页,地址址为0x2010000.为了保持持进程内存存的连续续性,找到到的物理页页虚拟地址为为0x30000.这时(仅在这时时)xv6使使用pagging hardwaree将一个虚虚拟地址转化到一一个不同的的物理地址址。Xv6修改PTE(包含地址从0x3000‐0x3ffff)指向物物理含虚拟地000高位的的20bit),并且设置置flag。这这样页0x201(0x2010能够使用从从0开始16MB1的连连续内存。。 进程就能程的生命周周期分析析 4. 进程 A. 通常的进进程都是父父进程通过过系统调用用fork()创创建的。foork()调用后将将创建子进进程的描述述符和进程程ID,在在子进程中中复制父进程程的进程描描述符,子进程使用子用父进程的的内存,在父在进程的地址空间中运行。 B. exec()系统调用将在子进程的地址空间中复制新的程序数据。由于共享相同的地址空间,写入新的程序数据将会导致内存的页错误,因此,在这种情况下,Linux内核将分配新的物理页给子进程。一般情况下,子进程执行自己的程序,避免了复制完整地址空间的低效率操作。 C. 当程序执行完成时,子进程通过系统调用exit()终止进程。exit()系统调用释放子进程相应的资源,并发送信号给父进程,通知子进程的终止。在这个时刻,子进程被称为僵尸进程。父进程通过系统调用wait()接收子进程的终止信号。当父进程接收到该信号,删除子进程所有的数据结构,并释放子进程的进程描述符,这个时候子进程才被完全删除。 三、 阅后心得 本次代码的阅读主要是内存的组织和进程有关的部分。其中有很多的函数是用汇编语言实现的,所以遇到了这些函数,就无法明白函数的具体实现过程。所以,要是想彻底的明白xv6系统的化,还是应该具体的学习汇编语言的。所以,我打算好好学习汇编语言,之后在回过头来看看xv6的代码,相信那时候我会明白的更多。 

发布者:admin,转转请注明出处:http://www.yc00.com/web/1690957559a472799.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信