BUAA_OS lab3 难点梳理
BUAA_OS lab3 难点梳理
实验难点
进程创建
对于初始化部分,首先需要在pmap.c中修改mips_vm_init()函数,为envs开空间,并map到UENVS空间。
其次,模仿page_init()的做法,将空闲进程控制块串成env_free_list。
至此没有什么理解上的难度。
进程部分的难点,主要在于进程创建流程的理解。进程创建的流程为:
从env_free_list中获取一个空的PCB
初始化进程控制块
为进程分配资源
从空闲链表中移出,开始执行
STEP1&2
env_alloc()函数清晰地展现了这一进程的前两步,我们以此展开分析。
1 int
2 env_alloc(struct Env **new, u_int parent_id)
3 {
4 int r;
5 struct Env *e;
6
7 /*Step 1: Get a new Env from env_free_list*/
8 if(LIST_EMPTY(&env_free_list)){
9 *new=NULL;
10 return -E_NO_FREE_ENV;
11 }
12 e = LIST_FIRST(&env_free_list);
13
14 /*Step 2: Call certain function(has been completed just now) to init kernel memory layout for this new Env.
15 *The function mainly maps the kernel address to this new Env address. */
16
17 r = env_setup_vm(e);
18 if(r<0){
19 return r;
20 }
21
22 /*Step 3: Initialize every field of new Env with appropriate values.*/
23 e->env_id=mkenvid(e);
24 e->env_parent_id=parent_id;
25 e->env_status=ENV_RUNNABLE;
26
27 /*Step 4: Focus on initializing the sp register and cp0_status of env_tf field, located at this new Env. */
28 e->env_tf.cp0_status = 0x10001004;
29 e->env_tf.regs[29]=USTACKTOP;
30
31
32 /*Step 5: Remove the new Env from env_free_list. */
33 LIST_REMOVE(e,env_link);
34 *new=e;
35 return 0;
36
37 }
env_alloc
首先,从env_free_list中取出一个空PCB。
然后调用env_setup_vm()函数,该函数的主要作用是初始化新进程的空间。具体实现如下:
1 static int
2 env_setup_vm(struct Env *e)
3 {
4 //printf("start_env_setup_vm\n");
5 int i, r;
6 struct Page *p = NULL;
7 Pde *pgdir;
8
9 /* Step 1: Allocate a page for the page directory
10 * using a function you completed in the lab2 and add its pp_ref.
11 * pgdir is the page directory of Env e, assign value for it. */
12 r = page_alloc(&p);
13 if (r < 0) {
14 panic("env_setup_vm - page alloc error\n");
15 return r;
16 }
17 p->pp_ref++;
18 pgdir = (Pde *)page2kva(p);
19 /*Step 2: Zero pgdir's field before UTOP. */
20 for(i=0;i<PDX(UTOP);i++){
21 pgdir[i]=0;
22 }
23
24 /*Step 3: Copy kernel's boot_pgdir to pgdir. */
25
26 /* Hint:
27 * The VA space of all envs is identical above UTOP
28 * (except at UVPT, which we've set below).
29 * See ./include/mmu.h for layout.
30 * Can you use boot_pgdir as a template?
31 */
32 for(i=PDX(UTOP);i<=PDX(~0);i++){
33 pgdir[i]=boot_pgdir[i];
34 }
35 e->env_pgdir = pgdir;
36 e->env_cr3 = PADDR(pgdir);
37 // UVPT maps the env's own page table, with read-only permission.
38 e->env_pgdir[PDX(UVPT)] = e->env_cr3 | PTE_V|PTE_R;
39 // printf("end_setup_vm\n");
40 return 0;
41 }
env_setup_vm
首先我们要明确,每个进程都有自己的页表
在这个函数中,首先调用page_alloc()为该进程分配一个页目录页。获取该页的虚拟地址为pgdir的虚拟地址(至于为什么是虚拟地址,lab2中已有说明)。
接下来,需要将内核部分的页表进行拷贝。这是因为每个进程都有自己单独的页表,这个页表会映射完整的4G空间。但由于实验中采用的是2G+2G的模式,对于所有进程而言,用户态是不同的,但内核态部分是相同的(共享)。所以,所有进程的页表的内核2G部分都是完全相同的
完成页表拷贝之后,需要对PCB中相应值进行设置。然后回到env_alloc()。
接下来需要设置PCB中的某些值,其中尤其要注意的是e->env_tf.cp0_status。该设置使得能正常相应中断。然后将该进程从空闲列表中移出。
至此,创建进程的前两步完成。
STEP3&4
创建进程第三步,本质上也就是加载二进制镜像,在lab3中涉及三个函数,主要步骤如下:
load_icode()函数,初始化一个栈,然后调用load_elf()函数。
load_elf()负责解析ELF文件的字段,并调用load_icode_mapper()函数。
load_icode_mapper()则根据传入的参数将ELF文件内容加载进内存。
返回load_icode()函数后,设置pc寄存器值,使得能正常进入执行
这一部分也没有很复杂的逻辑,但是难在load_icode_mapper()函数的实现。
首先来看一个指导书中的图,可以说是活命必需品:
难点就在于,需要处理情况种类较多,需要重合考虑va是否对齐;bin_size结尾处是否对齐;sgsize结尾处是否对齐。
进程运行和切换
这一部分涉及函数为env_run()。其作用为保存当前进程上下文+恢复启动进程上下文
1 void
2 env_run(struct Env *e)
3 {
4 /*Step 1: save register state of curenv. */
5 /* Hint: if there is an environment running, you should do
6 * switch the context and save the registers. You can imitate env_destroy() 's behaviors.*/
7 // printf("start run\n");
8 struct Trapframe *old;
9 old = (struct Trapframe *)(TIMESTACK - sizeof(struct Trapframe));
10
11 if(curenv!=NULL){
12 curenv->env_tf=*old;
13 curenv->env_tf.pc=curenv->env_tf.cp0_epc;
14 }
15
16 /*Step 2: Set 'curenv' to the new environment. */
17 //printf("start curenv=e\n");
18 curenv=e;
19 curenv->env_status=ENV_RUNNABLE;
20 /*Step 3: Use lcontext() to switch to its address space. */
21 // printf("start lcontext\n");
22 lcontext((int)e->env_pgdir);
23
24 /*Step 4: Use env_pop_tf() to restore the environment's
25 * environment registers and return to user mode.
26 *
27 * Hint: You should use GET_ENV_ASID there. Think why?
28 * (read <see mips run linux>, page 135-144)
29 */
30 // printf("start pop tf\n");
31 env_pop_tf(&curenv->env_tf, GET_ENV_ASID(curenv->env_id));
32 printf("end run\n");
33 }
env_run
首先,我们取出old,及当前环境上下文(寄存器的值等)。
然后将当前环境保存进当前进程的env_tf中,并当前进程的pc设置为cp0_epc,让其陷入中断。
到这里,保存现场的任务完成,可以将curenv设置为下一进程e。
最后,调用env_pop_tf()恢复现场。
在进程切换过程中,最难理解的就是TIMESTACK的含义。我认为TIMESTACK是时钟栈,存储时钟中断的时候存的trapframe。进入时钟中断后,把TIMESTACK的值赋值给寄存器们,再执行中断处理。而KERNEL_SP应当是系统调用后的存储区。
有关TIMESTACK,还有个很难理解的地方,在以下函数中:
void
env_destroy(struct Env *e)
{
/* Hint: free e. */
env_free(e);
/* Hint: schedule to run a new environment. */
if (curenv == e) {
curenv = NULL;
/* Hint:Why this? */
bcopy((void *)KERNEL_SP - sizeof(struct Trapframe),
(void *)TIMESTACK - sizeof(struct Trapframe),
sizeof(struct Trapframe));
printf("i am killed ... \n");
sched_yield();
}
}
为什么要在destroy进程的时候,将KERNEL_SP的tf拷贝到TIMESTACK中?百思不得其解。
个人想法是,在调用sched_yield()获取下一个要执行的进程之前,要将环境恢复到调用当前进程之前的环境。
也可能和kill到最后一个进程的时候要恢复到最初状态有关。
中断异常
中断一场部分代码量较小,主要需要理解的是遇到中断异常后函数的调用关系。
跳转到.text.exc_vec3代码段
根据时钟中断,分发handle_ int函数来处理时钟中断
timer_ irq 里跳转到sched_ yield,选择下一个进程执行。
调度函数的实现根据注释来也没有大问题。但是在后期lab4-extra的时候可能会由于调度错误导致无法通过,所以需要尽量保证情况周全。
(代码仓库位于右上角Github)
BUAA_OS lab3 难点梳理的更多相关文章
- BUAA_OS lab4 难点梳理
BUAA_OS lab4 难点梳理 lab4体会到了OS难度的飞升.实验需要掌握的重点有以下: 系统调用流程 进程通信机制 fork 本lab理解难度较高,接下来将以以上三部分分别梳理. 系统调用 概 ...
- BUAA_OS lab2 难点梳理
BUAA_OS lab2 难点梳理 实验重点 所列出的实验重点为笔者在进行lab2过程中认为需要深刻理解的部分. 进行内存访问的流程 熟悉mips内存映射布局,即理解mmu.h内图 二级页表的理解和实 ...
- Collection集合重难点梳理,增强for注意事项和三种遍历的应用场景,栈和队列特点,数组和链表特点,ArrayList源码解析, LinkedList-源码解析
重难点梳理 使用到的新单词: 1.collection[kəˈlekʃn] 聚集 2.empty[ˈempti] 空的 3.clear[klɪə(r)] 清除 4.iterator 迭代器 学习目标: ...
- HRMS(人力资源管理系统)-从单机应用到SaaS应用-系统介绍
上周发布的<2018,全新出发(全力推动实现住有所居)>文章,其中记录了个人在这5年过程中的成长和收获,有幸认识了不少博客园的朋友,大家一起学习交流,在这个过程当中好多朋友提出SaaS系统 ...
- 《BAT前端进阶[师徒班]》学习总结
这是一个培训课 是的,这是一个面向中级前端的培训班,但明显跟传统的填鸭式培训班不太一样.这边的老师都是大牛这是毫无疑问的,而且都是一线开发人员.而且课程一开始就说明了面向了是有1-3年有工作经验的前端 ...
- 什么是泛型?,Set集合,TreeSet集合自然排序和比较器排序,数据结构-二叉树,数据结构-平衡二叉树
==知识点== 1.泛型 2.Set集合 3.TreeSet 4.数据结构-二叉树 5.数据结构-平衡二叉树 ==用到的单词== 1.element[ˈelɪmənt] 要素 元素(软) 2.key[ ...
- 什么是可变参数?如何创建不可变集合?Steam三类方法是什么?获取流方法特点?流中间方法特点?终结流方法特点?
==知识梳理== ==重难点梳理== ==今日目标== 1.能够了解什么是可变参数 2.能够了解如何去创建不可变集合 3.能够掌握Stream流的使用 ==知识点== 1.可变参数 2.Stream流 ...
- 多线程,线程类三种方式,线程调度,线程同步,死锁,线程间的通信,阻塞队列,wait和sleep区别?
重难点梳理 知识点梳理 学习目标 1.能够知道什么是进程什么是线程(进程和线程的概述,多进程和多线程的意义) 2.能够掌握线程常见API的使用 3.能够理解什么是线程安全问题 4.能够知道什么是锁 5 ...
- JDBC基础:JDBC快速入门,JDBC工具类,SQL注入攻击,JDBC管理事务
JDBC基础 重难点梳理 一.JDBC快速入门 1.jdbc的概念 JDBC(Java DataBase Connectivity:java数据库连接)是一种用于执行SQL语句的Java API,可以 ...
随机推荐
- HDFS 01 - HDFS是什么?它的适用场景有哪些?它的架构是什么?
目录 1.HDFS 是什么 1.1 简单介绍 1.2 发展历史 2.HDFS 应用场景 2.1 适合的应用场景 2.2 不适合的应用场景 3.HDFS 的架构 4.NameNode 和 DataNod ...
- oracle中的in参数超过1000的解决方案
在oracle中,使用in方法查询记录的时候,如果in后面的参数个数超过1000个,那么会发生错误,JDBC会抛出"java.sql.SQLException: ORA-01795: 列表中 ...
- CentOS7安装Mysql并配置远程访问
(su root登录到root账户) 下载repo源 wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm 安装rpm ...
- 使用gitlab构建基于docker的持续集成(二)
使用gitlab构建基于docker的持续集成(二) gitlab docker aspnetcore Centos配置gitlab镜像并且启动 Centos配置防火墙 windows上访问gitla ...
- 后端程序员之路 45、nginx CORS 跨域
在提供api给其它应用使用时,有时我们会要限制它的跨域使用,而有时,我们又要用CORS来打破AJAX只能同源使用的限制 跨域资源共享 CORS 详解 - 阮一峰的网络日志http://www.ruan ...
- ZooKeeper未授权访问漏洞确认与修复
目录 探测2181 探测四字命令 用安装好zk环境的客户端连接测试 修复 修复步骤一 关闭四字命令 修复步骤二 关闭未授权访问 zookeeper未授权访问测试参考文章: https://www.cn ...
- c++中深层复制(浅层复制运行错误)成功运行-----sample
下面随笔给出c++中深层复制(浅层复制运行错误)成功运行------sample. 浅层复制与深层复制 浅层复制 实现对象间数据元素的一一对应复制. 深层复制 当被复制的对象数据成员是指针类型时,不是 ...
- 最简单的,在win,linux中,用powershell,自动获取Let's Encrypt证书方法
powershell传教士原创 2020-04-12 Let's Encrypt证书有效期3个月,支持泛域名[*.你的网站.net].支持n天内(一般10天内就够用了),用脚本自动续期. 简介: 这个 ...
- Java 面向对象 01
面向对象·一级 面向对象思想概述 * A:面向过程思想概述 * 第一步 * 第二步 * B:面向对象思想概述 * 找对象(第一步,第二步) * C:举例 * 买煎饼果子 ...
- Java 常见对象 05
常见对象·正则表达式 和 其他类 正则表达式的概述和简单使用 * A:正则表达式 * 是指一个用来描述或者匹配一系列符合某个语法规则的字符串的单个字符串.其实就是一种规则,有自己的特殊应用 * 作用: ...