南京航空航天大学《计算机组成原理Ⅱ课程设计》
报告
姓名:方建军
班级: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指令: