这次操作系统实验感觉还是比较难的,除了因为助教老师笔误引发的2个错误外,还有一些关键性的理解的地方感觉还没有很到位,这些天一直在不断地消化、理解Lab3里的内容,到现在感觉比Lab2里面所蕴含的内容丰富很多,也算是有所收获,和大家分享一下我个人的一些看法与思路,如果有错误的话请指正。


 

[关键函数理解]

首先第一部分我觉得比较关键的是对于一些非常关键的函数的理解与把握,这些函数是我们本次实验的精华所在,虽然好几个实验都不需要我们自己实现,但是这些函数真的是非常厉害!有多厉害,呆会就知道了。

首先是从第一个我们要填的函数说起吧:

 

  • env_init

void
env_init(void)
{
       int i;

/*precondition: envs pointer has been initialized at mips_vm_init, called by mips_init*/
       /*1. initial env_free_list*/
       LIST_INIT(&env_free_list);
       //step 1;
       /*2. travel the elements in 'envs', initial every element(mainly initial its status, mark it as free) and inserts them into
       the env_free_list. attention :Insert in reverse order */
       for(i=NENV-1;i>=0;i--){
             envs[i].env_status = ENV_FREE;
             LIST_INSERT_HEAD(&env_free_list,envs+i,env_link);
       }

}

  以上是env_init的实现。其实这个函数没什么太多好说的,就是初始化env_free_list,然后按逆序插入envs[i]。

  这里唯一值得并需要引起警惕的是逆序,因为我们使用的是LIST_INSERT_HEAD这个宏,任何一个对齐有所了解的人应该都知道,这个宏每次都会将一个结点插入,变成链表的第一个可用结点,而我们在取用的时候是使用LIST_FIRST宏来取的,所以如果这里写错了的话,可能在调度算法里就要有所更改。

  可能会有同学问为什么NENV是envs的长度,这个实际上在pmap.c里面的mips_vm_init里可以找到我们的证据,证明envs数组确实给它分配了NENV个结构体的空间,所以它也就有NENV个元素了。

  • env_steup_vm

static int
env_setup_vm(struct Env *e)
{
       // Hint:

       int i, r;
       struct Page *p = NULL;

       Pde *pgdir;
       if ((r = page_alloc(&p)) < 0)
       {
               panic("env_setup_vm - page_alloc error\n");
                       return r;
       }
       p->pp_ref++;
       e->env_pgdir = (void *)page2kva(p);
       e->env_cr3 = page2pa(p);

       static_assert(UTOP % PDMAP == 0);
       for (i = PDX(UTOP); i <= PDX(~0); i++)
         e->env_pgdir[i] = boot_pgdir[i];
       e->env_pgdir[PDX(VPT)]   = e->env_cr3 ;
       e->env_pgdir[PDX(UVPT)]  = e->env_cr3 ;

       return 0;
}

  其实这个函数并不需要我们实现,但是我还是想讲一讲这个函数的一些有意思的地方。

  我们知道,每一个进程都有4G的逻辑地址可以访问,我们所熟知的系统不管是Linux还是Windows系统,都可以支持3G/1G模式或者2G/2G模式。3G/1G模式即满32位的进程地址空间中,用户态占3G,内核态占1G。这些情况在进入内核态的时候叫做陷入内核,因为即使进入了内核态,还处在同一个地址空间中,并不切换CR3寄存器但是!还有一种模式是4G/4G模式,内核单独占有一个4G的地址空间,所有的用户进程独享自己的4G地址空间,这种模式下,在进入内核态的时候,叫做切换到内核,因为需要切换CR3寄存器,所以进入了不同的地址空间!

  而我们这次实验,根据./include/mmu.h里面的布局来说,我们其实就是2G/2G模式,用户态占用2G,内核态占用2G。所以记住,我们在用户进程开启后,访问内核地址不需要切换CR3寄存器!其实这个布局模式也很好地解释了为什么我们需要把boot_pgdir里的内容拷到我们的e->env_pgdir中,在我们的实验中,对于不同的进程而言,其虚拟地址ULIM以上的地方,映射关系都是一样的!这是因为这2G虚拟地址与物理地址的对应,不是由进程管理的,是由内核管理的。

  另外一点有意思的地方不知大家注意到没有,UTOP~ULIM明明是属于User的区域,却还是把内核这部分映射到了User区,而且我们看mmu.h的布局,觉得会非常有意思!
           
  盗用mmu.h里面这张图,我们仔细地来分析一下:

o            ULIM     -----> +----------------------------+-----------0x80000000    
o                               |         User VPT               |     PDMAP                    
o            UVPT     -----> +----------------------------+-----------0x7fc00000  
o                               |         PAGES                  |     PDMAP                     
o            UPAGES   -----> +----------------------------+-----------0x7f800000  
o                               |         ENVS                   |     PDMAP                     
o        UTOP,UENVS   -----> +----------------------------+-----------0x7f400000    
o          UXSTACKTOP -/         |     user exception stack         |     BY2PG                        
o                          +----------------------------+------------0x7f3ff000      
o                              |       Invalid memory            |     BY2PG                     
o            USTACKTOP ----> +----------------------------+------------0x7f3fe000    
o                              |     normal user stack           |     BY2PG                   
o                          +----------------------------+------------0x7f3fd000    
a                              |                                |

   可以看到UTOP是0x7f40 0000,既然有映射,一定就有分配映射的过程,我们使用grep指令搜索一下 UENVS,发现它在这里有pmap.c里的mips_vm_init有所迹象:

    envs = (struct Env*)alloc(NENV*sizeof(struct Env),BY2PG,1);
        boot_map_segment(pgdir,UENVS,NENV*sizeof(struct Env),PADDR(envs),PTE_R);

  可以发现什么呢?其实我们发现,UENVS和envs实际上都映射到了envs对应的物理地址!

  其实足以看出来,内核在映射的时候已经为用户留下了一条路径!一条获取其他进程信息的路途!而且我们其实可以知道,这一部分对于进程而言应当是只能读不可以写的。开启中断后我们在进程中再访问内核就会产生异常来陷入内核了,所以应该是为了方便读一些进程信息,内核专门开辟了这4M的用户进程虚拟区。用户读这4M空间的内容是不需要产生异常的。

        e->env_pgdir[PDX(VPT)]    = e->env_cr3 ;
        e->env_pgdir[PDX(UVPT)]  = e->env_cr3 ;

  这一部分是设置UVPT和VPT映射到4M的页表的起始地址,不过这里还没想太清楚。这里设置UVPT充其量只是能读到e->env_pgdir的那些东西,只有4K的页目录而已,那为什么要用4M的虚拟地址来映射呢?奇怪。。。

  • env_alloc

 1 int env_alloc(struct Env **new, u_int parent_id)
 2 {
 3         int r;
 4         /*precondtion: env_init has been called before this function*/
 5         /*1. get a new Env from env_free_list*/
 6         struct Env *currentE;
 7         currentE = LIST_FIRST(&env_free_list);
 8         /*2. call some function(has been implemented) to intial kernel memory layout for this new Env.
 9          *hint:please read this c file carefully, this function mainly map the kernel address to this new Env address*/
10         if((r=env_setup_vm(currentE))<0)
11                 return r;
12         /*3. initial every field of new Env to appropriate value*/
13         currentE->env_id = mkenvid(currentE);
14         currentE->env_parent_id = parent_id;
15         currentE->env_status = ENV_NOT_RUNNABLE;
16         /*4. focus on initializing env_tf structure, located at this new Env. especially the sp register,
17          * CPU status and PC register(the value of PC can refer the comment of load_icode function)*/
18         //currentE->env_tf.pc = 0x20+UTEXT;
19         currentE->env_tf.regs[29] = USTACKTOP;
20         currentE->env_tf.pc = UTEXT + 0xb0;
21         currentE->env_tf.cp0_status = 0x10001004;
22         /*5. remove the new Env from Env free list*/
23         LIST_REMOVE(currentE,env_link);
24         *new = currentE;
25         return 0;
26 }
View Code

相关文章: