VisualMike-I

Results 9 comments of VisualMike-I

亲爱的作者大大,最后一行:“这里我们看到,API中的各个参数和返回值分别被RISC-V通用寄存器 x10 (即存放系统调用号,也保存返回值)、 x11`(存放 `fd ) 、 x12 (存放 buf )和 x17 (存放 len )保存。” 按照代码的逻辑,应该是x17(存放系统调用号)吧?其他寄存器存储内容也偏差一位。换句话说,这四个寄存器存储的内容描述是否应该“循环左移一位“?当然x10的确是保存返回值。后面章节相应部分描述正确。内容繁多工作量大可以理解,为作者的无私贡献表示感谢。🌹

你好,unmap或unmap_one后需要当页表节点的所有页表项都是invalid时执行page_table.frames.remove()回收页表中相关页表节点物理页帧以及祖先物理页帧的空间吗?我通过grep没有找到相关代码,是某些地方隐含地执行了相关操作吗?还是目前不需要回收呢?也可能我还没理解到位。

MapArea::new时会start_va.floor(), end_va.ceil(),那么应用链接脚本中不是所有段都页对齐那么构建应用地址空间时有些段数据是否有重叠(两个MapArea段含有同一虚拟页面,如.data和.bss段)?

@wyfcyx 嗯嗯这么晚了感谢抽出宝贵的时间回答,懂了。谢谢,以下是纯建议,有些啰嗦,没时间看不用回复。 数据所在的物理页帧FrameTracker被drop回收了,但是页表项所在的物理页帧以及祖先页表节点所在物理页帧好像没根据其他页表项是非为空而drop回收掉。 data_frames.remove()回收了应用实际数据所在的物理页帧,但没有考虑页表节点所在的物理页帧回收问题,另外page_table.unmap()中也没有考虑回收页表节点所在的物理页帧回收问题,只是将虚拟页号对应页表项所在64位比特清零。按道理是否应该当某一页表节点的页表项全为invalid时回收当前页表节点所在物理页帧的空间,进而还要考虑祖先页表节点的物理页帧回收。 虽然os运行结束后各变量生命周期结束会drop回收掉页表节点所在物理页帧的空间,但在目前的实现中,若是在os运行期间执行unmap操作,不会回收页表节点所在物理页帧空间。尽管目前的实现没有上层应用真正触发unmap操作,但从功能完备性上是否应考虑释放页表节点所在物理页帧的空间?经过一定的算法在合适的地方调用page_table.frames.remove(a_frame_tracker)。但现在是vec向量存储frame_tracker,貌似不好根据ppn查找frame_tracker,需要遍历吗?BTreeMap应该好找,时间效率高。 (2023.9.30更新: 页表的物理页帧下一章提及了何时清理。) 另外在ch4 user/src/linker.ld 中,.data和.bss段之间好像没有页对齐,尽管它们访问权限相同,但是它们是由两个独立的program header标识的,那么根据代码会创建两个MapArea,可能导致覆盖。 🌹

原文__switch实现描述中:“阶段 [2] 体现在第 19~27 行,即根据 B 任务上下文保存的内容来恢复 ra 寄存器、s0~s11 寄存器以及 sp 寄存器。” 这段文字描述好像是说的阶段 [3] 的内容,阶段 [2] 是根据 A 的任务上下文来“保存”这些寄存器。 ^_^

@linyn-zero 第一遍阅读不必要求每一个细节都读懂,可以先把问题记录下来,第二遍第三遍重读同一节甚至读完整章再回过头阅读就会发现问题的解决思路了。作为一个完整的系统,不可能真正完全做到事无巨细循序渐进地讲解。读完整章有了整体概念,时常复习尽量记住足够多的上下文,有问题时才能了解从哪里寻找线索,得出答案。🌹

> 我有个问题,任务从taskA 通过 switch函数切换栈后,此时ra寄存器的值应该是switch函数的下个地址,然后函数会返回到taskB的trap_handler继续执行。trap_handler执行完毕后,此时应该会返回到__restore函数执行,这里是如何返回到__restore的我没想明白,ra寄存器被编译器自动设置成了__restore的地址? @yanglianoo 我也有过这个问题,当时没学过汇编,所以对汇编文件trap.S理解不到位,后来根据文档有关汇编脚本文件的上下文猜测到: __alltraps和__restore都是定义在汇编文件trap.S中的,__alltraps的最后一条指令是`call trap_handler`,其下一条指令是符号`__restore:`指示的`ld t0, 32*8(sp)`,即__restore第一条指令。而`call trap_handler`应该是“为内核支持函数调用”那节所说的用`jalr`实现的伪指令`call`实现函数调用,会保存当前指令的下一条指令地址`ld`的地址到ra寄存器并跳转到函数`trap_handler`。`trap_handler`执行完毕`ret`就返回到__restore首指令ld了。 [为内核支持函数调用](http://rcore-os.cn/rCore-Tutorial-Book-v3/chapter1/5support-func-call.html#term-function-call-and-stack) > 总结一下,在进行函数调用的时候,我们通过 jalr 指令保存返回地址并实现跳转;而在函数即将返回的时候,则通过 ret 伪指令回到跳转之前的下一条指令继续执行。

> 有个问题,请问ra和sepc的作用是不是重复了?在A-switch-B返回后,restore加载B的trap context后,请问pc究竟是跳转到sepc指定的地址还是跳转到ra指向的地址? @ctangam 执行执行环境返回指令`sret`会将`sepc`写到`pc`,执行函数调用返回指令`ret`会将`ra`写到`pc`。 前面定义了`pub fn __switch(~)`,因此在源代码中直接当做函数调用`__switch(~)`,`__switch`最后会执行指令`ret`。而`__restore`的调用是通过`call trap_handler`执行完毕后接着执行下条指令实现的,`__alltraps`最后会执行`sret`。下一节会看到`__switch`会在系统调用中执行,因此先是`__alltraps`保存trap上下文,再执行`__switch`切换任务,以此保存被调用保存寄存器(其中一个目的G是:任务切换回来时从调用`__switch()`的地方回来接着往下执行,这是通过执行`ret`将`ra`写到`pc`实现的,`ra`存储着同特权级函数调用完成返回时要跳转到的位置),恢复下个任务的被调用者保存寄存器(其中恢复`ra`就是为实现目的G做的准备工作)和切换至下个任务(这样就暂停了当前任务,暂停在调用函数`__switch`的代码处)的暂停点(系统调用内核代码调用`__switch`的地方)继续执行(执行`ret`实现),系统调用代码`__switch`执行完了在执行剩余的系统调用代码完了会在`__restore`处接着执行恢复trap上下文代码,最后执行`sret`恢复trap上下文。 简言之`ret`是为了从系统调用时内核函数调用暂停点继续执行,`sret`是为了切换特权级,从用户态调用系统调用的函数暂停点接着执行,并使用系统调用返回的结果。 [特权级切换的硬件控制](http://rcore-os.cn/rCore-Tutorial-Book-v3/chapter2/4trap-handling.html#trap-hw-mechanism) > 而当 CPU 完成 Trap 处理准备返回的时候,需要通过一条 S 特权级的特权指令 `sret` 来完成,这一条指令具体完成以下功能: > - CPU 会将当前的特权级按照 sstatus 的 SPP 字段设置为 U...

@sunyiwei24601 exit自己就是系统调用啊。 借用原文的例子,例如其中一种资源的过早回收会导致的问题是:exit系统调用执行过程中,可能会调用一些内核态的其他函数F,会用到该进程的内核栈,在下一节将看到,若是把该进程的所有资源都回收(含TCB),因该进程TCB中的pid_handler被回收(下一节内容),会触发drop函数释放内核栈空间(下一节内容),就不能利用栈帧中的ra信息回到系统调用函数exit调用F的中断处(第一章内容——为内核支持函数调用),继续往后执行exit的剩余代码了。