logo资料库

161720326 - 方建军 - PA3.1.pdf

第1页 / 共18页
第2页 / 共18页
第3页 / 共18页
第4页 / 共18页
第5页 / 共18页
第6页 / 共18页
第7页 / 共18页
第8页 / 共18页
资料共18页,剩余部分请下载后查看
南京航空航天大学《计算机组成原理Ⅱ课程设计》报告
目录
思考题
实验内容
遇到的问题及解决办法
心得体会
其他备注
南京航空航天大学《计算机组成原理Ⅱ课程设计》 报告 姓名:方建军 班级:1617203 学号:161720326 报告阶段:PA3.1 完成日期:2019.6.8 本次实验,我完成了所有内容 目录 南京航空航天大学《计算机组成原理Ⅱ课程设计》报告 目录 思考题 实验内容 遇到的问题及解决办法 心得体会 其他备注 思考题 1. 什么是操作系统? 操作系统(英语:operating system,缩写作 OS)是管理计算机硬件与软件资源的计算机程序,同时也是计算机系 统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、 操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。 2. 我们在 PA2 中对 cputest 和部分设备测试的程序直接运行在 AM 之上,那么和 Nanos-lite 是运行在 AM 之上 的 有什么差距呢?我们可以把 Nanos-lite 看作一个和 PA2 中这些测试用例同等地位的一个 AM 程序吗? 权限不一样,不可以 3. 我们把 PA2 中运行的这些测试用例的运行方式称作“直接运行在硬件上的用户程序”,经过上个思考题的思考, 请你总结一下操作系统的实质:操作系统就是一个较为大型的( ),它和直接运行在硬件上的程序( )(有/无)实质差 别。 用户程序,无 4. 我们曾经问过这样一个问题:程序设计课上老师说程序运行到 main() 函数的 return 0; 语句,程序就结束 了,是真的吗?现在再回想一下,当时自己的回答是否正确。程序在进入 main 函数前和退出 main 函数后,都 做了什么工作?如果你还不能理解,请结合 _start() 及其中调用过的函数或过程再思考一下。 进入之前先将寄存器等内容入栈,出来时在恢复进入main函数前的状态
  5.我们发现,系统调用会根据系统调用号在 IDT 中索引,取得该调用号对应的系统调用服务程序的地址,并跳转过 去。在触发系统调用前,会保护用户相关状态寄存器(EFLAGS, EIP等)到栈中,系统调用完毕后再恢复,这和我们 所学过的什么的过程非常相似?我们可以将系统调用的服务程序理解为一个比较特殊的“函数”吗?请说明理由。这 个“服务程序”和我们的“用户编写的函数”有什么不同? 与函先将数的过程调用类似,可以,因为在调用前都要做准备工作,先将一些寄存器内容入栈,调用完后再恢复 6.我们在 GNU/Linux 下编写了带有 bug 的程序,虽然能通过编译,但在运行时却遇到了“Segmentation Fault”(段 错误),为什么在编译阶段不能发现这些潜在的段错误呢?段错误通常是由哪些程序行为引起的? 段错误一般是当你访问了未申请的内存或非法的内存时产生的。主要还是程序的内存管理有问 题。编译时无法发现 7.我们知道进行函数调用的时候也需要保存调用者的状态:返回地址,以及调用约定(calling convention)中需要 调用者保存的寄存器。而进行异常处理之前却要保存更多的信息。尝试对比它们,并思考两者保存信息不同是什么原 因造成的。 函数调用没有保存cs等,因为异常处理可能涉及到多层函数调用 8. trap.S 中有一行 pushl %esp 的代码,乍看之下其行为十分诡异。你能结合前后的代码理解它的行为吗?Hint: 不用想太多,其实都是你学过的知识。 将陷进帧压栈 9.事件号和系统调用号有什么区别? 事件代表事件编号,而系统调用号代表事件所触发系统调用的编号 10.我们曾经在编写各种 C 语言程序时,遇到过这种情况: #include int main(){    int *p = NULL;    printf("I am here!");    *p = 10; // giving value to a NULL address will cause segmentation fault    return 0; } 程序在运行到 *p = 10; 时出现了段错误,但是 I am here! 这个字符串却并没有被打印到屏幕上,这是为什么? 怎么修改上述代码使得能在 *p = 10; 出现段错误时,仍能打印出 I am here! 这个字符串?如下面的操作所示: $ vim test.c    # type code above into test.c $ gcc test.c    # compile test.c to ./a.out $ ./a.out       # execute ./a.out and a Segmentation fault will be reported Segmentation fault $ vim test.c    # how to make some modification to let string get printed? $ gcc test.c    # compile test.c again $ ./a.out       # execute ./a.out again and the supposed string was printed I am here! Segmentation fault 请你在输入上述程序时,务必不要出现任何手误,确保是原样输入(当然,你无需输入注释内容),否则你将有可能 意外地不会出现此处的段错误。请你结合讲义内容和自己的实验,尝试找出解决方案,并解释其中的原因。
解决方案:将printf放到指针定义前面, 出现原因:C语言在定义指针变量时会先实现内存分配 11.方便起见,我们在更新后的框架代码中替大家实现了几个文件管理函数。那么,请你参考这些 fs_ 开头的函数, 思考一下它们是如何编写出来的 利用一些文件的基本知识 12.网上流传着一些关于仙剑奇侠传的秘技,其中的若干条秘技如下: 1. 很多人到了云姨那里都会去拿三次钱,其实拿一次就会让钱箱爆满!你拿了一次钱就去买剑把钱用到只剩一千 多,然后去道士那里,先不要上楼,去掌柜那里买酒,多买几次你就会发现钱用不完了。 2. 不断使用乾坤一掷(钱必须多于五千文)用到财产低于五千文,钱会暴增到上限,如此一来就有用不完的钱 了。 3. 当李逍遥等级到达 99 级时,用 5~10 只金蚕王,经验点又跑出来了,而且升级所需经验会变回初期 5~10 级内 的经验值,然后去打敌人或用金蚕王升级,可以学到灵儿的法术(从五气朝元开始);升到 199 级后再用 5~10 只金蚕王,经验点再跑出来,所需升级经验也是很低,可以学到月如的法术(从一阳指开始);到 299 级后再 用 10~30 只金蚕王,经验点出来后继续升级,可学到阿奴的法术(从万蚁蚀象开始)。 假设这些上述这些秘技并非游戏制作人员的本意,请尝试解释这些秘技为什么能生效。 相关数据发生了溢出 13.文件读写的具体过程 仙剑奇侠传中有以下行为: 在 navy-apps/apps/pal/src/global/global.c 的 PAL_LoadGame() 中通过 fread() 读取游戏存档 在 navy-apps/apps/pal/src/hal/hal.c 的 redraw() 中通过 NDL_DrawRect() 更新屏幕 请结合代码解释仙剑奇侠传、库函数、libos、Nanos-lite、AM、NEMU 是如何相互协助,来分别完成游戏存档的读 取和屏幕的更新。 游戏存档:库函数读取存档向Nanos-lite申请系统调用,Nanos-lite在转到AM实现,nemu底层实现 屏幕更新同上。 实验内容 1.实现loader 实现简单 loader,触发未实现指令 int: 代码: ramdisk_read(DEFAULT_ENTRY,0,get_ramdisk_size()); return (uintptr_t)DEFAULT_ENTRY; 代码截图: 思想:直接调用ramdisk_read函数即可
实现引入文件系统后的 loader: 代码: uintptr_t loader(_Protect *as, const char *filename) { //ramdisk_read(DEFAULT_ENTRY,0,get_ramdisk_size()); int fd = fs_open(filename, 0, 0);   fs_read(fd, DEFAULT_ENTRY, fs_filesz(fd));   fs_close(fd);   return (uintptr_t)DEFAULT_ENTRY; } 代码截图: 思想:用fs_open函数打开要加载的文件,用fs_read函数读取即可 2.添加寄存器和 LIDT 指令 根据 i386 手册正确添加 IDTR 和 CS 寄存器: 在reg.h里添加CS和IDTR寄存器即可 截图: 在 restart() 中正确设置寄存器初始值: 设置即可 代码: static inline void restart() { /* Set the initial instruction pointer. */ cpu.eip = ENTRY_START; cpu.eflags = 0x00000002; cpu.cs = 0x8; #ifdef DIFF_TEST init_qemu_reg(); #endif }
截图: 实现LIDT指令: 代码: make_EHelper(lidt) { cpu.idtr.limit = vaddr_read(id_dest->addr, 2); if (decoding.is_operand_size_16) cpu.idtr.base = vaddr_read(id_dest->addr + 2, 3); else cpu.idtr.base = vaddr_read(id_dest->addr + 2, 4); print_asm_template1(lidt); } 代码截图: 思想:根据i386手册实现即可,并添加译码表 3.实现 INT 指令 代码: raise_intr: void raise_intr(uint8_t NO, vaddr_t ret_addr) { /* TODO: Trigger an interrupt/exception with ``NO''. - That is, use ``NO'' to index the IDT. */   union {   GateDesc gd;   struct{uint32_t lo,     hi;         };   }item;   vaddr_t addr;  
  addr=8*NO+cpu.idtr.base;   item.lo=vaddr_read(addr,4);   item.hi=vaddr_read(addr+4,4);   decoding.is_jmp=1;   decoding.jmp_eip=(item.gd.offset_15_0&0xFFFF)|((item.gd.offset_31_16&0xFFFF)<<16);       rtl_push(&cpu.eflags);   t0 = cpu.cs;   rtl_push(&t0);   rtl_push(&ret_addr);   cpu.IF=0; }er函数: helper函数: make_EHelper(int) { raise_intr(id_dest->val, decoding.seq_eip); print_asm("int %s", id_dest->str); #ifdef DIFF_TEST diff_test_skip_nemu(); #endif } 截图:
思想:根据i386手册实现即可,并添加译码表 4.实现其他相关指令和结构体 组织 _RegSet 结构体: 代码: struct _RegSet { uintptr_t edi, esi, ebp, esp, ebx, edx, ecx, eax; int       irq; uintptr_t error_code, eip, cs, eflags; }; 截图: 思想:根据trap frame结构体形成顺序来组织顺序 pusha指令: 代码: make_EHelper(pusha) { if (decoding.is_operand_size_16) {     t0 = cpu.esp;     rtl_lr_w(&t1, R_AX);     rtl_push(&t1);     rtl_lr_w(&t1, R_CX);     rtl_push(&t1);     rtl_lr_w(&t1, R_DX);     rtl_push(&t1);     rtl_lr_w(&t1, R_BX);     rtl_push(&t1);     rtl_push(&t0);     rtl_lr_w(&t1, R_BP);     rtl_push(&t1);     rtl_lr_w(&t1, R_SI);     rtl_push(&t1);     rtl_lr_w(&t1, R_DI);
    rtl_push(&t1); } else { t0 = cpu.esp; rtl_lr_l(&t1, R_EAX); rtl_push(&t1); rtl_lr_l(&t1, R_ECX); rtl_push(&t1); rtl_lr_l(&t1, R_EDX); rtl_push(&t1); rtl_lr_l(&t1, R_EBX); rtl_push(&t1); rtl_push(&t0); rtl_lr_l(&t1, R_EBP); rtl_push(&t1); rtl_lr_l(&t1, R_ESI); rtl_push(&t1); rtl_lr_l(&t1, R_EDI); rtl_push(&t1); } print_asm("pusha"); } 截图: popa指令:
分享到:
收藏