Mengzelev's Blog

PA3实验报告

Word count: 2,503 / Reading time: 9 min
2018/12/08 Share

实验进度

我已完成所有内容。
好,下面是惯例碎碎念。

2018/11/18
心态爆炸的一天!先是因为脑子短路完全没有想到idt的地址就在lidt译码过后的id_dest里。然后还因为惯性思维从id_dest->val里读地址,疯狂出错才发现应该在id_dest->addr里(因为是地址)。组织Context的时候思考了半天,最后问了大腿才知道怎么判断上下文的组织结构。感觉还是课本知识学得不够扎实的恶果。今日不宜PA,关机睡觉。

2018/11/19
PA3.1完成。感觉测试手段都不太给力,不知道有没有埋下隐藏bug

2018/11/24
PA3.2完成。感觉莫名顺利,可能是3.3要自闭的征兆

2018/12/1
开始自闭PA3.3,实现了文件读写操作,死活看不到PASS!!!,文件读写时对长度超过文件大小的情况的处理有一些麻烦,调了半天,最后发现是PA3.2时就好了的SYS_write没有加上刚刚写的fs_write,喷了。开始编写设备文件操作,看不懂vga那块到底是要干啥,机房关门了,回去睡觉。

2018/12/2
继续自闭PA3.3。为了看懂VGA那块的架构直接翻出了bmptest的源码,然后一路摸到NDL库的源码,花了大概一个下去终于显示出了ProjectN的Logo。实现events_read()时还把事件的格式搞错了debug了半天,终于可以开始跑仙剑了。小心翼翼地开始游戏,没有注释掉Log因此看到了瀑布般的Log信息,然后50min才播放完片头对话,所幸无事。然后注释掉了所有Log,又胆战心惊地开始尝试所有存档点,尝试了穿墙和穿人,全部失败,确认了没有肉眼可见的bug。长舒一口气,然后加急写完了批处理系统,机房关门,走人。难以置信我居然跑出了仙剑,亢奋。

必答题

其实我写PA的时候都在系统调用过程梳理里做好笔记了

游戏的存档

  • 仙剑奇侠传中调用C标准I/O库函数fread()
  • 库函数fread()会调用libos中的系统调用的封装函数_read()
  • libos中的_read()函数通过调用_syscall_(),直接使用内联汇编语句编译出int 0x81的系统调用内陷指令,并将相应的参数放入约定的寄存器中(%eax,%ebx%ecx%edx)
  • nemu执行指令时遇到了int 0x81指令,译码后执行raise_intr(),将EFLAGS,CS,EIP的值压入栈中,并到am中的中断门描述符表idt中查询0x81对应的跳转目标的偏移量offset,为vectrap()
  • vectrap()直接通过汇编指令将错误码irq压栈,然后跳转到asm_jmp函数执行(nexus-am/am/arch/x86-nemu/src/trap.S中定义)
  • asm_trap中通过pusha指令让nemu将所有寄存器压栈,然后执行irq_handle()函数(在nexus-am/am/arch/x86-nemu/src/cte.c中)
  • irq_handle()把执行流切换的原因打包成事件,然后调用在_cte_init() 中注册的事件处理回调函数, 将事件交给Nanos-lite来处理
  • Nanos-lite中的do_event()函数根据nemu打包传过来的时间决定系统调用类型,此处是_EVENT_SYSCALL类型的,就调用do_syscall()函数来处理这个系统调用
  • do_syscall()根据上下文中寄存器%eax保存的参数确定系统调用的类型,此时为SYS_read类型,就调用文件系统中的fs_read()并将相应的参数传入
  • fs_read()根据传入的文件名(存档信息文件的名称)读取需要的长度到指定的位置,恢复成存档前的状态,就可以从存档点继续游戏
  • fs_read()执行完成后,一路返回到asm_trap中,恢复之前压入栈中保存的寄存器,然后执行iret指令
  • nemu执行iret指令,恢复EFLAGS,CS,EIP,跳转到EIP所指向的地方继续执行之后的指令

至此,一次读取存档的系统调用全部完成

更新屏幕

  • 仙剑奇侠传中调用libndl中的库函数NDL_DrawRect()
  • NDL_DrawRect()会调用C标准I/O库函数fwrite(),将当前的像素信息写入stdout中,这里的stdout不是终端里的标准输出,而是应用程序的标准输出_REENT->stdout
  • 系统调用过程同上,此处不再赘述。进入Nanos-lite的文件系统后,由于是对显示设备抽象成的文件/dev/fb进行读取,fs_write()调用的是fb_write()
  • fb_write()调用了am的klib中提供的screen_width()draw_rect()
  • am中的draw_rect()调用了vga设备的写函数video_write(),把相应的像素信息写入到映射到vga_memory的物理内存中
  • 当nemu访问到从0x4000开始的一段被映射到I/O空间的物理地址时,就会通过mmio_write来修改I/O空间的数据
  • I/O空间的数据被修改后调用了update_screen(),由nemu把更新后的屏幕显示信息显示了出来
  • 进行系统调用的返回操作,同样不再赘述

至此,一次更新屏幕的系统调用完成

蓝框思考题

最近太忙了一题都没写…
想写的时候讲义又挂了…
二周目的时候再思考吧orz

实验中的发现

关于nanos-lite和nemu是怎么通讯的

在写PA3.1中的lidt指令的时候,我有个很大的疑惑,idt是在nanos-lite里被定义的,nemu要怎么获取它的地址呢?_cte_init中有一步调用了set_idt函数,将idt的地址和大小用内联汇编语句写入了指令集。因此当nanos编译成nemu可执行的指令后,idt的地址就可以通过相应的译码函数写入id_dest中。
问了一位nb的学长后,学长给出了如下理解

不在system.c里的指令,一般靠编译c文件成机器码就可以给nemu做事,所以pa2很顺利;而system.c的东西依赖于系统(比如中断),只靠编译c文件做不到int、lidt这些东西,所以要内联汇编

关于如何判断_Context结构的组织顺序

_Context是在函数irq_handle里作为参数的类型被传入的,然而irq_handle是通过汇编代码直接调用的,因此没有显式的参数传递语句,实际的参数是存放在栈中%ebp-8的位置开始的位置,观察call irq_handle之前所有的push相关语句就可以得知寄存器的顺序。而eip,eflagseip是硬件保存的,因此需要观察int指令中的入栈顺序才能知道这三者的顺序。

系统调用过程梳理

  1. 用户层navy-apps在相应的系统调用函数(如_exit())中调用系统调用接口函数_syscall_()
  2. _syscall_用内联汇编语句将int 0x80和相应的系统调用参数传给nemu
  3. nemu执行int指令,叫出nanos-lite
  4. irq_handler通过int指令传入的参数识别出这是一次系统调用的event,打包传给do_event
  5. irq.c中的do_event()函数调用do_syscall()执行系统调用事件
  6. syscall.c中的do_syscall()函数根据传入的参数(在上下文的相应寄存器中)执行相应的系统调用操作,设置返回值并返回

我遇到的bug

  • 堆区管理的时候声明的用于记录program break的变量pbrk没有初始化【所以说初始化真的是个好文明
  • 写了fs_write却忘了修改相应的系统调用SYS_write找了一个下午
  • 所有关于文件读写的系统操作都需要控制open_offset,不能超过当前文件的size,但是serial_writedispinfo_read等不同文件的读写又不能一概而论,写bug和debug的时候要特别注意
  • fs_lseek最后要控制当前的open_offset,不然可能会导致fs_readfs_write的功能无法正常使用
  • fs_readfs_write需要对open_offset做出修改
  • 函数指针的调用就跟函数一样用就行了
  • fb_write里的offset怎么用,怎么给draw_rect传参,可能要一直摸到libnbl才能搞清楚
  • 字符串不能x**初始化,会由于某些我搞不懂的原因编译出STOS指令,导致看到亲切的i386 Logo
  • if((keytemp & 0x8000) == 1)if(keytemp & 0x8000)是不一样的!!!!!![你是沙雕吗.jpg]

对讲义的一些建议(你们选择性反馈给yzh)

  • 建议把能看到PASS!!!信息的放在serial_write()之后,不少同学都在实现完SYS_write之后没看到PASS!!!感到困惑(当然仔细想一下还是能想通)
  • 堆区管理那一块太考验阅读理解水平了,而且大部分人会忘掉PA2还接触过AM里一个叫heap的结构体,就稍微提醒一下下呗(
  • 让dummy实现_syscall_时,最好能提醒一下如果按照之前加系统调用一下加入do_event中会触发死循环,或者干脆把下面的必做部分并到一起写,减少困惑

实验心得

  • RTFSC是很难的,不要一下子看不出来就自闭,不然会出事情的
  • 要成为一名优秀的程序猿,首先你要学好语文
  • 出bug不可怕,如果能通过debug的过程对框架代码和计算机系统的原理有更深刻的理解,其实是一件很赚的事情。听了助教讲课之后我深刻感受到,一出bug就问大腿“怎么写才是对的”,实在太亏了,应该搞清楚哪里写错了,为什么写的fault这个出现这样的failure。

Appreciation

  • 感谢某人提供的探路和debug服务,我觉得有点保护过度了再这样下去我要不会自己写代码了【捂脸
  • 感谢名字一直在换的学习群的群友们互帮互助的debug氛围

本实验报告同样会将去掉必答题的版本同步上传至个人blog

CATALOG
  1. 1. 实验进度
  2. 2. 必答题
    1. 2.1. 游戏的存档
    2. 2.2. 更新屏幕
  3. 3. 蓝框思考题
  4. 4. 实验中的发现
    1. 4.1. 关于nanos-lite和nemu是怎么通讯的
    2. 4.2. 关于如何判断_Context结构的组织顺序
    3. 4.3. 系统调用过程梳理
  5. 5. 我遇到的bug
  6. 6. 对讲义的一些建议(你们选择性反馈给yzh)
  7. 7. 实验心得
  8. 8. Appreciation