Open utterances-bot opened 3 years ago
您好,我想问问在map和unmap之后为啥没有刷新TLB
@honeyhhhh 由于应用和内核在不同的地址空间下,我们无需在每一次map/unmap之后都立即刷新TLB,只需在所有的操作结束后,即将切换回应用地址空间之前刷新一次TLB即可,这可以参考__restore
的实现。这样做是由于刷新TLB是一个十分耗时的操作,需要尽可能避免不必要的刷新。
您好,我觉得应该发现了一个框架代码中的逻辑bug。 find_pte_create()中: for i in 0..3 { 23 let pte = &mut ppn.get_pte_array()[idxs[i]]; 24 if i == 2 { 25 result = Some(pte); 26 break; 27 } 28 if !pte.is_valid() { 29 let frame = frame_alloc().unwrap(); 30 *pte = PageTableEntry::new(frame.ppn, PTEFlags::V); 31 self.frames.push(frame); 32 } 33 ppn = pte.ppn(); 34 } 判断页表项是否合法的逻辑在判断是否等于2之前,是比较合理的。因为三级页表实际上映射到物理页帧是在MapArea中。在这个函数结束时,已经分配了三级页表的物理页帧。 但在框架代码 find_pte()中: for i in 0..3 { 16 let pte = &ppn.get_pte_array()[idxs[i]]; 17 if i == 2 { 18 result = Some(pte); 19 break; 20 } 21 if !pte.is_valid() { 22 return None; 23 } 24 ppn = pte.ppn(); 25 } 我觉得应该把判断页表项是否合法的逻辑放在判断等于2之前。 这样的话第三级页表是否合法也在检测范围中。 而我确实因为这个改动解决了测试ch2t_write0不通过的问题。所以我觉得它可能是有一点问题的。
页表基本数据结构与访问接口一节的第一行到第二行的位置:
因此 PageTable``要保存它根节点的物理页号 ``root_ppn 作为页表唯一的区分标志
这里估计多写了一个"`"符号导致了高亮错乱
但Bootloader把操作系统内核加载到物理内存中后,物理内存上已经有一部分用于放置内核的代码和数据。
但->当
函数 find_pte 和 find_pte_create 中的 pte 合法性检查放在 i==2 检查前会不会更严谨
我感觉 PageTable 这个结构体的 frames 段可以直接设计成 BTreeMap 映射。因为这样就不需要在 MemoryArea 中再设计 BTreeMap 映射了,内聚性会好一点。
函数 find_pte 和 find_pte_create 中的 pte 合法性检查放在 i==2 检查前会不会更严谨
我们的PageTable仅负责从VPN查到页表项的位置,但是并不要求这个页表项必须合法,这个检查工作应该由find_pte
的调用者完成。
我感觉 PageTable 这个结构体的 frames 段可以直接设计成 BTreeMap 映射。因为这样就不需要在 MemoryArea 中再设计 BTreeMap 映射了,内聚性会好一点。
看起来是个更好的设计。
上一节更多的是站在硬件的角度来分析SV39多级页表的硬件机制,本节我们主要讲解基于 SV39 多级页表机制的操作系统内存管理。这还需进一步管理计算机系统中当前已经使用是或空闲的物理页帧,
这还需进一步管理计算机系统中当前已经使用"是或"空闲的物理页帧 ---这里应该是“或是”
回收掉 FRAME_ALLOCATOR 中: 改成 回收到
这里我们只需为
FrameTracker
实现Drop
Trait 即可。当一个FrameTracker
实例被回收的时候,它的drop
方法会自动被编译器调用,通过之前实现的frame_dealloc
我们就将它控制的物理页帧回收以供后续使用了。
对于“它的 drop
方法会自动被编译器调用”这句表述是不是不太恰当,调用 drop
方法是在运行时进行的,此时显然和编译器没有什么关系。
比如在某个函数中使用了一个 FrameTracker
的实例 frame_tracker
,编译器只是在编译的时候在此函数的末尾自动的插入core::mem::drop(frame_tracker);
这一函数调用语句,应用运行期间才会进行 drop
方法的真正调用。
从 find_pte 的实现还可以看出,即使找到的页表项不合法,还是会将其返回回去而不是返回 None 。这说明在目前的实现中,页表和页表项是相对解耦合的。
这一段为什么说find_pte不返回None?笔误?和代码、前面的内容也对不上
PageTable::find_pte 与 find_pte_create 的不同在于当找不到合法叶子节点的时候不会新建叶子节点而是直接返回 None 即查找失败。
@longguzzz 这里主要在说find_pte
的这段逻辑:
if i == 2 {
result = Some(pte);
break;
}
这里在if
里面并没有再判断pte
是否合法,而是将pte
直接包裹起来返回。所以find_pte
可能返回一个不合法(即标志位V
为0)的页表项。注意叶子节点和页表项并不是一个概念:叶子节点指的是页表树结构的叶子,它包含512个页表项。
当然,这里的描述可能还有些混乱,稍后有空想想如何修改。
@longguzzz 这里主要在说
find_pte
的这段逻辑:if i == 2 { result = Some(pte); break; }
这里在
if
里面并没有再判断pte
是否合法,而是将pte
直接包裹起来返回。所以find_pte
可能返回一个不合法(即标志位V
为0)的页表项。注意叶子节点和页表项并不是一个概念:叶子节点指的是页表树结构的叶子,它包含512个页表项。当然,这里的描述可能还有些混乱,稍后有空想想如何修改。
看代码其实很清晰明确。而且树算法还可以从递归角度想初始条件、转移规则、递归出口,也很清楚。
看了后几章,回过头来想,可能必须要用递归的方式来理解内存寻址,才能理解清楚。 (但也只是个人观点,不知是否正确)
因为用递归的方式思考,更容易发现逻辑上潜在的“访虚址->需页表->在内存中->访虚址”递归链。所以,虽然可能实现算法并不需要用递归,但是为了跳出“虚址<->页表”的逻辑循环思考,可能有必要用递归的方式重新描述一遍算法。
比如这个问题:开启分页机制后,不考虑TLB,内核访问应用数据,通过页表需要几次访问物理内存?(比如sys_waitpid
里用到translated_refmut
,从而在内核里为用户进程传进来的exit_code_ptr
保存其子进程的exit_code
,translated_refmut
调用translate_va
,translate_va
调用find_pte
。从调用translated_refmut
,到把exit_code
存到内存里,这样的过程要访问多少次物理内存?是4次,还是(3+1)*(3+1)=16次?)
用sys_waitpid
里的translated_refmut
举例,(SV39)MMU物理访存是16次的理由如下:
为什么在StackFrameAllocator
的alloc
实现中还需要Some((self.current - 1).into())
?我的理解这里的current
已经就是PhysPageNum
了吧,还是说Rust不支持这样的类型转换?
我的理解这里的current已经就是PhysPageNum了吧,还是说Rust不支持这样的类型转换? 看一下数据结构,current是usize类型
勘误:
在分页机制开启前,这样做自然成立;而开启之后,虽然裸指针被视为一个虚拟地址,但是上面已经提到,基于恒等映射,虚拟地址会映射到一个相同的物理地址,因此在也是成立的
少了一个字,应为“因此现在”
请问一下如何建立“恒等”映射啊?比如如何使用虚拟地址0x80400000访问物理地址0x80400000啊,虚拟地址应该要经过三次页表项的访问才能拿到物理地址,但是页表项本身又怎么建立起来的呢,如何在虚拟地址寻址的情况下,维护页表本身啊
请问一下如何建立“恒等”映射啊?比如如何使用虚拟地址0x80400000访问物理地址0x80400000啊,虚拟地址应该要经过三次页表项的访问才能拿到物理地址,但是页表项本身又怎么建立起来的呢,如何在虚拟地址寻址的情况下,维护页表本身啊
经过分析大概明白了,感觉这块还是比较复杂的,它是在物理寻址的时候建立“恒等”映射,而不是开启虚拟地址寻址后再建立“恒等”映射,在物理寻址期间,会根据MapArea的映射需求建立一个三级页表,页表本身存放到ekernel~MEMORY_END之间,核心是:比如要建立地址ppn的恒等映射,它会先把ppn按照虚拟地址的格式解析,按照类似字典树的方式建立一个三级页表,并在最后一级页表(叶节点)的页表项的物理地址字段写入ppn,即写入本次寻址的地址本身,这样就建立好了一个“恒等”映射。
是否应该将解析设备树相关的内容,从 rustsbi-qemu 移动到这个学习文档中来。 进一步,应该直接基于 rustsbi 来构建 os ,而不是基于 rustsbi-qemu 。理论上 rustsbi、opensbi 是可以相互替换的。
学完这节,我一直没办法理解,是如何实现的恒等映射,我认为是随机映射(任意一个虚拟地址,返回一个目前首先找到的具有随机性的物理页面),我的理解如下:
fn find_pte_create(&mut self,vpn:VirtPageNum) -> Option<&mut PageTableEntry>{
...
let frame = frame_alloc().unwrap();
*pte = PageTableEntry::new(frame.ppn, PTEFlags::V);
self.frames.push(frame);
...
}
在这里,没有实现映射的虚拟地址会调用frame_alloc()
函数进行分配页面,frame_alloc()
函数会调用alloc()
函数,如下:
fn alloc(&mut self) -> Option<PhysPageNum> {
if let Some(ppn) = self.recycled.pop(){
Some(ppn.into())
}else{
if(self.current==self.end){
None
}
else{
self.current+=1;
Some((self.current-1).into())
}
}
}
alloc会找到一个没有被使用的的物理页面,返回回去。 通过上面的步骤,我没发现一个操作,能使虚拟地址得到的就是其相对应的物理页号的页面,他得到的首先是栈(recycled)里回收的页面,如果栈空,就重新申请新页面。
额,这章没有实现恒等映射,他是下一章通过PhsyPageNum(vpn.0)直接访问虚拟页号,然后建立的恒等映射
@WGoodLive find_pte_create
中frame_alloc
分配出来的物理页帧并不是恒等映射的目标物理页帧,而只是用作多级页表的中间节点。find_pte_create
的功能是根据虚拟地址找到一个最终的页表项,然后在页表项里填入与虚拟地址要恒等映射到的物理地址的物理页号以及权限位。因此建立恒等映射的关键一步是最后填页表项,即PageTable::map
函数中的*pte = PageTableEntry::new(ppn, flags | PTEFlags::V);
这一句。
实现 SV39 多级页表机制(下) — rCore-Tutorial-Book-v3 0.1 文档
https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter4/4sv39-implementation-2.html