rcore-os / rCore-Tutorial-Book-v3

A book about how to write OS kernels in Rust easily.
https://rcore-os.github.io/rCore-Tutorial-Book-v3/
GNU General Public License v3.0
1.23k stars 230 forks source link

rCore-Tutorial-Book-v3/chapter4/5kernel-app-spaces #68

Open utterances-bot opened 3 years ago

utterances-bot commented 3 years ago

内核与应用的地址空间 — rCore-Tutorial-Book-v3 0.1 文档

https://rcore-os.github.io/rCore-Tutorial-Book-v3/chapter4/5kernel-app-spaces.html

TroubleMaker369 commented 3 years ago

你好,page_tables.rs中的 unmap方法中获取物理页帧的方法,可以由find_pte_create换成find_pte这个方法吗?然后根据Option进行逻辑的编写,我觉得如果是unmap的话,之前map过的虚拟页号在页表中已经建立了,只需找到物理页帧对应的页表项就行,create可以不用吗?

wyfcyx commented 3 years ago

@TroubleMaker369 你说得对,这里的确应该调用find_pte而非find_pte_create,稍后修改。

csyJoy commented 2 years ago

你好,在from_elf方法中max_end_vpn这个变量为什么是直接赋值,而不是每次都取max。是因为program header的vpn_range是严格递增的吗?

thlstsul commented 2 years ago

页表项标志位没有相关的访问控制逻辑?还是有我没有了解到的机制?请指教

liangyongrui commented 2 years ago

而第 32 行的 from_elf 则可以应用的 ELF 格式可执行文件解析出各数据段并对应生成应用的地址空间

不太通顺

itewqq commented 2 years ago

有一点不太理解, 如果在MemorySet中用Vec来存储MapArea的话, sys_munmap的时候只能遍历这个Vec来找到对应的MapArea? 还是说后面的章节有进一步的优化设计呢?

Yui5427 commented 2 years ago

高于256G且低于0xffff_ffc0_0000_0000是不是不能被访问到,因为通不过mmu

wyfcyx commented 2 years ago

高于256G且低于0xffff_ffc0_0000_0000是不是不能被访问到,因为通不过mmu

你的理解是对的。

RobinWitch commented 1 year ago

在user/src/linker.ld中如果忽略掉第28行(.debug),最后会有Panicked at src/mm/address.rs:202 start VPN:0x80a9f > end VPN:0x80800!的意外情况,请问大家这是为什么啊?

wyfcyx commented 1 year ago

@mxq-151 帮你删掉了。如果想自己删除的话,只需要找到当前节的issue页面就能删掉自己的评论了。

wyfcyx commented 1 year ago

@RobinWitch 这样的话会保留应用可执行文件中的调试信息,使得应用可执行文件大小膨胀,最终超出我们预设的物理内存大小限制。

RobinWitch commented 1 year ago

@RobinWitch 这样的话会保留应用可执行文件中的调试信息,使得应用可执行文件大小膨胀,最终超出我们预设的物理内存大小限制。

@wyfcyx 十分感谢!

longguzzz commented 1 year ago

应用地址空间的布局的配图有误?0x0的位置是空指针

longguzzz commented 1 year ago

因为在应用二进制镜像中,内存布局中各个逻辑段的置和访问限制等信息都被裁剪掉了。

这里的“置和访问限制”是“标志位和访问限制”吗?

wyfcyx commented 1 year ago

@longguzzz 应用地址空间配图稍后更新;“置和访问限制”应该是“位置和访问限制”。

longguzzz commented 1 year ago

如果在trampoline后紧凑地放置Kernel Stack,则 App 0 Kernel Stack范围为左闭右开区间[2^64B-(4KiB+KERNEL_STACK_SIZE), 2^64B-4KiB) App i Kernel Stack范围为左闭右开区间[2^64B-(i+1)×(4KiB+KERNEL_STACK_SIZE), 2^64B-(i+1)×(4KiB+KERNEL_STACK_SIZE)+KERNEL_STACK_SIZE)

App Address Space(High)的图示中标注2^64B-i×(4KiB+KERNEL_STACK_SIZE)的地方实际上在App i-1的最低位置。

wyfcyx commented 1 year ago

@longguzzz 地址空间配图已更新。

ljsheng7128 commented 1 year ago

有个问题需要请教:跳板的虚拟地址为VirtAddr::from(TRAMPOLINE),而TRAMPOLINE为usize::MAX - PAGE_SIZE + 1,但这个地址值已经超越了SV39分页模式的最大合法虚拟地址2^39,甚至也超越了最大合法物理地址2^56,因此应该已经不合法了吧?

Unik-lif commented 1 year ago

你好,在from_elf方法中max_end_vpn这个变量为什么是直接赋值,而不是每次都取max。是因为program header的vpn_range是严格递增的吗?

可以解答一下这个问题吗,我对max_end_vpn的获取这一点也非常困惑。

Unik-lif commented 1 year ago

可以解答一下这个问题吗,我对max_end_vpn的获取这一点也非常困惑。

哦,我找到资料了qaq,求夸!

PT_LOAD Specifies a loadable segment, described by p_filesz and p_memsz. The bytes from the file are mapped to the beginning of the memory segment. If the segment's memory size (p_memsz) is larger than the file size (p_filesz), the extra bytes are defined to hold the value 0 and to follow the segment's initialized area. The file size can not be larger than the memory size. Loadable segment entries in the program header table appear in ascending order, sorted on the p_vaddr member.

https://docs.oracle.com/cd/E19683-01/816-1386/chapter6-83432/index.html

这里提到PT_LOAD类型的代码段是根据p_vaddr来排布的,所以就能解释上面代码关于max_end_vpn的写法了。

VisualMike-I commented 1 year ago

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

VisualMike-I commented 1 year ago

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

wyfcyx commented 1 year ago

@VisualMike-I 是的,可能会产生冲突甚至后加入的段覆写先加入的段的情况,因此目前无论内核还是应用,段与段之间都插入强制4K页对齐。

wyfcyx commented 1 year ago

@VisualMike-I 在MapArea::unmap_one中,self.data_frames.remove(&vpn);其实是删除了一个键值对(注意这是一个map),因此作为值的FrameTracker同时也被删掉了,此时触发了FrameTracker::drop将这个物理页回收。

VisualMike-I commented 1 year ago

@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,可能导致覆盖。

🌹

wyfcyx commented 1 year ago

@VisualMike-I

  1. 关于保存页表的物理页,由于数量很少(只有保存数据的1/512),目前没有设计支持分配之后运行时动态回收的功能,而是进程退出时一起回收的。要支持这个机制的话,得维护页表树结构的引用计数才行,还要考虑如何高效利用内存。
  2. 没注意到.data.bss之间没有页对齐,那么MapArea确实有可能覆盖,好在不影响功能吧,也不是严重问题。后面加上页对齐也可。
xuanz20 commented 1 year ago

请问MapArea中的pub fn copy_data(&mut self, page_table: &mut PageTable, data: &[u8])函数为什么一定需要page_table这个参数,按理说我们完全可以通过MapArea中的data_frames来完成同样的事情啊,比如我这么写:

pub fn copy_data(&mut self, _page_table: &mut PageTable, data: &[u8]) {
        assert_eq!(self.map_type, MapType::Framed);
        let mut start: usize = 0;
        let mut current_vpn = self.vpn_range.get_start();
        let len = data.len();
        loop {
            let src = &data[start..len.min(start + PAGE_SIZE)];
            /*
            let dst = &mut page_table
                .translate(current_vpn)
                .unwrap()
                .ppn()
                .get_bytes_array()[..src.len()];
            */
            let dst = &mut self.data_frames
                .get(&current_vpn)
                .unwrap()
                .ppn
                .get_bytes_array()[..src.len()];
            dst.copy_from_slice(src);
            start += PAGE_SIZE;
            if start >= len {
                break;
            }
            current_vpn.step();
        }
    }

有什么区别吗

xuanz20 commented 1 year ago

四个逻辑段.text/.rodata/.data/.bss被恒等映射到物理内存,这使得我们在无需调整内核内存布局 os/src/linker.ld的情况下就仍能和启用页表机制之前那样访问内核的各数据段。

请问这里无需调整内核内存布局仍能像之前那样访问内核各数据段是什么意思呢? linker.ld链接时候的地址应该也都是虚拟地址我的理解?只要QEMU加载的时候把内核加载到0x80200000就可以了吧

chestNutLsj commented 1 year ago

勘误病句:

实现 push 方法地址空间中插入一个逻辑段 MapArea 的时候

改成“在push方法中实现地址空间中插入一个逻辑段MapArea时”

wyfcyx commented 1 year ago

@chestNutLsj 多谢您近期提出的大量反馈意见。最近工作比较忙,等后面有空了会集中修一下。

chestNutLsj commented 1 year ago

@chestNutLsj 多谢您近期提出的大量反馈意见。最近工作比较忙,等后面有空了会集中修一下。

@wyfcyx 只是做一些力所能及的贡献😀 之前一直是只学习过《精髓与设计原理》这样的理论,rcore这本书的实践给我很大的启发,从代码的层面加深了对系统细节的认识。但是受限于能力和时间因素,只能有限地解决一些瑕疵问题☺️

FuuuOverclocking commented 1 year ago

希望 PageTable 的 map 和 unmap 改个名字,例如 apply_mapping 和 remove_mapping。

FuuuOverclocking commented 1 year ago

上一条写错了,是希望 MapArea 的 map 和 unmap 改名,可改成 apply_mapping 和 remove_mapping。

lythesia commented 1 week ago

请问MapArea中的pub fn copy_data(&mut self, page_table: &mut PageTable, data: &[u8])函数为什么一定需要page_table这个参数,按理说我们完全可以通过MapArea中的data_frames来完成同样的事情啊,比如我这么写:

pub fn copy_data(&mut self, _page_table: &mut PageTable, data: &[u8]) {
        assert_eq!(self.map_type, MapType::Framed);
        let mut start: usize = 0;
        let mut current_vpn = self.vpn_range.get_start();
        let len = data.len();
        loop {
            let src = &data[start..len.min(start + PAGE_SIZE)];
            /*
            let dst = &mut page_table
                .translate(current_vpn)
                .unwrap()
                .ppn()
                .get_bytes_array()[..src.len()];
            */
            let dst = &mut self.data_frames
                .get(&current_vpn)
                .unwrap()
                .ppn
                .get_bytes_array()[..src.len()];
            dst.copy_from_slice(src);
            start += PAGE_SIZE;
            if start >= len {
                break;
            }
            current_vpn.step();
        }
    }

有什么区别吗

确实, 目前看来copy_data()调用的场景一定是Framed, 且地址空间已经映射好了, 所以直接BTreeMap::get没问题

我这里猜一下可能copy_data()可能会在其他场景下被调用, 因为看函数签名就算使用translate()也不需要&mut PageTable&PageTable就够了, 所以translate()的早期设计可能是translate(&mut self, ..), 内部调用的是find_pte_create, 这样也能在地址空间没有映射好的情况下直接调用(会同时构造多级页表的pte)

这样做一定不会出错, 算是一种防御性编程吧