zpl指令打印中文内容_6.3 ECALL指令之前的状态

接下来,我将切换到gdb的世界中。 大家可以看我共享的屏幕,我们将要跟踪一个XV6的系统调用,也就是Shell将它的提示信息通过write系统调用走到操作系统再输出到console的过程。你们可以看到,用户代码sh.c初始了这一切。

2b4680345bc73fa060451eda0538e806.png


上图中选中的行,是一个write系统调用,它将“$ ”写入到文件描述符2。接下来我将打开gdb并启动XV6。

d9c76d86ddc1e6ea0edb9c2b9ca825f6.png


作为用户代码的Shell调用write时,实际上调用的是关联到Shell的一个库函数。你可以查看这个库函数的源代码,在usys.s。

0d012be563fe21c275b6f043433048cb.png


上面这几行代码就是实际被调用的write函数的实现。这是个非常短的函数,它首先将SYS_write加载到a7寄存器,SYS_write是常量16。这里告诉内核,我想要运行第16个系统调用,而这个系统调用正好是write。之后这个函数中执行了ecall指令,从这里开始代码执行跳转到了内核。内核完成它的工作之后,代码执行会返回到用户空间,继续执行ecall之后的指令,也就是ret,最终返回到Shell中。所以ret从write库函数返回到了Shell中。
为了展示这里的系统调用,我会在ecall指令处放置一个断点,为了能放置断点,我们需要知道ecall指令的地址,我们可以通过查看由XV6编译过程产生的sh.asm找出这个地址。sh.asm是带有指令地址的汇编代码(注,asm文件3.7有介绍)。我这里会在ecall指令处放置一个断点,这条指令的地址是0xde6。

ca43a1d7c9bc5dc5b181a8563d16da3f.png


现在,我要让XV6开始运行。我期望的是XV6在Shell代码中正好在执行ecall之前就会停住。

016143fdaa57127f5189bbca77a7e33f.png


完美,从gdb可以看出,我们下一条要执行的指令就是ecall。我们来检验一下我们真的在我们以为自己在的位置,让我们来打印程序计数器(Program Counter),正好我们期望在的位置0xde6。

4afc1acc4094a65d1433f32c44a79bc5.png


我们还可以输入info reg打印全部32个用户寄存器,

dba4793d94d1921100ddf33e3a8a6cc8.png


这里有一些数值我们还不知道,也不关心,但是这里的a0,a1,a2是Shell传递给write系统调用的参数。所以a0是文件描述符2;a1是Shell想要写入字符串的指针;a2是想要写入的字符数。我们还可以通过打印Shell想要写入的字符串内容,来证明断点停在我们认为它应该停在的位置。

34b59f31127ae29e2186f2bf8f993003.png


可以看出,输出的确是美元符($)和一个空格。所以,我们现在位于我们期望所在的write系统调用函数中。
有一件事情需要注意,上图的寄存器中,程序计数器(pc)和堆栈指针(sp)的地址现在都在距离0比较近的地址,这进一步印证了当前代码运行在用户空间,因为用户空间中所有的地址都比较小。但是一旦我们进入到了内核,内核会使用大得多的内存地址。
系统调用的时间点会有大量状态的变更,其中一个最重要的需要变更的状态,并且在它变更之前我们对它还有依赖的,就是是当前的page table。我们可以查看STAP寄存器。

6dc8ed86eb744123b21b69913abdf72e.png


这里输出的是物理内存地址,它并没有告诉我们有关page table中的映射关系是什么,page table长什么样。但是幸运的是,在QEMU中有一个方法可以打印当前的page table。从QEMU界面,输入ctrl a + c可以进入到QEMU的console,之后输入info mem,QEMU会打印完整的page table。

94d0a9aced7f319910a26569641aa2f4.png


这是个非常小的page table,它只包含了6条映射关系。这是用户程序Shell的page table,而Shell是一个非常小的程序,这6条映射关系是有关Shell的指令和数据,以及一个无效的page用来作为guard page,以防止Shell尝试使用过多的stack page。我们可以看出这个page是无效的,因为在attr这一列它并没有设置u标志位(第三行)。attr这一列是PTE的标志位,第三行的标志位是rwx表明这个page可以读,可以写,也可以执行指令。之后的是u标志位,它表明PTE_u标志位是否被设置,用户代码只能访问u标志位设置了的PTE。再下一个标志位我也不记得是什么了(注,从4.3可以看出,这个标志位是Global)。再下一个标志位是a(Accessed),表明这条PTE是不是被使用过。再下一个标志位d(Dirty)表明这条PTE是不是被写过。
现在,我们有了这个小小的page table。顺便说一下,最后两条PTE的虚拟地址非常大,非常接近虚拟地址的顶端,如果你读过了XV6的书,你就知道这两个page分别是trapframe page和trampoline page。你可以看到,它们都没有设置u标志,所以用户代码不能访问这两条PTE。一旦我们进入到了supervisor mode,我们就可以访问这两条PTE了。
对于这里page table,有一件事情需要注意:它并没有包含任何内核部分的地址映射,这里既没有对于kernel data的映射,也没有对于kernel指令的映射。除了最后两条PTE,这个page table几乎是完全为用户代码执行而创建,所以它对于在内核执行代码并没有直接特殊的作用。
学生提问:PTE中a标志位是什么意思?
Robert教授:这表示这条PTE是不是被代码访问过,是不是曾经有一个被访问过的地址包含在这个PTE的范围内。d标志位表明是否曾经有写指令使用过这条PTE。这些标志位由硬件维护以方便操作系统使用。对于比XV6更复杂的操作系统,当物理内存吃紧的时候,可能会通过将一些内存写入到磁盘来,同时将相应的PTE设置成无效,来释放物理内存page。你可以想到,这里有很多策略可以让操作系统来挑选哪些page可以释放。我们可以查看a标志位来判断这条PTE是否被使用过,如果它没有被使用或者最近没有被使用,那么这条PTE对应的page适合用来保存到磁盘中。类似的,d标志位告诉内核,这个page最近被修改过。
不过XV6没有这样的策略。
接下来,我会在Shell中打印出write函数的内容。

98fed3bf7d298e34821c0a8f8b3b2e28.png


程序计数器现在指向ecall指令,我们接下来要执行ecall指令。现在我们还在用户空间,但是马上我们就要进入内核空间了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值