From 40fe15e0953f989ccfeb74826d61621d43dea6bb Mon Sep 17 00:00:00 2001 From: LoGin Date: Sat, 22 Jul 2023 16:27:02 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E7=9A=84=E5=86=85=E5=AD=98=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E6=A8=A1=E5=9D=97=20(#303)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit   实现了具有优秀架构设计的新的内存管理模块,对内核空间和用户空间的内存映射、分配、释放、管理等操作进行了封装,使得内核开发者可以更加方便地进行内存管理。   内存管理模块主要由以下类型的组件组成: - **硬件抽象层(MemoryManagementArch)** - 提供对具体处理器架构的抽象,使得内存管理模块可以在不同的处理器架构上运行 - **页面映射器(PageMapper)**- 提供对虚拟地址和物理地址的映射,以及页表的创建、填写、销毁、权限管理等操作。分为两种类型:内核页表映射器(KernelMapper)和用户页表映射器(位于具体的用户地址空间结构中) - **页面刷新器(PageFlusher)** - 提供对页表的刷新操作(整表刷新、单页刷新、跨核心刷新) - **页帧分配器(FrameAllocator)** - 提供对页帧的分配、释放、管理等操作。具体来说,包括BumpAllocator、BuddyAllocator - **小对象分配器** - 提供对小内存对象的分配、释放、管理等操作。指的是内核里面的SlabAllocator (SlabAllocator的实现目前还没有完成) - **MMIO空间管理器** - 提供对MMIO地址空间的分配、管理操作。(目前这个模块待进一步重构) - **用户地址空间管理机制** - 提供对用户地址空间的管理。 - VMA机制 - 提供对用户地址空间的管理,包括VMA的创建、销毁、权限管理等操作 - 用户映射管理 - 与VMA机制共同作用,管理用户地址空间的映射 - **系统调用层** - 提供对用户空间的内存管理系统调用,包括mmap、munmap、mprotect、mremap等 - **C接口兼容层** - 提供对原有的C代码的接口,是的C代码能够正常运行。 除上面的新增内容以外,其它的更改内容: - 新增二进制加载器,以及elf的解析器 - 解决由于local_irq_save、local_irq_restore函数的汇编不规范导致影响栈行为的bug。 - 解决local_irq_save未关中断的错误。 - 修复sys_gettimeofday对timezone参数的处理的bug --------- Co-authored-by: kong --- .vscode/settings.json | 3 +- Makefile | 12 +- README.md | 15 +- README_EN.md | 8 +- docs/Multiboot2 Specification version 2.0.pdf | Bin 245235 -> 0 bytes docs/community/contact/index.rst | 40 +- docs/introduction/build_system.md | 5 +- docs/introduction/features.md | 24 +- docs/introduction/index.rst | 4 +- docs/kernel/core_api/allocate-memory.md | 15 - docs/kernel/core_api/index.rst | 13 - docs/kernel/core_api/mm-api.md | 267 ---- .../memory_management/allocate-memory.md | 29 + docs/kernel/memory_management/index.rst | 8 +- docs/kernel/memory_management/intro.md | 20 + docs/kernel/process_management/index.rst | 3 +- docs/kernel/process_management/load_binary.md | 15 + kernel/Cargo.toml | 7 + kernel/src/Makefile | 5 +- kernel/src/arch/mod.rs | 4 +- kernel/src/arch/x86_64/asm/irqflags.rs | 5 +- kernel/src/arch/x86_64/context.rs | 8 + kernel/src/arch/x86_64/fpu.rs | 2 +- kernel/src/arch/x86_64/interrupt/mod.rs | 6 +- kernel/src/arch/x86_64/libs/mod.rs | 1 + kernel/src/arch/x86_64/mm/mod.rs | 637 +++++++- kernel/src/arch/x86_64/mod.rs | 6 + kernel/src/arch/x86_64/msi.rs | 2 +- kernel/src/arch/x86_64/syscall.rs | 198 ++- kernel/src/common/atomic.h | 8 + kernel/src/driver/acpi/acpi.c | 34 +- kernel/src/driver/base/char/mod.rs | 10 +- kernel/src/driver/base/device/mod.rs | 3 +- kernel/src/driver/disk/ahci/ahci.c | 2 +- kernel/src/driver/interrupt/apic/apic.c | 10 +- kernel/src/driver/pci/msi.c | 2 +- kernel/src/driver/pci/pci.rs | 64 +- kernel/src/driver/pci/pci_irq.rs | 10 +- kernel/src/driver/uart/uart.rs | 2 +- kernel/src/driver/video/video.c | 17 +- kernel/src/driver/virtio/virtio_impl.rs | 96 +- kernel/src/exception/mod.rs | 6 +- kernel/src/exception/softirq.rs | 1 + kernel/src/filesystem/procfs/mod.rs | 15 +- kernel/src/filesystem/sysfs/bus.rs | 44 +- kernel/src/filesystem/vfs/VFS.h | 2 +- kernel/src/filesystem/vfs/file.rs | 2 +- kernel/src/head.S | 115 +- kernel/src/include/bindings/wrapper.h | 3 +- kernel/src/ipc/signal.rs | 8 +- kernel/src/lib.rs | 15 +- kernel/src/libs/align.rs | 28 +- kernel/src/libs/elf.rs | 785 ++++++++++ kernel/src/libs/libUI/screen_manager.c | 38 +- kernel/src/libs/libUI/screen_manager.h | 4 +- kernel/src/libs/libUI/textui.c | 26 +- kernel/src/libs/libUI/textui.h | 6 +- kernel/src/libs/mod.rs | 1 + kernel/src/libs/printk.c | 4 + kernel/src/libs/printk.rs | 102 +- kernel/src/libs/rwlock.rs | 48 +- kernel/src/libs/spinlock.rs | 32 +- kernel/src/main.c | 58 +- kernel/src/mm/Makefile | 27 - kernel/src/mm/allocator.rs | 55 - kernel/src/mm/allocator/buddy.rs | 667 +++++++++ kernel/src/mm/allocator/bump.rs | 112 ++ kernel/src/mm/allocator/kernel_allocator.rs | 101 ++ kernel/src/mm/allocator/mod.rs | 5 + kernel/src/mm/allocator/page_frame.rs | 338 +++++ kernel/src/mm/allocator/slab.rs | 123 ++ kernel/src/mm/c_adapter.rs | 135 ++ kernel/src/mm/internal.h | 79 - kernel/src/mm/kernel_mapper.rs | 145 ++ kernel/src/mm/mm-stat.c | 196 --- kernel/src/mm/mm-types.h | 175 --- kernel/src/mm/mm.c | 686 --------- kernel/src/mm/mm.h | 379 +---- kernel/src/mm/mmap.c | 582 -------- kernel/src/mm/mmio.c | 9 - kernel/src/mm/mmio.h | 4 +- kernel/src/mm/mmio_buddy.rs | 379 ++--- kernel/src/mm/mod.rs | 613 +++++++- kernel/src/mm/no_init.rs | 79 + kernel/src/mm/page.rs | 924 ++++++++++++ kernel/src/mm/slab.c | 713 --------- kernel/src/mm/slab.h | 113 +- kernel/src/mm/syscall.rs | 232 ++- kernel/src/mm/ucontext.rs | 1319 +++++++++++++++++ kernel/src/mm/utils.c | 109 -- kernel/src/mm/vma.c | 275 ---- kernel/src/process/abi.rs | 86 ++ kernel/src/process/c_adapter.rs | 115 ++ kernel/src/process/exec.rs | 288 ++++ kernel/src/process/fork.c | 88 +- kernel/src/process/fork.rs | 40 +- kernel/src/process/mod.rs | 32 + kernel/src/process/proc-types.h | 29 +- kernel/src/process/process.c | 483 +----- kernel/src/process/process.h | 74 +- kernel/src/process/process.rs | 111 +- kernel/src/sched/cfs.rs | 10 +- kernel/src/sched/core.c | 2 +- kernel/src/sched/core.rs | 3 + kernel/src/sched/rt.rs | 16 +- kernel/src/sched/syscall.rs | 12 +- kernel/src/smp/c_adapter.rs | 13 + kernel/src/smp/mod.rs | 12 +- kernel/src/smp/smp.c | 40 +- kernel/src/syscall/mod.rs | 82 +- kernel/src/syscall/syscall.c | 96 -- kernel/src/syscall/syscall_num.h | 31 +- kernel/src/syscall/user_access.rs | 141 ++ kernel/src/time/clocksource.rs | 4 +- kernel/src/time/jiffies.rs | 12 +- kernel/src/time/syscall.rs | 13 +- kernel/src/time/timekeeping.rs | 8 +- kernel/src/time/timer.h | 3 +- kernel/src/time/timer.rs | 5 +- user/apps/about/about.c | 3 + user/apps/shell/cmd.c | 27 +- user/apps/test_relibc/Makefile | 2 +- user/libs/libc/src/malloc.c | 1 + user/libs/libc/src/unistd.c | 8 +- 124 files changed, 8277 insertions(+), 5150 deletions(-) delete mode 100644 docs/Multiboot2 Specification version 2.0.pdf delete mode 100644 docs/kernel/core_api/allocate-memory.md delete mode 100644 docs/kernel/core_api/mm-api.md create mode 100644 docs/kernel/memory_management/allocate-memory.md create mode 100644 docs/kernel/memory_management/intro.md create mode 100644 docs/kernel/process_management/load_binary.md create mode 100644 kernel/src/arch/x86_64/libs/mod.rs create mode 100644 kernel/src/libs/elf.rs delete mode 100644 kernel/src/mm/Makefile delete mode 100644 kernel/src/mm/allocator.rs create mode 100644 kernel/src/mm/allocator/buddy.rs create mode 100644 kernel/src/mm/allocator/bump.rs create mode 100644 kernel/src/mm/allocator/kernel_allocator.rs create mode 100644 kernel/src/mm/allocator/mod.rs create mode 100644 kernel/src/mm/allocator/page_frame.rs create mode 100644 kernel/src/mm/allocator/slab.rs create mode 100644 kernel/src/mm/c_adapter.rs delete mode 100644 kernel/src/mm/internal.h create mode 100644 kernel/src/mm/kernel_mapper.rs delete mode 100644 kernel/src/mm/mm-stat.c delete mode 100644 kernel/src/mm/mm.c delete mode 100644 kernel/src/mm/mmap.c delete mode 100644 kernel/src/mm/mmio.c create mode 100644 kernel/src/mm/no_init.rs create mode 100644 kernel/src/mm/page.rs delete mode 100644 kernel/src/mm/slab.c create mode 100644 kernel/src/mm/ucontext.rs delete mode 100644 kernel/src/mm/utils.c delete mode 100644 kernel/src/mm/vma.c create mode 100644 kernel/src/process/abi.rs create mode 100644 kernel/src/process/c_adapter.rs create mode 100644 kernel/src/process/exec.rs create mode 100644 kernel/src/smp/c_adapter.rs create mode 100644 kernel/src/syscall/user_access.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index 28d66802..4e904bad 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -174,7 +174,8 @@ "sleep.h": "c", "net.h": "c", "lz4.h": "c", - "cmd_test.h": "c" + "cmd_test.h": "c", + "cmpxchg.h": "c" }, "C_Cpp.errorSquiggles": "enabled", "esbonio.sphinx.confDir": "", diff --git a/Makefile b/Makefile index 4cbcaaef..876a19c1 100644 --- a/Makefile +++ b/Makefile @@ -61,14 +61,24 @@ clean: cd .. ;\ done +.PHONY: ECHO +ECHO: + @echo "$@" + cppcheck-xml: cppcheck kernel user --platform=unix64 --std=c11 -I user/libs/ -I=kernel/ --force -j $(NPROCS) --xml 2> cppcheck.xml cppcheck: cppcheck kernel user --platform=unix64 --std=c11 -I user/libs/ -I=kernel/ --force -j $(NPROCS) +docs: ECHO + bash -c "cd docs && make html && cd .." + +clean-docs: + bash -c "cd docs && make clean && cd .." + gdb: - gdb -n -x tools/.gdbinit + rust-gdb -n -x tools/.gdbinit # 写入磁盘镜像 write_diskimage: diff --git a/README.md b/README.md index 84961f3c..9c881711 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # DragonOS +## 打造完全自主可控的数字化未来! + +--- + **Languages** 中文|[English](README_EN.md)   @@ -18,6 +22,8 @@ - 项目文档 **[docs.DragonOS.org](https://docs.dragonos.org)** +- **了解开发动态、开发任务,请访问DragonOS的zulip社群**: [https://DragonOS.zulipchat.com](https://DragonOS.zulipchat.com) + - 开源论坛 **[bbs.DragonOS.org](https://bbs.dragonos.org)** - 软件镜像站 **[mirrors.DragonOS.org](https://mirrors.DragonOS.org)** @@ -42,9 +48,9 @@ ## 如何加入? -  如果你愿意加入我们,你可以查看GitHub仓库的Project面板,选择近期已规划的功能,对他们进行完善。 +  如果你愿意加入我们,你可以访问DragonOS的zulip社群,了解开发动态、开发任务: [https://DragonOS.zulipchat.com](https://DragonOS.zulipchat.com) -  或者,你也可以带着你的创意与想法,和社区的小伙伴一起讨论,为DragonOS创造一些新的功能。 +  你也可以带着你的创意与想法,和社区的小伙伴一起讨论,为DragonOS创造一些新的功能。 ## 如何与社区建立联系? @@ -52,9 +58,8 @@   或者是加入我们的开发交流QQ群:**115763565** -  对于正式问题的讨论,我们建议在论坛[bbs.DragonOS.org](https://bbs.dragonos.org/)上的对应板块,使用正式的语言发帖讨论。亦或者是在本仓库的issue下提出问题。 +  对于正式问题的讨论,我们建议在 **[DragonOS的zulip社群](https://DragonOS.zulipchat.com)** 上的对应板块,使用正式的语言发帖讨论。亦或者是在本仓库的issue下提出问题。 -  在发帖的同时,可以把帖子转发到交流QQ群,这样能使得问题的交流更加高效,也便于问题的归档。 ## 贡献者名单 @@ -68,7 +73,7 @@ ## 赞助 -  DragonOS是一个公益性质的开源项目,但是它的发展离不开资金的支持,如果您愿意的话,可以通过 ** [赞助 - DragonOS](https://dragonos.org/?page_id=37) ** ,从而促进这个项目的发展。所有的赞助者的名单都会被公示。您的每一分赞助,都会为DragonOS的发展作出贡献! +  DragonOS是一个公益性质的开源项目,但是它的发展离不开资金的支持,如果您愿意的话,可以通过 **[赞助 - DragonOS](https://dragonos.org/?page_id=37)** ,从而促进这个项目的发展。所有的赞助者的名单都会被公示。您的每一分赞助,都会为DragonOS的发展作出贡献! ### 赞助的资金都会被用到哪里? diff --git a/README_EN.md b/README_EN.md index dafd3ed1..c4ab5b76 100644 --- a/README_EN.md +++ b/README_EN.md @@ -12,6 +12,7 @@ - Home Page **[DragonOS.org](https://dragonos.org)** - Documentation **[docs.DragonOS.org](https://docs.dragonos.org)** +- **To learn about development dynamics and development tasks, please visit DragonOS's zulip community:** [https://DragonOS.zulipchat.com](https://DragonOS.zulipchat.com) - BBS **[bbs.DragonOS.org](https://bbs.dragonos.org)** - Software mirror website **[mirrors.DragonOS.org](https://mirrors.DragonOS.org)** - QQ group **115763565** @@ -31,7 +32,7 @@ ## How to join DragonOS ? -  If you are willing to join us, you can check the project panel of the GitHub repo, select the recently planned functions, and improve them. +  If you are willing to join us, you can visit DragonOS's zulip community, learn about development dynamics and development tasks: [https://DragonOS.zulipchat.com](https://DragonOS.zulipchat.com)   Or, you can also bring your ideas, discuss with community members, and create some new functions for DragonOS. @@ -41,9 +42,8 @@   Or join our development exchange QQ group: **115763565** -  For the discussion of formal issues, we suggest that they be discussed in the forum [BBS.Dragonos.org](https://bbs.dragonos.org/) In the corresponding section of the, use formal language to post for discussion. Or ask questions under the issue of the warehouse. +  For the discussion of formal issues, we recommend that you use the official language to post on the corresponding section of **[DragonOS's zulip community](https://DragonOS.zulipchat.com)**. Or you can post questions under the issue of this repository. -  While posting, you can forward the post to the communication QQ group, which can make the communication of problems more efficient and facilitate the archiving of problems. ## List of contributors @@ -58,7 +58,7 @@ Maintainer longjin's Email:longjin@DragonOS.org ## Reward -  DragonOS is an open source public welfare project, but its development cannot be separated from the support of funds. If you want, you can visit ** [Sponsor - DragonOS](https://dragonos.org/?page_id=37) ** , so as to promote the development of this project. The list of all sponsors will be published. Every bit of your sponsorship will contribute to the development of DragonOS! +  DragonOS is an open source public welfare project, but its development cannot be separated from the support of funds. If you want, you can visit **[Sponsor - DragonOS](https://dragonos.org/?page_id=37)** , so as to promote the development of this project. The list of all sponsors will be published. Every bit of your sponsorship will contribute to the development of DragonOS! ### Where will the sponsorship funds be used? diff --git a/docs/Multiboot2 Specification version 2.0.pdf b/docs/Multiboot2 Specification version 2.0.pdf deleted file mode 100644 index 97baac9f6f12b7d9fafa78d30ca4a94f9b4e4ce5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 245235 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

U?z$?Z!xQc+l2I&yXGX=c;jS>D1xs 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 EA8buA2mk;8 diff --git a/docs/community/contact/index.rst b/docs/community/contact/index.rst index 7ddb6480..2abe7b25 100644 --- a/docs/community/contact/index.rst +++ b/docs/community/contact/index.rst @@ -3,12 +3,50 @@ 与社区建立联系 ==================================== +联系方式 +------------------------- + 社区公共邮箱:contact@DragonOS.org -DragonOS负责人: longjin +DragonOS社区负责人: longjin 工作邮箱: longjin@DragonOS.org 开发交流QQ群: 115763565 DragonOS官网: https://DragonOS.org + +了解开发动态、开发任务,请访问DragonOS的zulip社群: https://DragonOS.zulipchat.com + + +赞助及捐赠 +------------------------- + +DragonOS是一个开源项目,我们欢迎任何形式的赞助和捐赠,您的捐赠将用于DragonOS的开发和维护,以及社区的运营。 + +您可以通过以下方式赞助和捐赠: + +- 访问DragonOS官网 https://DragonOS.org ,点击页面右上角的“赞助”按钮,进行捐赠 +- 联系社区负责人,沟通具体的赞助方式等。 + +财务及捐赠信息公开 +------------------------- + +DragonOS社区的捐赠信息将按年进行公开。赞助商、赞助者信息将在收到赞助后,15天内进行公开。 + +社区管理、财务及法务主体 +------------------------- + +DragonOS社区的管理、财务及法务主体为:灵高计算机系统(广州)有限公司。 + +我们是一家开源公司,我们坚信,开源能为我国将来的IT,打下更好的基础。我们也通过其他业务创收,投入到DragonOS的研发之中。 + +公司负责DragonOS社区的运营、财务、法务事项处理工作。 + +地址:广东省广州市番禺区小谷围街广州大学城华南理工大学大学城校区 + +邮件:contact@DragonOS.org + +官网:https://ringotek.com.cn + + diff --git a/docs/introduction/build_system.md b/docs/introduction/build_system.md index 61019acc..38e91cd5 100644 --- a/docs/introduction/build_system.md +++ b/docs/introduction/build_system.md @@ -233,7 +233,10 @@ make run-docker - Docker编译,并写入磁盘镜像,: `make docker` - Docker编译,写入磁盘镜像,并在QEMU中运行: `make run-docker` - 不编译,直接从已有的磁盘镜像启动: `make qemu` +- 清理编译产生的文件: `make clean` +- 编译文档: `make docs` (需要手动安装sphinx以及docs下的`requirements.txt`中的依赖) +- 清理文档: `make clean-docs` :::{note} 如果您需要在vnc中运行DragonOS,请在上述命令后加上`-vnc`后缀。如:`make run-vnc` -::: \ No newline at end of file +::: diff --git a/docs/introduction/features.md b/docs/introduction/features.md index e61ad879..77071106 100644 --- a/docs/introduction/features.md +++ b/docs/introduction/features.md @@ -12,10 +12,14 @@ ### 内存管理 -- [x] 页分配器 -- [x] slab分配器 +- [x] 页帧分配器 +- [x] 小对象分配器 - [x] VMA - [x] MMIO地址空间自动分配 +- [x] 页面映射器 +- [x] 硬件抽象层 +- [x] 独立的用户地址空间管理机制 +- [x] C接口兼容层 ### 多核 @@ -31,6 +35,7 @@ - [x] exec - [x] 进程睡眠(支持高精度睡眠) - [x] kthread机制 +- [x] 可扩展二进制加载器 #### 同步原语 @@ -43,7 +48,10 @@ ### 调度 - [x] CFS调度器 +- [x] 实时调度器(FIFO、RR) - [x] 单核调度 +- [x] 多核调度 +- [x] 负载均衡 ### IPC @@ -56,7 +64,8 @@ - [x] fat12/16/32 - [x] Devfs - [x] RamFS -- [x] procfs +- [x] Procfs +- [x] Sysfs ### 异常及中断处理 @@ -95,7 +104,7 @@ - [x] ACPI 高级电源配置模块 - [x] IDE硬盘 - [x] AHCI硬盘 -- [x] PCI +- [x] PCI、PCIe总线 - [x] XHCI(usb3.0) - [x] ps/2 键盘 - [x] ps/2 鼠标 @@ -107,6 +116,8 @@ - [x] VirtIO网卡 - [x] x87FPU - [x] TTY终端 +- [x] 浮点处理器 + ## 用户层 @@ -121,9 +132,9 @@ - [x] 基于字符串匹配的解析 - [x] 基本的几个命令 -### 驱动程序 +### Http Server -- [x] ps/2键盘用户态驱动 +- 使用C编写的简单的Http Server,能够运行静态网站。 ## 软件移植 @@ -132,3 +143,4 @@ - [x] gmp 6.2.1 [https://github.com/DragonOS-Community/gmp-6.2.1](https://github.com/DragonOS-Community/gmp-6.2.1) - [x] mpfr 4.1.1 [https://github.com/DragonOS-Community/mpfr](https://github.com/DragonOS-Community/mpfr) - [x] mpc 1.2.1 [https://github.com/DragonOS-Community/mpc](https://github.com/DragonOS-Community/mpc) +- [x] relibc [https://github.com/DragonOS-Community/relibc](https://github.com/DragonOS-Community/relibc) diff --git a/docs/introduction/index.rst b/docs/introduction/index.rst index 763b97d4..1b32565f 100644 --- a/docs/introduction/index.rst +++ b/docs/introduction/index.rst @@ -3,9 +3,11 @@ DragonOS简介 DragonOS龙操作系统(以下简称“DragonOS”)是一个面向服务器领域的,从0开发内核及用户态环境,并提供Linux兼容性的64位操作系统。它使用Rust与C语言进行编写,并正在逐步淘汰原有的C代码,以在将来提供更好的安全性与可靠性。 +**我们致力于打造完全自主可控的数字化未来!** + DragonOS的目标是,构建一个完全独立自主的、开源的、高性能及高可靠性的服务器操作系统,为国家数字基础设施建设提供完全独立自主的底层核心动力。 - 作为一个社区驱动的开源操作系统,为了促进其发展,避免让其遭受一些不遵守开源协议的商业公司的侵权,我们决定使用GPLv2协议开放源代码,以严格的开源协议来保护DragonOS。 + 作为一个社区驱动的开源操作系统,为了促进开源社区建设,并避免让其遭受一些不遵守开源协议的商业公司的侵权,我们决定使用GPLv2协议开放源代码,以严格的开源协议来保护DragonOS。 你可能对DragonOS中已经实现了哪些功能感兴趣,您可以转到这里::ref:`功能特性 <_genreal_features>` diff --git a/docs/kernel/core_api/allocate-memory.md b/docs/kernel/core_api/allocate-memory.md deleted file mode 100644 index 43874c04..00000000 --- a/docs/kernel/core_api/allocate-memory.md +++ /dev/null @@ -1,15 +0,0 @@ -# 内存分配指南 - -DragonOS提供了一些用于内存分配的api。您可以使用*kmalloc*来分配小的内存块,也可以使用*alloc_pages*分配连续的2MB大小的内存页面。 - -## 选择合适的内存分配器 - -在内核中,最直接、最简单的分配内存的方式就是,使用`kmalloc()`函数进行分配。并且,出于安全起见,除非内存在分配后一定会被覆盖,且您能确保内存中的脏数据一定不会对程序造成影响,在其余情况下,我们建议使用`kzalloc()`进行内存分配,它将会在`kmalloc()`的基础上,把申请到的内存进行清零。 - -您可以通过`kmalloc()`函数分配得到32bytes到1MBytes之间的内存对象。并且,这些内存对象具有以下的性质: - -- 内存起始地址及大小按照2次幂对齐。(比如,申请的是80bytes的内存空间,那么获得的内存对象大小为128bytes且内存地址按照128bytes对齐) - -对于需要大量连续内存的分配,可以使用`alloc_pages()`向页面分配器申请连续的内存页。 - -当内存空间不再被使用时,那么必须释放他们。若您使用的是`kmalloc()`分配的内存,那么您需要使用`kfree()`释放它。若是使用`alloc_pages()`分配的内存,则需要使用`free_pages()`来释放它们。 diff --git a/docs/kernel/core_api/index.rst b/docs/kernel/core_api/index.rst index 8152fcbb..92899c3c 100644 --- a/docs/kernel/core_api/index.rst +++ b/docs/kernel/core_api/index.rst @@ -13,16 +13,3 @@ data_structures casting softirq - -内存管理 -=================== - - 这里快速讲解了如何在DragonOS中分配、使用内存。以便您能快速的了解这个模块。 - - 详细的内存管理模块的文档请参见::ref:`memory_management_module` - -.. toctree:: - :maxdepth: 1 - - allocate-memory - mm-api \ No newline at end of file diff --git a/docs/kernel/core_api/mm-api.md b/docs/kernel/core_api/mm-api.md deleted file mode 100644 index a05276c1..00000000 --- a/docs/kernel/core_api/mm-api.md +++ /dev/null @@ -1,267 +0,0 @@ -(_core_mm_api)= - -# 内存管理API - -## SLAB内存池 - -SLAB内存池提供小内存对象的分配功能。 - -### `void *kmalloc(unsigned long size, gfp_t gfp)` - -  获取小块的内存。 - -#### 描述 - -  kmalloc用于获取那些小于2M内存页大小的内存对象。可分配的内存对象大小为32bytes~1MBytes. 且分配的内存块大小、起始地址按照2的n次幂进行对齐。(比如,申请的是80bytes的内存空间,那么获得的内存对象大小为128bytes且内存地址按照128bytes对齐) - -##### 参数 - -**size** - -  内存对象的大小 - -**gfp** - -  标志位 - -### `void *kzalloc(unsigned long size, gfp_t gfp)` - -#### 描述 - -  获取小块的内存,并将其清零。其余功能与kmalloc相同。 - - -##### 参数 - -**size** - -  内存对象的大小 - -**gfp** - -  标志位 - -### `unsigned long kfree(void *address)` - -  释放从slab分配的内存。 - -#### 描述 - -  该函数用于释放通过kmalloc申请的内存。如果`address`为NULL,则函数被调用后,无事发生。 - -  请不要通过这个函数释放那些不是从`kmalloc()`或`kzalloc()`申请的内存,否则将会导致系统崩溃。 - -##### 参数 - -**address** - -  指向内存对象的起始地址的指针 - -## 物理页管理 - -DragonOS支持对物理页的直接操作 - -### `struct Page *alloc_pages(unsigned int zone_select, int num, ul flags)` - -#### 描述 - -  从物理页管理单元中申请一段连续的物理页 - -#### 参数 - -**zone_select** - -  要申请的物理页所位于的内存区域 - -可选值: - -- `ZONE_DMA` DMA映射专用区域 -- `ZONE_NORMAL` 正常的物理内存区域,已在页表高地址处映射 -- `ZONE_UNMAPPED_IN_PGT` 尚未在页表中映射的区域 - -**num** - -  要申请的连续物理页的数目,该值应当小于64 - -**flags** - -  分配的页面要被设置成的属性 - -可选值: - -- `PAGE_PGT_MAPPED` 页面在页表中已被映射 -- `PAGE_KERNEL_INIT` 内核初始化所占用的页 -- `PAGE_DEVICE` 设备MMIO映射的内存 -- `PAGE_KERNEL` 内核层页 -- `PAGE_SHARED` 共享页 - -#### 返回值 - -##### 成功 - -  成功申请则返回指向起始页面的Page结构体的指针 - -##### 失败 - -  当ZONE错误或内存不足时,返回`NULL` - -### `void free_pages(struct Page *page, int number)` - -#### 描述 - -  从物理页管理单元中释放一段连续的物理页。 - -#### 参数 - -**page** - -  要释放的第一个物理页的Page结构体 - -**number** - -  要释放的连续内存页的数量。该值应小于64 - -## 页表管理 - -### `int mm_map_phys_addr(ul virt_addr_start, ul phys_addr_start, ul length, ul flags, bool use4k)` - -#### 描述 - -  将一段物理地址映射到当前页表的指定虚拟地址处 - -#### 参数 - -**virt_addr_start** - -  虚拟地址的起始地址 - -**phys_addr_start** - -  物理地址的起始地址 - -**length** - -  要映射的地址空间的长度 - -**flags** - -  页表项的属性 - -**use4k** - -  使用4级页表,将地址区域映射为若干4K页 - -### `int mm_map_proc_page_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul phys_addr_start, ul length, ul flags, bool user, bool flush, bool use4k)` - -#### 描述 - -  将一段物理地址映射到指定页表的指定虚拟地址处 - -#### 参数 - -**proc_page_table_addr** - -  指定的顶层页表的起始地址 - -**is_phys** - -  该顶层页表地址是否为物理地址 - -**virt_addr_start** - -  虚拟地址的起始地址 - -**phys_addr_start** - -  物理地址的起始地址 - -**length** - -  要映射的地址空间的长度 - -**flags** - -  页表项的属性 - -**user** - -  页面是否为用户态可访问 - -**flush** - -  完成映射后,是否刷新TLB - -**use4k** - -  使用4级页表,将地址区域映射为若干4K页 - -#### 返回值 - -- 映射成功:0 -- 映射失败:-EFAULT - -### `void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul length)` - -#### 描述 - -  取消给定页表中的指定地址空间的页表项映射。 - -#### 参数 - -**proc_page_table_addr** - -  指定的顶层页表的基地址 - -**is_phys** - -  该顶层页表地址是否为物理地址 - -**virt_addr_start** - -  虚拟地址的起始地址 - -**length** - -  要取消映射的地址空间的长度 - -### `mm_unmap_addr(virt_addr, length)` - -#### 描述 - -  该宏定义用于取消当前进程的页表中的指定地址空间的页表项映射。 - -#### 参数 - -**virt_addr** - -  虚拟地址的起始地址 - -**length** - -  要取消映射的地址空间的长度 - -## 内存信息获取 - -### `struct mm_stat_t mm_stat()` - -#### 描述 - -  获取计算机目前的内存空间使用情况 - -#### 参数 - -无 - -#### 返回值 - -  返回值是一个`mm_mstat_t`结构体,该结构体定义于`mm/mm.h`中。其中包含了以下信息(单位均为字节): - -| 参数名 | 解释 | -| ---------- | ----------------------- | -| total | 计算机的总内存数量大小 | -| used | 已使用的内存大小 | -| free | 空闲物理页所占的内存大小 | -| shared | 共享的内存大小 | -| cache_used | 位于slab缓冲区中的已使用的内存大小 | -| cache_free | 位于slab缓冲区中的空闲的内存大小 | -| available | 系统总空闲内存大小(包括kmalloc缓冲区) | \ No newline at end of file diff --git a/docs/kernel/memory_management/allocate-memory.md b/docs/kernel/memory_management/allocate-memory.md new file mode 100644 index 00000000..b4ce2b5c --- /dev/null +++ b/docs/kernel/memory_management/allocate-memory.md @@ -0,0 +1,29 @@ +# 内存分配指南 + +  本文将讲述如何在内核中进行内存分配。在开始之前,请您先了解一个基本点:DragonOS的内核使用4KB的页来管理内存,并且具有伙伴分配器和slab分配器。并且对用户空间、内核空间均具有特定的管理机制。 + +## 1. 安全的分配内存 + +  在默认情况下,KernelAllocator被绑定为全局内存分配器,它会根据请求分配的内存大小,自动选择使用slab还是伙伴分配器。因此,在内核中,使用Rust原生的 +内存分配函数,或者是创建一个`Box`对象等等,都是安全的。 + + +## 2. 手动管理页帧 + +:::{warning} +**请格外小心!** 手动管理页帧脱离了Rust的内存安全机制,因此可能会造成内存泄漏或者是内存错误。 +::: + +  在某些情况下,我们需要手动分配页帧。例如,我们需要在内核中创建一个新的页表,或者是在内核中创建一个新的地址空间。这时候,我们需要手动分配页帧。使用`LockedFrameAllocator`的`allocate()`函数,能够分配在物理地址上连续的页帧。请注意,由于底层使用的是buddy分配器,因此页帧数目必须是2的n次幂,且最大大小不超过1GB。 + +  当需要释放页帧的时候,使用`LockedFrameAllocator`的`deallocate()`函数,或者是`deallocate_page_frames()`函数,能够释放在物理地址上连续的页帧。 + +  当您需要映射页帧的时候,可使用`KernelMapper::lock()`函数,获得一个内核映射器对象,然后进行映射。由于KernelMapper是对PageMapper的封装,因此您在获取KernelMapper之后,可以使用PageMapper相关接口对内核空间的映射进行管理。 + +:::{warning} +**千万不要** 使用KernelMapper去映射用户地址空间的内存,这会使得这部分内存脱离用户地址空间的管理,从而导致内存错误。 +::: + +## 3. 为用户程序分配内存 + +  在内核中,您可以使用用户地址空间结构体(`AddressSpace`)的`mmap()`,`map_anonymous()`等函数,为用户程序分配内存。这些函数会自动将用户程序的内存映射到用户地址空间中,并且会自动创建VMA结构体。您可以使用`AddressSpace`的`munmap()`函数,将用户程序的内存从用户地址空间中解除映射,并且销毁VMA结构体。调整权限等操作可以使用`AddressSpace`的`mprotect()`函数。 diff --git a/docs/kernel/memory_management/index.rst b/docs/kernel/memory_management/index.rst index 421ecc00..16cb74db 100644 --- a/docs/kernel/memory_management/index.rst +++ b/docs/kernel/memory_management/index.rst @@ -1,14 +1,14 @@ .. _memory_management_module: ==================================== -内存管理文档 +内存管理 ==================================== - 这里讲解了内存管理模块的一些设计及实现原理。 - - 如果你正在寻找使用内存管理模块的方法,请转到::ref:`内存管理API文档 <_core_mm_api>` + 这里讲解了内存管理模块的一些设计及实现原理,以及相应的接口。 .. toctree:: :maxdepth: 1 + intro + allocate-memory mmio \ No newline at end of file diff --git a/docs/kernel/memory_management/intro.md b/docs/kernel/memory_management/intro.md new file mode 100644 index 00000000..b459652d --- /dev/null +++ b/docs/kernel/memory_management/intro.md @@ -0,0 +1,20 @@ +# 内存管理模块简介 + +## 1. 概述 + +  DragonOS实现了具有优秀架构设计的内存管理模块,对内核空间和用户空间的内存映射、分配、释放、管理等操作进行了封装,使得内核开发者可以更加方便地进行内存管理。 + +  DragonOS的内存管理模块主要由以下类型的组件组成: + +- **硬件抽象层(MemoryManagementArch)** - 提供对具体处理器架构的抽象,使得内存管理模块可以在不同的处理器架构上运行 +- **页面映射器(PageMapper)**- 提供对虚拟地址和物理地址的映射,以及页表的创建、填写、销毁、权限管理等操作。分为两种类型:内核页表映射器(KernelMapper)和用户页表映射器(位于具体的用户地址空间结构中) +- **页面刷新器(PageFlusher)** - 提供对页表的刷新操作(整表刷新、单页刷新、跨核心刷新) +- **页帧分配器(FrameAllocator)** - 提供对页帧的分配、释放、管理等操作。具体来说,包括BumpAllocator、BuddyAllocator +- **小对象分配器** - 提供对小内存对象的分配、释放、管理等操作。指的是内核里面的SlabAllocator (SlabAllocator的实现目前还没有完成) +- **MMIO空间管理器** - 提供对MMIO地址空间的分配、管理操作。(目前这个模块待进一步重构) +- **用户地址空间管理机制** - 提供对用户地址空间的管理。 + - VMA机制 - 提供对用户地址空间的管理,包括VMA的创建、销毁、权限管理等操作 + - 用户映射管理 - 与VMA机制共同作用,管理用户地址空间的映射 +- **系统调用层** - 提供对用户空间的内存管理系统调用,包括mmap、munmap、mprotect、mremap等 +- **C接口兼容层** - 提供对原有的C代码的接口,使得C代码能够正常运行。 + diff --git a/docs/kernel/process_management/index.rst b/docs/kernel/process_management/index.rst index 747f1aee..c25eb3ef 100644 --- a/docs/kernel/process_management/index.rst +++ b/docs/kernel/process_management/index.rst @@ -5,4 +5,5 @@ :maxdepth: 1 kthread - pcb \ No newline at end of file + pcb + load_binary diff --git a/docs/kernel/process_management/load_binary.md b/docs/kernel/process_management/load_binary.md new file mode 100644 index 00000000..27b68ba6 --- /dev/null +++ b/docs/kernel/process_management/load_binary.md @@ -0,0 +1,15 @@ +# 加载程序 + +## 1. 二进制程序装载 + +  在小节,你将了解DragonOS的二进制程序加载器的原理。 + +  DragonOS在装载二进制程序时,执行了“探测-装载”的过程。 + +  在探测阶段,DragonOS会读取文件首部,然后依次调用各个二进制加载器的探测函数,判断该二进制程序是否适用于该加载器。如果适用,则使用这个加载器进行装载。 + +  在装载阶段,DragonOS会使用上述加载器进行装载。装载器会将二进制程序的各个段映射到内存中,并且得到二进制程序的入口地址。 + +:::{note} +目前DragonOS不支持动态链接,因此所有的二进制程序都是静态链接的。并且暂时支持的只有ELF加载器。 +::: diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 361204ee..99ea9e0a 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -22,6 +22,9 @@ smoltcp = { git = "https://github.com/DragonOS-Community/smoltcp.git", rev = "90 num-traits = { git = "https://github.com/DragonOS-Community/num-traits.git", rev="1597c1c", default-features = false } num = { version = "0.4.0", default-features = false } num-derive = "0.3" +# 一个no_std的hashmap、hashset +hashbrown = "0.13.2" +elf = { version = "0.7.2", default-features = false } # 构建时依赖项 [build-dependencies] @@ -34,4 +37,8 @@ version = "1.4.0" features = ["spin_no_std"] +# The release profile, used for `cargo build --release` +[profile.release] +debug = false + diff --git a/kernel/src/Makefile b/kernel/src/Makefile index ef693996..7500550a 100644 --- a/kernel/src/Makefile +++ b/kernel/src/Makefile @@ -17,7 +17,7 @@ export ASFLAGS := --64 LD_LIST := head.o -kernel_subdirs := common driver process debug arch exception mm smp sched syscall ktest libs ipc io time +kernel_subdirs := common driver process debug arch exception smp sched syscall ktest libs ipc io time @@ -58,7 +58,8 @@ all: kernel $(LD) -b elf64-x86-64 -z muldefs -o kernel head.o main.o $(shell find . -name "*.o") ../target/x86_64-unknown-none/release/libdragonos_kernel.a ./debug/kallsyms.o -T link.lds @echo "Generating kernel ELF file..." # 生成内核文件 - $(OBJCOPY) -I elf64-x86-64 -O elf64-x86-64 -R ".comment" -R ".eh_frame" kernel ../../bin/kernel/kernel.elf + $(OBJCOPY) -I elf64-x86-64 -O elf64-x86-64 kernel ../../bin/kernel/kernel.elf +# $(OBJCOPY) -I elf64-x86-64 -O elf64-x86-64 -R ".comment" -R ".eh_frame" kernel ../../bin/kernel/kernel.elf @echo "Kernel Build Done." ECHO: diff --git a/kernel/src/arch/mod.rs b/kernel/src/arch/mod.rs index aa6b0495..fd04262c 100644 --- a/kernel/src/arch/mod.rs +++ b/kernel/src/arch/mod.rs @@ -1,9 +1,9 @@ pub mod x86_64; #[cfg(target_arch = "x86_64")] -pub use self::x86_64::pci::pci::X86_64PciArch as PciArch; -#[cfg(target_arch = "x86_64")] pub use self::x86_64::*; //公开x86_64架构下的函数,使外界接口统一 + use crate::driver::pci::pci::{BusDeviceFunction, PciAddr, PciError, PciRoot, SegmentGroupNumber}; + /// TraitPciArch Pci架构相关函数,任何架构都应独立实现trait里的函数 pub trait TraitPciArch { /// @brief 读取寄存器值,x86_64架构通过读取两个特定io端口实现 diff --git a/kernel/src/arch/x86_64/asm/irqflags.rs b/kernel/src/arch/x86_64/asm/irqflags.rs index 667c8c8c..c9bb481c 100644 --- a/kernel/src/arch/x86_64/asm/irqflags.rs +++ b/kernel/src/arch/x86_64/asm/irqflags.rs @@ -3,8 +3,9 @@ use core::arch::asm; #[inline] pub fn local_irq_save() -> usize { let x: usize; + // x86_64::registers::rflags:: unsafe { - asm!("pushfq ; pop {} ; cli", out(reg) x, options(nostack)); + asm!("pushfq; pop {}; cli", out(reg) x, options(nomem, preserves_flags)); } x } @@ -13,6 +14,6 @@ pub fn local_irq_save() -> usize { // 恢复先前保存的rflags的值x pub fn local_irq_restore(x: usize) { unsafe { - asm!("push {} ; popfq", in(reg) x, options(nostack)); + asm!("push {}; popfq", in(reg) x, options(nomem, preserves_flags)); } } diff --git a/kernel/src/arch/x86_64/context.rs b/kernel/src/arch/x86_64/context.rs index 2c1a9576..b52a0b9b 100644 --- a/kernel/src/arch/x86_64/context.rs +++ b/kernel/src/arch/x86_64/context.rs @@ -16,7 +16,15 @@ pub fn switch_process( fp_state_save(prev); fp_state_restore(next); compiler_fence(core::sync::atomic::Ordering::SeqCst); + let new_address_space = next.address_space().unwrap_or_else(|| { + panic!( + "switch_process: next process:{} address space is null", + next.pid + ) + }); unsafe { + // 加载页表 + new_address_space.read().user_mapper.utable.make_current(); switch_proc(prev, next); } compiler_fence(core::sync::atomic::Ordering::SeqCst); diff --git a/kernel/src/arch/x86_64/fpu.rs b/kernel/src/arch/x86_64/fpu.rs index 32c31127..48e1eabe 100644 --- a/kernel/src/arch/x86_64/fpu.rs +++ b/kernel/src/arch/x86_64/fpu.rs @@ -79,7 +79,7 @@ impl FpState { /// @brief 从用户态进入内核时,保存浮点寄存器,并关闭浮点功能 pub fn fp_state_save(pcb: &mut process_control_block) { // 该过程中不允许中断 - let rflags = local_irq_save(); + let rflags: usize = local_irq_save(); let fp: &mut FpState = if pcb.fp_state == null_mut() { let f = Box::leak(Box::new(FpState::default())); diff --git a/kernel/src/arch/x86_64/interrupt/mod.rs b/kernel/src/arch/x86_64/interrupt/mod.rs index 6a12c1d0..c8292658 100644 --- a/kernel/src/arch/x86_64/interrupt/mod.rs +++ b/kernel/src/arch/x86_64/interrupt/mod.rs @@ -41,14 +41,14 @@ impl InterruptArch for X86_64InterruptArch { fn is_irq_enabled() -> bool { let rflags: u64; unsafe { - asm!("pushfq; pop {}", out(reg) rflags); + asm!("pushfq; pop {}", out(reg) rflags, options(nomem, preserves_flags)); } return rflags & (1 << 9) != 0; } unsafe fn save_and_disable_irq() -> IrqFlagsGuard { compiler_fence(Ordering::SeqCst); - let rflags = local_irq_save() as u64; + let rflags = local_irq_save(); let flags = IrqFlags::new(rflags); let guard = IrqFlagsGuard::new(flags); compiler_fence(Ordering::SeqCst); @@ -57,7 +57,7 @@ impl InterruptArch for X86_64InterruptArch { unsafe fn restore_irq(flags: IrqFlags) { compiler_fence(Ordering::SeqCst); - local_irq_restore(flags.flags() as usize); + local_irq_restore(flags.flags()); compiler_fence(Ordering::SeqCst); } } diff --git a/kernel/src/arch/x86_64/libs/mod.rs b/kernel/src/arch/x86_64/libs/mod.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/kernel/src/arch/x86_64/libs/mod.rs @@ -0,0 +1 @@ + diff --git a/kernel/src/arch/x86_64/mm/mod.rs b/kernel/src/arch/x86_64/mm/mod.rs index bda00b3f..be8e25ec 100644 --- a/kernel/src/arch/x86_64/mm/mod.rs +++ b/kernel/src/arch/x86_64/mm/mod.rs @@ -1,28 +1,627 @@ pub mod barrier; -use crate::include::bindings::bindings::process_control_block; + +use alloc::vec::Vec; +use hashbrown::HashSet; +use x86::time::rdtsc; +use x86_64::registers::model_specific::EferFlags; + +use crate::driver::uart::uart::c_uart_send_str; +use crate::include::bindings::bindings::{ + disable_textui, enable_textui, multiboot2_get_memory, multiboot2_iter, multiboot_mmap_entry_t, + video_reinitialize, +}; +use crate::libs::align::page_align_up; +use crate::libs::printk::PrintkWriter; +use crate::libs::spinlock::SpinLock; + +use crate::mm::allocator::page_frame::{FrameAllocator, PageFrameCount}; +use crate::mm::mmio_buddy::mmio_init; +use crate::{ + arch::MMArch, + mm::allocator::{buddy::BuddyAllocator, bump::BumpAllocator}, +}; + +use crate::mm::kernel_mapper::KernelMapper; +use crate::mm::page::{PageEntry, PageFlags}; +use crate::mm::{MemoryManagementArch, PageTableKind, PhysAddr, PhysMemoryArea, VirtAddr}; +use crate::syscall::SystemError; +use crate::{kdebug, kinfo}; use core::arch::asm; -use core::ptr::read_volatile; +use core::ffi::c_void; +use core::fmt::{Debug, Write}; +use core::mem::{self}; -use self::barrier::mfence; +use core::sync::atomic::{compiler_fence, AtomicBool, Ordering}; -/// @brief 切换进程的页表 -/// -/// @param 下一个进程的pcb。将会把它的页表切换进来。 -/// -/// @return 下一个进程的pcb(把它return的目的主要是为了归还所有权) -#[inline(always)] -#[allow(dead_code)] -pub fn switch_mm( - next_pcb: &'static mut process_control_block, -) -> &'static mut process_control_block { - mfence(); - // kdebug!("to get pml4t"); - let pml4t = unsafe { read_volatile(&next_pcb.mm.as_ref().unwrap().pgd) }; +pub type PageMapper = + crate::mm::page::PageMapper; + +/// @brief 用于存储物理内存区域的数组 +static mut PHYS_MEMORY_AREAS: [PhysMemoryArea; 512] = [PhysMemoryArea { + base: PhysAddr::new(0), + size: 0, +}; 512]; + +/// 初始的CR3寄存器的值,用于内存管理初始化时,创建的第一个内核页表的位置 +static mut INITIAL_CR3_VALUE: PhysAddr = PhysAddr::new(0); + +/// 内核的第一个页表在pml4中的索引 +/// 顶级页表的[256, 512)项是内核的页表 +static KERNEL_PML4E_NO: usize = (X86_64MMArch::PHYS_OFFSET & ((1 << 48) - 1)) >> 39; + +static INNER_ALLOCATOR: SpinLock>> = SpinLock::new(None); + +#[derive(Clone, Copy)] +pub struct X86_64MMBootstrapInfo { + kernel_code_start: usize, + kernel_code_end: usize, + kernel_data_end: usize, + kernel_rodata_end: usize, + start_brk: usize, +} + +impl Debug for X86_64MMBootstrapInfo { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + write!( + f, + "kernel_code_start: {:x}, kernel_code_end: {:x}, kernel_data_end: {:x}, kernel_rodata_end: {:x}, start_brk: {:x}", + self.kernel_code_start, self.kernel_code_end, self.kernel_data_end, self.kernel_rodata_end, self.start_brk) + } +} + +pub static mut BOOTSTRAP_MM_INFO: Option = None; + +/// @brief X86_64的内存管理架构结构体 +#[derive(Debug, Clone, Copy, Hash)] +pub struct X86_64MMArch; + +/// XD标志位是否被保留 +static XD_RESERVED: AtomicBool = AtomicBool::new(false); + +impl MemoryManagementArch for X86_64MMArch { + /// 4K页 + const PAGE_SHIFT: usize = 12; + + /// 每个页表项占8字节,总共有512个页表项 + const PAGE_ENTRY_SHIFT: usize = 9; + + /// 四级页表(PML4T、PDPT、PDT、PT) + const PAGE_LEVELS: usize = 4; + + /// 页表项的有效位的index。在x86_64中,页表项的第[0, 47]位表示地址和flag, + /// 第[48, 51]位表示保留。因此,有效位的index为52。 + /// 请注意,第63位是XD位,表示是否允许执行。 + const ENTRY_ADDRESS_SHIFT: usize = 52; + + const ENTRY_FLAG_DEFAULT_PAGE: usize = Self::ENTRY_FLAG_PRESENT; + + const ENTRY_FLAG_DEFAULT_TABLE: usize = Self::ENTRY_FLAG_PRESENT; + + const ENTRY_FLAG_PRESENT: usize = 1 << 0; + + const ENTRY_FLAG_READONLY: usize = 0; + + const ENTRY_FLAG_READWRITE: usize = 1 << 1; + + const ENTRY_FLAG_USER: usize = 1 << 2; + + const ENTRY_FLAG_WRITE_THROUGH: usize = 1 << 3; + + const ENTRY_FLAG_CACHE_DISABLE: usize = 1 << 4; + + const ENTRY_FLAG_NO_EXEC: usize = 1 << 63; + /// x86_64不存在EXEC标志位,只有NO_EXEC(XD)标志位 + const ENTRY_FLAG_EXEC: usize = 0; + + /// 物理地址与虚拟地址的偏移量 + /// 0xffff_8000_0000_0000 + const PHYS_OFFSET: usize = Self::PAGE_NEGATIVE_MASK + (Self::PAGE_ADDRESS_SIZE >> 1); + + const USER_END_VADDR: VirtAddr = VirtAddr::new(0x0000_7eff_ffff_ffff); + const USER_BRK_START: VirtAddr = VirtAddr::new(0x700000000000); + const USER_STACK_START: VirtAddr = VirtAddr::new(0x6ffff0a00000); + + /// @brief 获取物理内存区域 + unsafe fn init() -> &'static [crate::mm::PhysMemoryArea] { + extern "C" { + fn _text(); + fn _etext(); + fn _edata(); + fn _erodata(); + fn _end(); + } + + Self::init_xd_rsvd(); + + let bootstrap_info = X86_64MMBootstrapInfo { + kernel_code_start: _text as usize, + kernel_code_end: _etext as usize, + kernel_data_end: _edata as usize, + kernel_rodata_end: _erodata as usize, + start_brk: _end as usize, + }; + unsafe { + BOOTSTRAP_MM_INFO = Some(bootstrap_info); + } + + // 初始化物理内存区域(从multiboot2中获取) + let areas_count = + Self::init_memory_area_from_multiboot2().expect("init memory area failed"); + c_uart_send_str(0x3f8, "x86 64 init end\n\0".as_ptr()); + + return &PHYS_MEMORY_AREAS[0..areas_count]; + } + + /// @brief 刷新TLB中,关于指定虚拟地址的条目 + unsafe fn invalidate_page(address: VirtAddr) { + compiler_fence(Ordering::SeqCst); + asm!("invlpg [{0}]", in(reg) address.data(), options(nostack, preserves_flags)); + compiler_fence(Ordering::SeqCst); + } + + /// @brief 刷新TLB中,所有的条目 + unsafe fn invalidate_all() { + compiler_fence(Ordering::SeqCst); + // 通过设置cr3寄存器,来刷新整个TLB + Self::set_table(PageTableKind::User, Self::table(PageTableKind::User)); + compiler_fence(Ordering::SeqCst); + } + + /// @brief 获取顶级页表的物理地址 + unsafe fn table(_table_kind: PageTableKind) -> PhysAddr { + let paddr: usize; + compiler_fence(Ordering::SeqCst); + asm!("mov {}, cr3", out(reg) paddr, options(nomem, nostack, preserves_flags)); + compiler_fence(Ordering::SeqCst); + return PhysAddr::new(paddr); + } + + /// @brief 设置顶级页表的物理地址到处理器中 + unsafe fn set_table(_table_kind: PageTableKind, table: PhysAddr) { + compiler_fence(Ordering::SeqCst); + asm!("mov cr3, {}", in(reg) table.data(), options(nostack, preserves_flags)); + compiler_fence(Ordering::SeqCst); + } + + /// @brief 判断虚拟地址是否合法 + fn virt_is_valid(virt: VirtAddr) -> bool { + return virt.is_canonical(); + } + + /// 获取内存管理初始化时,创建的第一个内核页表的地址 + fn initial_page_table() -> PhysAddr { + unsafe { + return INITIAL_CR3_VALUE; + } + } + + /// @brief 创建新的顶层页表 + /// + /// 该函数会创建页表并复制内核的映射到新的页表中 + /// + /// @return 新的页表 + fn setup_new_usermapper() -> Result { + let new_umapper: crate::mm::page::PageMapper = unsafe { + PageMapper::create(PageTableKind::User, LockedFrameAllocator) + .ok_or(SystemError::ENOMEM)? + }; + + let current_ktable: KernelMapper = KernelMapper::lock(); + let copy_mapping = |pml4_entry_no| unsafe { + let entry: PageEntry = current_ktable + .table() + .entry(pml4_entry_no) + .unwrap_or_else(|| panic!("entry {} not found", pml4_entry_no)); + new_umapper.table().set_entry(pml4_entry_no, entry) + }; + + // 复制内核的映射 + for pml4_entry_no in KERNEL_PML4E_NO..512 { + copy_mapping(pml4_entry_no); + } + + return Ok(crate::mm::ucontext::UserMapper::new(new_umapper)); + } +} + +impl X86_64MMArch { + unsafe fn init_memory_area_from_multiboot2() -> Result { + // 这个数组用来存放内存区域的信息(从C获取) + let mut mb2_mem_info: [multiboot_mmap_entry_t; 512] = mem::zeroed(); + c_uart_send_str(0x3f8, "init_memory_area_from_multiboot2 begin\n\0".as_ptr()); + + let mut mb2_count: u32 = 0; + multiboot2_iter( + Some(multiboot2_get_memory), + &mut mb2_mem_info as *mut [multiboot_mmap_entry_t; 512] as usize as *mut c_void, + &mut mb2_count, + ); + c_uart_send_str(0x3f8, "init_memory_area_from_multiboot2 2\n\0".as_ptr()); + + let mb2_count = mb2_count as usize; + let mut areas_count = 0usize; + let mut total_mem_size = 0usize; + for i in 0..mb2_count { + // Only use the memory area if its type is 1 (RAM) + if mb2_mem_info[i].type_ == 1 { + // Skip the memory area if its len is 0 + if mb2_mem_info[i].len == 0 { + continue; + } + total_mem_size += mb2_mem_info[i].len as usize; + PHYS_MEMORY_AREAS[areas_count].base = PhysAddr::new(mb2_mem_info[i].addr as usize); + PHYS_MEMORY_AREAS[areas_count].size = mb2_mem_info[i].len as usize; + areas_count += 1; + } + } + c_uart_send_str(0x3f8, "init_memory_area_from_multiboot2 end\n\0".as_ptr()); + kinfo!("Total memory size: {} MB, total areas from multiboot2: {mb2_count}, valid areas: {areas_count}", total_mem_size / 1024 / 1024); + + return Ok(areas_count); + } + + fn init_xd_rsvd() { + // 读取ia32-EFER寄存器的值 + let efer: EferFlags = x86_64::registers::model_specific::Efer::read(); + if !efer.contains(EferFlags::NO_EXECUTE_ENABLE) { + // NO_EXECUTE_ENABLE是false,那么就设置xd_reserved为true + kdebug!("NO_EXECUTE_ENABLE is false, set XD_RESERVED to true"); + XD_RESERVED.store(true, Ordering::Relaxed); + } + compiler_fence(Ordering::SeqCst); + } + + /// 判断XD标志位是否被保留 + pub fn is_xd_reserved() -> bool { + return XD_RESERVED.load(Ordering::Relaxed); + } +} + +impl VirtAddr { + /// @brief 判断虚拟地址是否合法 + #[inline(always)] + pub fn is_canonical(self) -> bool { + let x = self.data() & X86_64MMArch::PHYS_OFFSET; + // 如果x为0,说明虚拟地址的高位为0,是合法的用户地址 + // 如果x为PHYS_OFFSET,说明虚拟地址的高位全为1,是合法的内核地址 + return x == 0 || x == X86_64MMArch::PHYS_OFFSET; + } +} + +/// @brief 初始化内存管理模块 +pub fn mm_init() { + c_uart_send_str(0x3f8, "mm_init\n\0".as_ptr()); + PrintkWriter + .write_fmt(format_args!("mm_init() called\n")) + .unwrap(); + // printk_color!(GREEN, BLACK, "mm_init() called\n"); + static _CALL_ONCE: AtomicBool = AtomicBool::new(false); + if _CALL_ONCE + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { + c_uart_send_str(0x3f8, "mm_init err\n\0".as_ptr()); + panic!("mm_init() can only be called once"); + } + + unsafe { X86_64MMArch::init() }; + kdebug!("bootstrap info: {:?}", unsafe { BOOTSTRAP_MM_INFO }); + kdebug!("phys[0]=virt[0x{:x}]", unsafe { + MMArch::phys_2_virt(PhysAddr::new(0)).unwrap().data() + }); + + // 初始化内存管理器 + unsafe { allocator_init() }; + // enable mmio + mmio_init(); + // 启用printk的alloc选项 + PrintkWriter.enable_alloc(); +} + +unsafe fn allocator_init() { + let virt_offset = BOOTSTRAP_MM_INFO.unwrap().start_brk; + let phy_offset = + unsafe { MMArch::virt_2_phys(VirtAddr::new(page_align_up(virt_offset))) }.unwrap(); + + kdebug!("PhysArea[0..10] = {:?}", &PHYS_MEMORY_AREAS[0..10]); + let mut bump_allocator = + BumpAllocator::::new(&PHYS_MEMORY_AREAS, phy_offset.data()); + kdebug!( + "BumpAllocator created, offset={:?}", + bump_allocator.offset() + ); + + // 暂存初始在head.S中指定的页表的地址,后面再考虑是否需要把它加到buddy的可用空间里面! + // 现在不加的原因是,我担心会有安全漏洞问题:这些初始的页表,位于内核的数据段。如果归还到buddy, + // 可能会产生一定的安全风险(有的代码可能根据虚拟地址来进行安全校验) + let _old_page_table = MMArch::table(PageTableKind::Kernel); + + let new_page_table: PhysAddr; + // 使用bump分配器,把所有的内存页都映射到页表 + { + // 用bump allocator创建新的页表 + let mut mapper: crate::mm::page::PageMapper> = + crate::mm::page::PageMapper::::create( + PageTableKind::Kernel, + &mut bump_allocator, + ) + .expect("Failed to create page mapper"); + new_page_table = mapper.table().phys(); + kdebug!("PageMapper created"); + + // 取消最开始时候,在head.S中指定的映射(暂时不刷新TLB) + { + let table = mapper.table(); + let empty_entry = PageEntry::::new(0); + for i in 0..MMArch::PAGE_ENTRY_NUM { + table + .set_entry(i, empty_entry) + .expect("Failed to empty page table entry"); + } + } + kdebug!("Successfully emptied page table"); + + for area in PHYS_MEMORY_AREAS.iter() { + // kdebug!("area: base={:?}, size={:#x}, end={:?}", area.base, area.size, area.base + area.size); + for i in 0..((area.size + MMArch::PAGE_SIZE - 1) / MMArch::PAGE_SIZE) { + let paddr = area.base.add(i * MMArch::PAGE_SIZE); + let vaddr = unsafe { MMArch::phys_2_virt(paddr) }.unwrap(); + let flags = kernel_page_flags::(vaddr); + + let flusher = mapper + .map_phys(vaddr, paddr, flags) + .expect("Failed to map frame"); + // 暂时不刷新TLB + flusher.ignore(); + } + } + + // 添加低地址的映射(在smp完成初始化之前,需要使用低地址的映射.初始化之后需要取消这一段映射) + LowAddressRemapping::remap_at_low_address(&mut mapper); + } unsafe { - asm!("mov cr3, {}", in(reg) pml4t); + INITIAL_CR3_VALUE = new_page_table; } - mfence(); - return next_pcb; + kdebug!( + "After mapping all physical memory, DragonOS used: {} KB", + bump_allocator.offset() / 1024 + ); + + // 初始化buddy_allocator + let buddy_allocator = unsafe { BuddyAllocator::::new(bump_allocator).unwrap() }; + + // 设置全局的页帧分配器 + unsafe { set_inner_allocator(buddy_allocator) }; + kinfo!("Successfully initialized buddy allocator"); + // 关闭显示输出 + unsafe { + disable_textui(); + } + // make the new page table current + { + let mut binding = INNER_ALLOCATOR.lock(); + let mut allocator_guard = binding.as_mut().unwrap(); + kdebug!("To enable new page table."); + compiler_fence(Ordering::SeqCst); + let mapper = crate::mm::page::PageMapper::::new( + PageTableKind::Kernel, + new_page_table, + &mut allocator_guard, + ); + compiler_fence(Ordering::SeqCst); + mapper.make_current(); + compiler_fence(Ordering::SeqCst); + kdebug!("New page table enabled"); + } + kdebug!("Successfully enabled new page table"); + // 重置显示输出目标 + unsafe { + video_reinitialize(false); + } + + // 打开显示输出 + unsafe { + enable_textui(); + } + kdebug!("Text UI enabled"); +} + +#[no_mangle] +pub extern "C" fn rs_test_buddy() { + test_buddy(); +} +pub fn test_buddy() { + // 申请内存然后写入数据然后free掉 + // 总共申请200MB内存 + const TOTAL_SIZE: usize = 200 * 1024 * 1024; + + for i in 0..10 { + kdebug!("Test buddy, round: {i}"); + // 存放申请的内存块 + let mut v: Vec<(PhysAddr, PageFrameCount)> = Vec::with_capacity(60 * 1024); + // 存放已经申请的内存块的地址(用于检查重复) + let mut addr_set: HashSet = HashSet::new(); + + let mut allocated = 0usize; + + let mut free_count = 0usize; + + while allocated < TOTAL_SIZE { + let mut random_size = 0u64; + unsafe { x86::random::rdrand64(&mut random_size) }; + // 一次最多申请4M + random_size = random_size % (1024 * 4096); + if random_size == 0 { + continue; + } + let random_size = + core::cmp::min(page_align_up(random_size as usize), TOTAL_SIZE - allocated); + let random_size = PageFrameCount::from_bytes(random_size.next_power_of_two()).unwrap(); + // 获取帧 + let (paddr, allocated_frame_count) = + unsafe { LockedFrameAllocator.allocate(random_size).unwrap() }; + assert!(allocated_frame_count.data().is_power_of_two()); + assert!(paddr.data() % MMArch::PAGE_SIZE == 0); + unsafe { + assert!(MMArch::phys_2_virt(paddr) + .as_ref() + .unwrap() + .check_aligned(allocated_frame_count.data() * MMArch::PAGE_SIZE)); + } + allocated += allocated_frame_count.data() * MMArch::PAGE_SIZE; + v.push((paddr, allocated_frame_count)); + assert!(addr_set.insert(paddr), "duplicate address: {:?}", paddr); + + // 写入数据 + let vaddr = unsafe { MMArch::phys_2_virt(paddr).unwrap() }; + let slice = unsafe { + core::slice::from_raw_parts_mut( + vaddr.data() as *mut u8, + allocated_frame_count.data() * MMArch::PAGE_SIZE, + ) + }; + for i in 0..slice.len() { + slice[i] = ((i + unsafe { rdtsc() } as usize) % 256) as u8; + } + + // 随机释放一个内存块 + if v.len() > 0 { + let mut random_index = 0u64; + unsafe { x86::random::rdrand64(&mut random_index) }; + // 70%概率释放 + if random_index % 10 > 7 { + continue; + } + random_index = random_index % v.len() as u64; + let random_index = random_index as usize; + let (paddr, allocated_frame_count) = v.remove(random_index); + assert!(addr_set.remove(&paddr)); + unsafe { LockedFrameAllocator.free(paddr, allocated_frame_count) }; + free_count += allocated_frame_count.data() * MMArch::PAGE_SIZE; + } + } + + kdebug!( + "Allocated {} MB memory, release: {} MB, no release: {} bytes", + allocated / 1024 / 1024, + free_count / 1024 / 1024, + (allocated - free_count) + ); + + kdebug!("Now, to release buddy memory"); + // 释放所有的内存 + for (paddr, allocated_frame_count) in v { + unsafe { LockedFrameAllocator.free(paddr, allocated_frame_count) }; + assert!(addr_set.remove(&paddr)); + free_count += allocated_frame_count.data() * MMArch::PAGE_SIZE; + } + + kdebug!("release done!, allocated: {allocated}, free_count: {free_count}"); + } +} +/// 全局的页帧分配器 +#[derive(Debug, Clone, Copy, Hash)] +pub struct LockedFrameAllocator; + +impl FrameAllocator for LockedFrameAllocator { + unsafe fn allocate( + &mut self, + count: crate::mm::allocator::page_frame::PageFrameCount, + ) -> Option<(PhysAddr, PageFrameCount)> { + if let Some(ref mut allocator) = *INNER_ALLOCATOR.lock_irqsave() { + return allocator.allocate(count); + } else { + return None; + } + } + + unsafe fn free( + &mut self, + address: crate::mm::PhysAddr, + count: crate::mm::allocator::page_frame::PageFrameCount, + ) { + assert!(count.data().is_power_of_two()); + if let Some(ref mut allocator) = *INNER_ALLOCATOR.lock_irqsave() { + return allocator.free(address, count); + } + } + + unsafe fn usage(&self) -> crate::mm::allocator::page_frame::PageFrameUsage { + todo!() + } +} + +/// 获取内核地址默认的页面标志 +pub unsafe fn kernel_page_flags(virt: VirtAddr) -> PageFlags { + let info: X86_64MMBootstrapInfo = BOOTSTRAP_MM_INFO.clone().unwrap(); + + if virt.data() >= info.kernel_code_start && virt.data() < info.kernel_code_end { + // Remap kernel code execute + return PageFlags::new().set_execute(true).set_write(true); + } else if virt.data() >= info.kernel_data_end && virt.data() < info.kernel_rodata_end { + // Remap kernel rodata read only + return PageFlags::new().set_execute(true); + } else { + return PageFlags::new().set_write(true).set_execute(true); + } +} + +unsafe fn set_inner_allocator(allocator: BuddyAllocator) { + static FLAG: AtomicBool = AtomicBool::new(false); + if FLAG + .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst) + .is_err() + { + panic!("Cannot set inner allocator twice!"); + } + *INNER_ALLOCATOR.lock() = Some(allocator); +} + +/// 低地址重映射的管理器 +/// +/// 低地址重映射的管理器,在smp初始化完成之前,需要使用低地址的映射,因此需要在smp初始化完成之后,取消这一段映射 +pub struct LowAddressRemapping; + +impl LowAddressRemapping { + // 映射32M + const REMAP_SIZE: usize = 32 * 1024 * 1024; + + pub unsafe fn remap_at_low_address( + mapper: &mut crate::mm::page::PageMapper>, + ) { + for i in 0..(Self::REMAP_SIZE / MMArch::PAGE_SIZE) { + let paddr = PhysAddr::new(i * MMArch::PAGE_SIZE); + let vaddr = VirtAddr::new(i * MMArch::PAGE_SIZE); + let flags = kernel_page_flags::(vaddr); + + let flusher = mapper + .map_phys(vaddr, paddr, flags) + .expect("Failed to map frame"); + // 暂时不刷新TLB + flusher.ignore(); + } + } + + /// 取消低地址的映射 + pub unsafe fn unmap_at_low_address(flush: bool) { + let mut mapper = KernelMapper::lock(); + assert!(mapper.as_mut().is_some()); + for i in 0..(Self::REMAP_SIZE / MMArch::PAGE_SIZE) { + let vaddr = VirtAddr::new(i * MMArch::PAGE_SIZE); + let flusher = mapper + .as_mut() + .unwrap() + .unmap(vaddr, true) + .expect("Failed to unmap frame"); + if flush == false { + flusher.ignore(); + } + } + } +} +#[no_mangle] +pub extern "C" fn rs_mm_init() { + mm_init(); } diff --git a/kernel/src/arch/x86_64/mod.rs b/kernel/src/arch/x86_64/mod.rs index 5b325ac9..2bb4724b 100644 --- a/kernel/src/arch/x86_64/mod.rs +++ b/kernel/src/arch/x86_64/mod.rs @@ -4,6 +4,7 @@ pub mod context; pub mod cpu; pub mod fpu; pub mod interrupt; +pub mod libs; pub mod mm; pub mod msi; pub mod pci; @@ -11,4 +12,9 @@ pub mod rand; pub mod sched; pub mod syscall; +pub use self::pci::pci::X86_64PciArch as PciArch; + +/// 导出内存管理的Arch结构体 +pub use self::mm::X86_64MMArch as MMArch; + pub use interrupt::X86_64InterruptArch as CurrentIrqArch; diff --git a/kernel/src/arch/x86_64/msi.rs b/kernel/src/arch/x86_64/msi.rs index 394206f1..fd72007e 100644 --- a/kernel/src/arch/x86_64/msi.rs +++ b/kernel/src/arch/x86_64/msi.rs @@ -12,7 +12,7 @@ pub fn ia64_pci_get_arch_msi_message_address(processor: u16) -> u32 { /// @return MSI Message Address pub fn ia64_pci_get_arch_msi_message_data( vector: u16, - processor: u16, + _processor: u16, trigger: TriggerMode, ) -> u32 { match trigger { diff --git a/kernel/src/arch/x86_64/syscall.rs b/kernel/src/arch/x86_64/syscall.rs index 8e3d48d9..24ce3754 100644 --- a/kernel/src/arch/x86_64/syscall.rs +++ b/kernel/src/arch/x86_64/syscall.rs @@ -1,24 +1,27 @@ -use core::ffi::c_void; +use core::{ffi::c_void, panic}; + +use alloc::{string::String, vec::Vec}; use crate::{ + arch::{asm::current::current_pcb, CurrentIrqArch}, + exception::InterruptArch, + filesystem::vfs::MAX_PATHLEN, include::bindings::bindings::{ - pt_regs, set_system_trap_gate, verify_area, CLONE_FS, CLONE_SIGNAL, CLONE_VM, PAGE_4K_SIZE, + pt_regs, set_system_trap_gate, CLONE_FS, CLONE_SIGNAL, CLONE_VM, USER_CS, USER_DS, }, ipc::signal::sys_rt_sigreturn, - kinfo, - syscall::{Syscall, SystemError, SYS_EXECVE, SYS_FORK, SYS_RT_SIGRETURN, SYS_VFORK}, + mm::{ucontext::AddressSpace, verify_area, VirtAddr}, + process::exec::{load_binary_file, ExecParam, ExecParamFlags}, + syscall::{ + user_access::{check_and_clone_cstr, check_and_clone_cstr_array}, + Syscall, SystemError, SYS_EXECVE, SYS_FORK, SYS_RT_SIGRETURN, SYS_VFORK, + }, }; use super::{asm::ptrace::user_mode, mm::barrier::mfence}; extern "C" { fn do_fork(regs: *mut pt_regs, clone_flags: u64, stack_start: u64, stack_size: u64) -> u64; - fn c_sys_execve( - path: *const u8, - argv: *const *const u8, - envp: *const *const u8, - regs: &mut pt_regs, - ) -> u64; fn syscall_int(); } @@ -71,23 +74,34 @@ pub extern "C" fn syscall_handler(regs: &mut pt_regs) -> () { // 权限校验 if from_user - && (unsafe { !verify_area(path_ptr as u64, PAGE_4K_SIZE as u64) } - || unsafe { !verify_area(argv_ptr as u64, PAGE_4K_SIZE as u64) }) - || unsafe { !verify_area(env_ptr as u64, PAGE_4K_SIZE as u64) } + && (verify_area(VirtAddr::new(path_ptr), MAX_PATHLEN).is_err() + || verify_area(VirtAddr::new(argv_ptr), MAX_PATHLEN).is_err() + || verify_area(VirtAddr::new(env_ptr), MAX_PATHLEN).is_err()) { syscall_return!(SystemError::EFAULT.to_posix_errno() as u64, regs); } else { - syscall_return!( - unsafe { - c_sys_execve( + unsafe { + // kdebug!("syscall: execve\n"); + syscall_return!( + rs_do_execve( path_ptr as *const u8, argv_ptr as *const *const u8, env_ptr as *const *const u8, - regs, - ) - }, - regs - ); + regs + ), + regs + ); + // let path = String::from("/bin/about.elf"); + // let argv = vec![String::from("/bin/about.elf")]; + // let envp = vec![String::from("PATH=/bin")]; + // let r = tmp_rs_execve(path, argv, envp, regs); + // kdebug!("syscall: execve r: {:?}\n", r); + + // syscall_return!( + // r.map(|_| 0).unwrap_or_else(|e| e.to_posix_errno() as usize), + // regs + // ) + } } } @@ -104,7 +118,147 @@ pub extern "C" fn syscall_handler(regs: &mut pt_regs) -> () { /// 系统调用初始化 pub fn arch_syscall_init() -> Result<(), SystemError> { - kinfo!("arch_syscall_init\n"); + // kinfo!("arch_syscall_init\n"); unsafe { set_system_trap_gate(0x80, 0, syscall_int as *mut c_void) }; // 系统调用门 return Ok(()); } + +#[no_mangle] +pub unsafe extern "C" fn rs_do_execve( + path: *const u8, + argv: *const *const u8, + envp: *const *const u8, + regs: &mut pt_regs, +) -> usize { + if path.is_null() { + return SystemError::EINVAL.to_posix_errno() as usize; + } + + let x = || { + let path: String = check_and_clone_cstr(path, Some(MAX_PATHLEN))?; + let argv: Vec = check_and_clone_cstr_array(argv)?; + let envp: Vec = check_and_clone_cstr_array(envp)?; + Ok((path, argv, envp)) + }; + let r: Result<(String, Vec, Vec), SystemError> = x(); + if let Err(e) = r { + panic!("Failed to execve: {:?}", e); + } + let (path, argv, envp) = r.unwrap(); + + return tmp_rs_execve(path, argv, envp, regs) + .map(|_| 0) + .unwrap_or_else(|e| { + panic!( + "Failed to execve, pid: {} error: {:?}", + current_pcb().pid, + e + ) + }); +} + +/// 执行第一个用户进程的函数(只应该被调用一次) +/// +/// 当进程管理重构完成后,这个函数应该被删除。调整为别的函数。 +#[no_mangle] +pub extern "C" fn rs_exec_init_process(regs: &mut pt_regs) -> usize { + let path = String::from("/bin/shell.elf"); + let argv = vec![String::from("/bin/shell.elf")]; + let envp = vec![String::from("PATH=/bin")]; + let r = tmp_rs_execve(path, argv, envp, regs); + // kdebug!("rs_exec_init_process: r: {:?}\n", r); + return r.map(|_| 0).unwrap_or_else(|e| e.to_posix_errno() as usize); +} + +/// 临时的execve系统调用实现,以后要把它改为普通的系统调用。 +/// +/// 现在放在这里的原因是,还没有重构中断管理模块,未实现TrapFrame这个抽象, +/// 导致我们必须手动设置中断返回时,各个寄存器的值,这个过程很繁琐,所以暂时放在这里。 +fn tmp_rs_execve( + path: String, + argv: Vec, + envp: Vec, + regs: &mut pt_regs, +) -> Result<(), SystemError> { + // kdebug!( + // "tmp_rs_execve: path: {:?}, argv: {:?}, envp: {:?}\n", + // path, + // argv, + // envp + // ); + // 关中断,防止在设置地址空间的时候,发生中断,然后进调度器,出现错误。 + let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() }; + // 暂存原本的用户地址空间的引用(因为如果在切换页表之前释放了它,可能会造成内存use after free) + let old_address_space = current_pcb().address_space(); + // 在pcb中原来的用户地址空间 + unsafe { + current_pcb().drop_address_space(); + } + // 创建新的地址空间并设置为当前地址空间 + let address_space = AddressSpace::new(true).expect("Failed to create new address space"); + unsafe { + current_pcb().set_address_space(address_space.clone()); + } + assert!( + AddressSpace::is_current(&address_space), + "Failed to set address space" + ); + // kdebug!("Switch to new address space"); + + // 切换到新的用户地址空间 + unsafe { address_space.read().user_mapper.utable.make_current() }; + + drop(old_address_space); + drop(irq_guard); + // kdebug!("to load binary file"); + let mut param = ExecParam::new(path.as_str(), address_space.clone(), ExecParamFlags::EXEC); + // 加载可执行文件 + let load_result = load_binary_file(&mut param) + .unwrap_or_else(|e| panic!("Failed to load binary file: {:?}, path: {:?}", e, path)); + // kdebug!("load binary file done"); + + param.init_info_mut().args = argv; + param.init_info_mut().envs = envp; + + // 把proc_init_info写到用户栈上 + + let (user_sp, argv_ptr) = unsafe { + param + .init_info() + .push_at( + address_space + .write() + .user_stack_mut() + .expect("No user stack found"), + ) + .expect("Failed to push proc_init_info to user stack") + }; + + // kdebug!("write proc_init_info to user stack done"); + + // (兼容旧版libc)把argv的指针写到寄存器内 + // TODO: 改写旧版libc,不再需要这个兼容 + regs.rdi = param.init_info().args.len() as u64; + regs.rsi = argv_ptr.data() as u64; + + // 设置系统调用返回时的寄存器状态 + // TODO: 中断管理重构后,这里的寄存器状态设置要删掉!!!改为对trap frame的设置。要增加架构抽象。 + regs.rsp = user_sp.data() as u64; + regs.rbp = user_sp.data() as u64; + regs.rip = load_result.entry_point().data() as u64; + + regs.cs = USER_CS as u64 | 3; + regs.ds = USER_DS as u64 | 3; + regs.ss = USER_DS as u64 | 3; + regs.es = 0; + regs.rflags = 0x200; + regs.rax = 1; + + // kdebug!("regs: {:?}\n", regs); + + // kdebug!( + // "tmp_rs_execve: done, load_result.entry_point()={:?}", + // load_result.entry_point() + // ); + return Ok(()); +} diff --git a/kernel/src/common/atomic.h b/kernel/src/common/atomic.h index e295ce7c..d9fc2ef6 100644 --- a/kernel/src/common/atomic.h +++ b/kernel/src/common/atomic.h @@ -9,6 +9,7 @@ * */ #pragma once +#include #define atomic_read(atomic) ((atomic)->value) // 读取原子变量 #define atomic_set(atomic,val) (((atomic)->value) = (val)) // 设置原子变量的初始值 @@ -97,3 +98,10 @@ inline void atomic_clear_mask(atomic_t *ato, long mask) : "r"(mask) : "memory"); } + +// cmpxchgq 比较并交换 +inline long atomic_cmpxchg(atomic_t *ato, long oldval, long newval) +{ + bool success = arch_try_cmpxchg(&ato->value, &oldval, &newval); + return success ? oldval : newval; +} \ No newline at end of file diff --git a/kernel/src/driver/acpi/acpi.c b/kernel/src/driver/acpi/acpi.c index e7f69f77..45f296b1 100644 --- a/kernel/src/driver/acpi/acpi.c +++ b/kernel/src/driver/acpi/acpi.c @@ -44,7 +44,8 @@ void acpi_iter_SDT(bool (*_fun)(const struct acpi_system_description_table_heade ul *ent = &(xsdt->Entry); for (int i = 0; i < acpi_XSDT_Entry_num; ++i) { - mm_map_phys_addr(acpi_description_header_base + PAGE_2M_SIZE * i, (*(ent + i)) & PAGE_2M_MASK, PAGE_2M_SIZE, PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD, false); + // mm_map_phys_addr(acpi_description_header_base + PAGE_2M_SIZE * i, (*(ent + i)) & PAGE_2M_MASK, PAGE_2M_SIZE, PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD, false); + rs_map_phys(acpi_description_header_base + PAGE_2M_SIZE * i, (*(ent + i)) & PAGE_2M_MASK, PAGE_2M_SIZE, PAGE_KERNEL_PAGE); sdt_header = (struct acpi_system_description_table_header_t *)((ul)(acpi_description_header_base + PAGE_2M_SIZE * i)); if (_fun(sdt_header, _data) == true) @@ -173,13 +174,14 @@ void acpi_init() ul rsdt_phys_base = rsdpv2->rsdp1.RsdtAddress & PAGE_2M_MASK; acpi_RSDT_offset = rsdpv2->rsdp1.RsdtAddress - rsdt_phys_base; - //申请mmio空间 + // 申请mmio空间 uint64_t size = 0; mmio_create(PAGE_2M_SIZE, VM_IO | VM_DONTCOPY, &acpi_rsdt_virt_addr_base, &size); - //映射rsdt表 + // 映射rsdt表 paddr = (uint64_t)rsdt_phys_base; - mm_map(&initial_mm, acpi_rsdt_virt_addr_base, PAGE_2M_SIZE, paddr); + // mm_map(&initial_mm, acpi_rsdt_virt_addr_base, PAGE_2M_SIZE, paddr); + rs_map_phys(acpi_rsdt_virt_addr_base, paddr, PAGE_2M_SIZE, PAGE_KERNEL_PAGE); // rsdt表虚拟地址 rsdt = (struct acpi_RSDT_Structure_t *)(acpi_rsdt_virt_addr_base + acpi_RSDT_offset); @@ -192,7 +194,7 @@ void acpi_init() printk_color(ORANGE, BLACK, "RSDT Length=%dbytes.\n", rsdt->header.Length); printk_color(ORANGE, BLACK, "RSDT Entry num=%d\n", acpi_RSDT_Entry_num); - //申请mmio空间 + // 申请mmio空间 mmio_create(PAGE_2M_SIZE, VM_IO | VM_DONTCOPY, &acpi_description_header_base, &size); // 映射所有的Entry的物理地址 @@ -201,23 +203,27 @@ void acpi_init() acpi_RSDT_entry_phys_base = MASK_HIGH_32bit(acpi_RSDT_entry_phys_base); paddr = (uint64_t)acpi_RSDT_entry_phys_base; - mm_map(&initial_mm, acpi_description_header_base, PAGE_2M_SIZE, paddr); + // mm_map(&initial_mm, acpi_description_header_base, PAGE_2M_SIZE, paddr); + rs_map_phys(acpi_description_header_base, paddr, PAGE_2M_SIZE, PAGE_KERNEL_PAGE); } else if (rsdpv1->RsdtAddress != (uint)0x00UL) { // rsdt表物理地址 ul rsdt_phys_base = rsdpv1->RsdtAddress & PAGE_2M_MASK; acpi_RSDT_offset = rsdpv1->RsdtAddress - rsdt_phys_base; - + kdebug("rsdpv1->RsdtAddress=%#018lx", rsdpv1->RsdtAddress); - //申请mmio空间 + // 申请mmio空间 uint64_t size = 0; mmio_create(PAGE_2M_SIZE, VM_IO | VM_DONTCOPY, &acpi_rsdt_virt_addr_base, &size); + // acpi_rsdt_virt_addr_base = 0xffffb00000000000UL; + kdebug("ACPI: mmio created. acpi_rsdt_virt_addr_base = %#018lx,size= %#010lx", acpi_rsdt_virt_addr_base, size); // kdebug("acpi_rsdt_virt_addr_base = %#018lx,size= %#010lx", acpi_rsdt_virt_addr_base, size); - //映射rsdt表 + // 映射rsdt表 paddr = (uint64_t)rsdt_phys_base; - mm_map(&initial_mm, acpi_rsdt_virt_addr_base, PAGE_2M_SIZE, paddr); + // mm_map(&initial_mm, acpi_rsdt_virt_addr_base, PAGE_2M_SIZE, paddr); + rs_map_phys(acpi_rsdt_virt_addr_base, paddr, PAGE_2M_SIZE, PAGE_KERNEL_PAGE); // rsdt表虚拟地址 rsdt = (struct acpi_RSDT_Structure_t *)(acpi_rsdt_virt_addr_base + acpi_RSDT_offset); kdebug("RSDT mapped!"); @@ -231,7 +237,7 @@ void acpi_init() printk_color(ORANGE, BLACK, "RSDT Length=%dbytes.\n", rsdt->header.Length); printk_color(ORANGE, BLACK, "RSDT Entry num=%d\n", acpi_RSDT_Entry_num); - //申请mmio空间 + // 申请mmio空间 mmio_create(PAGE_2M_SIZE, VM_IO | VM_DONTCOPY, &acpi_description_header_base, &size); // 映射所有的Entry的物理地址 @@ -240,9 +246,9 @@ void acpi_init() acpi_RSDT_entry_phys_base = MASK_HIGH_32bit(acpi_RSDT_entry_phys_base); paddr = (uint64_t)acpi_RSDT_entry_phys_base; - mm_map(&initial_mm, acpi_description_header_base, PAGE_2M_SIZE, paddr); - // kinfo("entry mapped!"); - + // mm_map(&initial_mm, acpi_description_header_base, PAGE_2M_SIZE, paddr); + rs_map_phys(acpi_description_header_base, paddr, PAGE_2M_SIZE, PAGE_KERNEL_PAGE); + kinfo("entry mapped!"); } else { diff --git a/kernel/src/driver/base/char/mod.rs b/kernel/src/driver/base/char/mod.rs index 7637069d..2b81d8ea 100644 --- a/kernel/src/driver/base/char/mod.rs +++ b/kernel/src/driver/base/char/mod.rs @@ -71,7 +71,7 @@ impl CharDeviceStruct { /// name: 字符设备名 /// char: 字符设备实例 /// @return: 实例 - /// + /// #[allow(dead_code)] pub fn new(dev_t: DeviceNumber, minorct: usize, name: &'static str) -> Self { Self { @@ -84,7 +84,7 @@ impl CharDeviceStruct { /// @brief: 获取起始次设备号 /// @parameter: None /// @return: 起始设备号 - /// + /// #[allow(dead_code)] pub fn device_number(&self) -> DeviceNumber { self.dev_t @@ -93,7 +93,7 @@ impl CharDeviceStruct { /// @brief: 获取起始次设备号 /// @parameter: None /// @return: 起始设备号 - /// + /// #[allow(dead_code)] pub fn base_minor(&self) -> usize { self.dev_t.minor() @@ -122,7 +122,7 @@ impl CharDevOps { /// @brief: 动态获取主设备号 /// @parameter: None - /// @return: 如果成功,返回主设备号,否则,返回错误码 + /// @return: 如果成功,返回主设备号,否则,返回错误码 #[allow(dead_code)] fn find_dynamic_major() -> Result { let chardevs = CHARDEVS.0.lock(); @@ -171,7 +171,7 @@ impl CharDevOps { /// @parameter: baseminor: 主设备号 /// count: 次设备号数量 /// name: 字符设备名 - /// @return: 如果注册成功,返回,否则,返回false + /// @return: 如果注册成功,返回,否则,返回false #[allow(dead_code)] pub fn alloc_chardev_region( baseminor: usize, diff --git a/kernel/src/driver/base/device/mod.rs b/kernel/src/driver/base/device/mod.rs index ccce9758..2baa5197 100644 --- a/kernel/src/driver/base/device/mod.rs +++ b/kernel/src/driver/base/device/mod.rs @@ -1,3 +1,5 @@ +use alloc::{collections::BTreeMap, string::String, sync::Arc}; + use crate::{ filesystem::{ sysfs::{ @@ -9,7 +11,6 @@ use crate::{ libs::spinlock::SpinLock, syscall::SystemError, }; -use alloc::{collections::BTreeMap, string::String, sync::Arc}; use core::{any::Any, fmt::Debug}; pub mod bus; diff --git a/kernel/src/driver/disk/ahci/ahci.c b/kernel/src/driver/disk/ahci/ahci.c index 4da4d54b..b58daebf 100644 --- a/kernel/src/driver/disk/ahci/ahci.c +++ b/kernel/src/driver/disk/ahci/ahci.c @@ -26,7 +26,7 @@ void ahci_cpp_init(uint32_t *count_ahci_devices, struct pci_device_structure_hea // 映射ABAR uint32_t bar5 = gen_devs[0]->BAR5; - mm_map_phys_addr(AHCI_MAPPING_BASE, (ul)(bar5)&PAGE_2M_MASK, PAGE_2M_SIZE, PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD, false); + rs_map_phys(AHCI_MAPPING_BASE, (ul)(bar5)&PAGE_2M_MASK, PAGE_2M_SIZE, PAGE_KERNEL_PAGE); kinfo("ABAR mapped!"); } diff --git a/kernel/src/driver/interrupt/apic/apic.c b/kernel/src/driver/interrupt/apic/apic.c index f585ae47..31a043b5 100644 --- a/kernel/src/driver/interrupt/apic/apic.c +++ b/kernel/src/driver/interrupt/apic/apic.c @@ -86,8 +86,8 @@ void apic_io_apic_init() // kdebug("(ul)apic_ioapic_map.virtual_index_addr=%#018lx", (ul)apic_ioapic_map.virtual_index_addr); // 填写页表,完成地址映射 - mm_map_phys_addr((ul)apic_ioapic_map.virtual_index_addr, apic_ioapic_map.addr_phys, PAGE_2M_SIZE, - PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD, false); + rs_map_phys((ul)apic_ioapic_map.virtual_index_addr, apic_ioapic_map.addr_phys, PAGE_2M_SIZE, + PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD); // 设置IO APIC ID 为0x0f000000 *apic_ioapic_map.virtual_index_addr = 0x00; @@ -278,8 +278,8 @@ void apic_local_apic_init() uint64_t ia32_apic_base = rdmsr(0x1b); // kdebug("apic base=%#018lx", (ia32_apic_base & 0x1FFFFFFFFFF000)); // 映射Local APIC 寄存器地址 - mm_map_phys_addr(APIC_LOCAL_APIC_VIRT_BASE_ADDR, (ia32_apic_base & 0x1FFFFFFFFFFFFF), PAGE_2M_SIZE, - PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD, false); + // todo: + rs_map_phys(APIC_LOCAL_APIC_VIRT_BASE_ADDR, (ia32_apic_base & 0x1FFFFFFFFFF000), PAGE_2M_SIZE, PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD); uint a, b, c, d; cpu_cpuid(1, 0, &a, &b, &c, &d); @@ -357,6 +357,7 @@ void apic_local_apic_init() */ int apic_init() { + kinfo("Initializing APIC..."); // 初始化中断门, 中断使用rsp0防止在软中断时发生嵌套,然后处理器重新加载导致数据被抹掉 for (int i = 32; i <= 55; ++i) set_intr_gate(i, 0, interrupt_table[i - 32]); @@ -396,6 +397,7 @@ int apic_init() RCBA_vaddr = 0; kwarn("Cannot get RCBA address. RCBA_phys=%#010lx", RCBA_phys); } + kinfo("APIC initialized."); sti(); return 0; } diff --git a/kernel/src/driver/pci/msi.c b/kernel/src/driver/pci/msi.c index 6f249eeb..e59ffc8a 100644 --- a/kernel/src/driver/pci/msi.c +++ b/kernel/src/driver/pci/msi.c @@ -96,7 +96,7 @@ static __always_inline int __msix_map_table(struct pci_device_structure_header_t // pci_dev->msix_mmio_vaddr, bar, pci_dev->msix_offset, pci_dev->msix_table_size, pci_dev->msix_mmio_size); // 将msix table映射到页表 - mm_map(&initial_mm, pci_dev->msix_mmio_vaddr, pci_dev->msix_mmio_size, bar); + rs_map_phys(pci_dev->msix_mmio_vaddr, bar, pci_dev->msix_mmio_size, PAGE_KERNEL_PAGE); return 0; } diff --git a/kernel/src/driver/pci/pci.rs b/kernel/src/driver/pci/pci.rs index 5c089fe2..f4adc08f 100644 --- a/kernel/src/driver/pci/pci.rs +++ b/kernel/src/driver/pci/pci.rs @@ -3,15 +3,17 @@ use super::pci_irq::{IrqType, PciIrqError}; use crate::arch::{PciArch, TraitPciArch}; -use crate::include::bindings::bindings::{ - initial_mm, mm_map, mm_struct, PAGE_2M_SIZE, VM_DONTCOPY, VM_IO, -}; +use crate::include::bindings::bindings::{PAGE_2M_SIZE, VM_DONTCOPY, VM_IO}; use crate::libs::rwlock::{RwLock, RwLockReadGuard, RwLockWriteGuard}; -use crate::mm::mmio_buddy::MMIO_POOL; +use crate::mm::kernel_mapper::KernelMapper; +use crate::mm::mmio_buddy::mmio_pool; +use crate::mm::page::PageFlags; +use crate::mm::{PhysAddr, VirtAddr}; use crate::{kdebug, kerror, kinfo, kwarn}; use alloc::vec::Vec; use alloc::{boxed::Box, collections::LinkedList}; use bitflags::bitflags; + use core::{ convert::TryFrom, fmt::{self, Debug, Display, Formatter}, @@ -632,20 +634,27 @@ impl PciRoot { let virtsize_ptr = &mut virtsize as *mut u64; let size = bus_number_double * PAGE_2M_SIZE; unsafe { - let initial_mm_ptr = &mut initial_mm as *mut mm_struct; - if let Err(_) = - MMIO_POOL.create_mmio(size, (VM_IO | VM_DONTCOPY) as u64, vaddr_ptr, virtsize_ptr) - { + if let Err(_) = mmio_pool().create_mmio( + size as usize, + (VM_IO | VM_DONTCOPY) as u64, + vaddr_ptr, + virtsize_ptr, + ) { kerror!("Create mmio failed when initing ecam"); return Err(PciError::CreateMmioError); }; + //kdebug!("virtaddress={:#x},virtsize={:#x}",virtaddress,virtsize); - mm_map( - initial_mm_ptr, - virtaddress, - size as u64, - self.physical_address_base, - ); + let vaddr = VirtAddr::new(virtaddress as usize); + let paddr = PhysAddr::new(self.physical_address_base as usize); + // kdebug!("pci root: map: vaddr={vaddr:?}, paddr={paddr:?}, size={size}"); + let page_flags = PageFlags::mmio_flags(); + let mut kernel_mapper = KernelMapper::lock(); + // todo: 添加错误处理代码。因为内核映射器可能是只读的,所以可能会出错 + assert!(kernel_mapper + .map_phys_with_size(vaddr, paddr, size as usize, page_flags, true) + .is_ok()); + drop(kernel_mapper); } self.mmio_base = Some(virtaddress as *mut u32); Ok(0) @@ -1302,8 +1311,9 @@ impl Display for BarInfo { } } } -//todo 增加对桥的bar的支持 +// todo 增加对桥的bar的支持 pub trait PciDeviceBar {} + ///一个普通PCI设备(非桥)有6个BAR寄存器,PciStandardDeviceBar存储其全部信息 #[derive(Clone, Debug, Eq, PartialEq)] pub struct PciStandardDeviceBar { @@ -1409,10 +1419,11 @@ pub fn pci_bar_init( let vaddr_ptr = &mut virtaddress as *mut u64; let mut virtsize: u64 = 0; let virtsize_ptr = &mut virtsize as *mut u64; - let initial_mm_ptr = &mut initial_mm as *mut mm_struct; - //kdebug!("size want={:#x}", size); - if let Err(_) = MMIO_POOL.create_mmio( - size, + + let size_want = size as usize; + + if let Err(_) = mmio_pool().create_mmio( + size_want, (VM_IO | VM_DONTCOPY) as u64, vaddr_ptr, virtsize_ptr, @@ -1421,7 +1432,20 @@ pub fn pci_bar_init( return Err(PciError::CreateMmioError); }; //kdebug!("virtaddress={:#x},virtsize={:#x}",virtaddress,virtsize); - mm_map(initial_mm_ptr, virtaddress, size as u64, address); + let vaddr = VirtAddr::new(virtaddress as usize); + let paddr = PhysAddr::new(address as usize); + let page_flags = PageFlags::new() + .set_write(true) + .set_execute(true) + .set_page_cache_disable(true) + .set_page_write_through(true); + kdebug!("Pci bar init: vaddr={vaddr:?}, paddr={paddr:?}, size_want={size_want}, page_flags={page_flags:?}"); + let mut kernel_mapper = KernelMapper::lock(); + // todo: 添加错误处理代码。因为内核映射器可能是只读的,所以可能会出错 + assert!(kernel_mapper + .map_phys_with_size(vaddr, paddr, size_want, page_flags, true) + .is_ok()); + drop(kernel_mapper); } bar_info = BarInfo::Memory { address_type, diff --git a/kernel/src/driver/pci/pci_irq.rs b/kernel/src/driver/pci/pci_irq.rs index 24e95afe..670f1289 100644 --- a/kernel/src/driver/pci/pci_irq.rs +++ b/kernel/src/driver/pci/pci_irq.rs @@ -6,16 +6,14 @@ use core::ptr::NonNull; use alloc::ffi::CString; use alloc::vec::Vec; -use super::pci::{HeaderType, PciDeviceStructure, PciDeviceStructureGeneralDevice, PciError}; +use super::pci::{PciDeviceStructure, PciDeviceStructureGeneralDevice, PciError}; use crate::arch::msi::{ia64_pci_get_arch_msi_message_address, ia64_pci_get_arch_msi_message_data}; use crate::arch::{PciArch, TraitPciArch}; use crate::include::bindings::bindings::{ c_irq_install, c_irq_uninstall, pt_regs, ul, EAGAIN, EINVAL, }; -use crate::libs::volatile::{ - volread, volwrite, ReadOnly, Volatile, VolatileReadable, VolatileWritable, WriteOnly, -}; -use crate::{kdebug, kerror, kinfo, kwarn}; +use crate::libs::volatile::{volread, volwrite, Volatile, VolatileReadable, VolatileWritable}; + /// MSIX表的一项 #[repr(C)] struct MsixEntry { @@ -268,7 +266,7 @@ pub trait PciInterrupt: PciDeviceStructure { return Err(PciError::PciIrqError(PciIrqError::PciDeviceNotSupportIrq)); } /// @brief 获取指定数量的中断号 todo 需要中断重构支持 - fn irq_alloc(num: u16) -> Option> { + fn irq_alloc(_num: u16) -> Option> { None } /// @brief 进行PCI设备中断的安装 diff --git a/kernel/src/driver/uart/uart.rs b/kernel/src/driver/uart/uart.rs index 15fefbca..101f4944 100644 --- a/kernel/src/driver/uart/uart.rs +++ b/kernel/src/driver/uart/uart.rs @@ -2,7 +2,7 @@ use super::super::base::device::Device; use crate::{ driver::base::{ char::CharDevice, - device::{driver::Driver, DeviceState, DeviceType, IdTable, KObject}, + device::{driver::Driver, DeviceState, DeviceType, IdTable, KObject}, platform::{ self, platform_device::PlatformDevice, platform_driver::PlatformDriver, CompatibleTable, }, diff --git a/kernel/src/driver/video/video.c b/kernel/src/driver/video/video.c index 00365236..6bbfc313 100644 --- a/kernel/src/driver/video/video.c +++ b/kernel/src/driver/video/video.c @@ -34,16 +34,10 @@ void init_frame_buffer() { kinfo("Re-mapping VBE frame buffer..."); - uint64_t global_CR3 = (uint64_t)get_CR3(); - - struct multiboot_tag_framebuffer_info_t info; - int reserved; - video_frame_buffer_info.vaddr = SPECIAL_MEMOEY_MAPPING_VIRT_ADDR_BASE + FRAME_BUFFER_MAPPING_OFFSET; - mm_map_proc_page_table(global_CR3, true, video_frame_buffer_info.vaddr, __fb_info.framebuffer_addr, - video_frame_buffer_info.size, PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD, false, true, false); - flush_tlb(); + rs_map_phys(video_frame_buffer_info.vaddr, __fb_info.framebuffer_addr, video_frame_buffer_info.size, PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD); + kinfo("VBE frame buffer successfully Re-mapped!"); } @@ -119,7 +113,6 @@ int video_reinitialize(bool level) // 这个函数会在main.c调用, 保证 vid // 启用屏幕刷新软中断 rs_register_softirq_video(); rs_raise_softirq(VIDEO_REFRESH_SIRQ); - } return 0; } @@ -189,8 +182,10 @@ int video_init() video_frame_buffer_info.width * video_frame_buffer_info.height * ((video_frame_buffer_info.bit_depth + 7) / 8); // 先临时映射到该地址,稍后再重新映射 video_frame_buffer_info.vaddr = 0xffff800003000000; - mm_map_phys_addr(video_frame_buffer_info.vaddr, __fb_info.framebuffer_addr, video_frame_buffer_info.size, - PAGE_KERNEL_PAGE | PAGE_PWT | PAGE_PCD, false); + char init_text1[] = "Video driver to map.\n"; + for (int i = 0; i < sizeof(init_text1) - 1; ++i) + c_uart_send(COM1, init_text1[i]); + rs_pseudo_map_phys(video_frame_buffer_info.vaddr, __fb_info.framebuffer_addr, video_frame_buffer_info.size); io_mfence(); char init_text2[] = "Video driver initialized.\n"; diff --git a/kernel/src/driver/virtio/virtio_impl.rs b/kernel/src/driver/virtio/virtio_impl.rs index 1ad3f9dc..645979b6 100644 --- a/kernel/src/driver/virtio/virtio_impl.rs +++ b/kernel/src/driver/virtio/virtio_impl.rs @@ -1,59 +1,101 @@ -/// 为virtio-drivers库提供的操作系统接口 -use crate::include::bindings::bindings::{ - alloc_pages, free_pages, memory_management_struct, Page, PAGE_2M_SHIFT, PAGE_2M_SIZE, - PAGE_OFFSET, PAGE_SHARED, ZONE_NORMAL, +use crate::arch::mm::kernel_page_flags; + +use crate::arch::MMArch; + +use crate::mm::kernel_mapper::KernelMapper; +use crate::mm::page::PageFlags; +use crate::mm::{ + allocator::page_frame::{ + allocate_page_frames, deallocate_page_frames, PageFrameCount, PhysPageFrame, + }, + MemoryManagementArch, PhysAddr, VirtAddr, }; -use crate::mm::virt_2_phys; -use core::mem::size_of; use core::ptr::NonNull; -use virtio_drivers::{BufferDirection, Hal, PhysAddr, PAGE_SIZE}; +use virtio_drivers::{BufferDirection, Hal, PAGE_SIZE}; + pub struct HalImpl; unsafe impl Hal for HalImpl { /// @brief 申请用于DMA的内存页 /// @param pages 页数(4k一页) /// @return PhysAddr 获得的内存页的初始物理地址 - fn dma_alloc(pages: usize, _direction: BufferDirection) -> (PhysAddr, NonNull) { - let page_num = (pages * PAGE_SIZE - 1 + PAGE_2M_SIZE as usize) / PAGE_2M_SIZE as usize; + fn dma_alloc( + pages: usize, + _direction: BufferDirection, + ) -> (virtio_drivers::PhysAddr, NonNull) { + let page_num = PageFrameCount::new( + ((pages * PAGE_SIZE + MMArch::PAGE_SIZE - 1) / MMArch::PAGE_SIZE).next_power_of_two(), + ); unsafe { - let pa = alloc_pages(ZONE_NORMAL, page_num as i32, PAGE_SHARED as u64); - let page = *pa; - //kdebug!("alloc pages num:{},Phyaddr={:#x}",pages,page.addr_phys); - ( - page.addr_phys as PhysAddr, - NonNull::new((page.addr_phys as PhysAddr + PAGE_OFFSET as usize) as _).unwrap(), - ) + let (paddr, count) = + allocate_page_frames(page_num).expect("VirtIO Impl: alloc page failed"); + let virt = MMArch::phys_2_virt(paddr).unwrap(); + // 清空这块区域,防止出现脏数据 + core::ptr::write_bytes(virt.data() as *mut u8, 0, count.data() * MMArch::PAGE_SIZE); + + let dma_flags: PageFlags = PageFlags::mmio_flags(); + + let mut kernel_mapper = KernelMapper::lock(); + let kernel_mapper = kernel_mapper.as_mut().unwrap(); + let flusher = kernel_mapper + .remap(virt, dma_flags) + .expect("VirtIO Impl: remap failed"); + flusher.flush(); + return ( + paddr.data(), + NonNull::new(MMArch::phys_2_virt(paddr).unwrap().data() as _).unwrap(), + ); } } /// @brief 释放用于DMA的内存页 /// @param paddr 起始物理地址 pages 页数(4k一页) /// @return i32 0表示成功 - unsafe fn dma_dealloc(paddr: PhysAddr, _vaddr: NonNull, pages: usize) -> i32 { - let page_num = (pages * PAGE_SIZE - 1 + PAGE_2M_SIZE as usize) / PAGE_2M_SIZE as usize; + unsafe fn dma_dealloc( + paddr: virtio_drivers::PhysAddr, + vaddr: NonNull, + pages: usize, + ) -> i32 { + let page_count = PageFrameCount::new( + ((pages * PAGE_SIZE + MMArch::PAGE_SIZE - 1) / MMArch::PAGE_SIZE).next_power_of_two(), + ); + + // 恢复页面属性 + let vaddr = VirtAddr::new(vaddr.as_ptr() as *mut u8 as usize); + let mut kernel_mapper = KernelMapper::lock(); + let kernel_mapper = kernel_mapper.as_mut().unwrap(); + let flusher = kernel_mapper + .remap(vaddr, kernel_page_flags(vaddr)) + .expect("VirtIO Impl: remap failed"); + flusher.flush(); + unsafe { - let pa = (memory_management_struct.pages_struct as usize - + (paddr >> PAGE_2M_SHIFT) * size_of::()) as *mut Page; - //kdebug!("free pages num:{},Phyaddr={}",page_num,paddr); - free_pages(pa, page_num as i32); + deallocate_page_frames(PhysPageFrame::new(PhysAddr::new(paddr)), page_count); } return 0; } /// @brief mmio物理地址转换为虚拟地址,不需要使用 /// @param paddr 起始物理地址 /// @return NonNull 虚拟地址的指针 - unsafe fn mmio_phys_to_virt(_paddr: PhysAddr, _size: usize) -> NonNull { + unsafe fn mmio_phys_to_virt(_paddr: virtio_drivers::PhysAddr, _size: usize) -> NonNull { NonNull::new((0) as _).unwrap() } /// @brief 与真实物理设备共享 /// @param buffer 要共享的buffer _direction:设备到driver或driver到设备 /// @return buffer在内存中的物理地址 - unsafe fn share(buffer: NonNull<[u8]>, _direction: BufferDirection) -> PhysAddr { - let vaddr = buffer.as_ptr() as *mut u8 as usize; + unsafe fn share( + buffer: NonNull<[u8]>, + _direction: BufferDirection, + ) -> virtio_drivers::PhysAddr { + let vaddr = VirtAddr::new(buffer.as_ptr() as *mut u8 as usize); //kdebug!("virt:{:x}", vaddr); // Nothing to do, as the host already has access to all memory. - virt_2_phys(vaddr) + return MMArch::virt_2_phys(vaddr).unwrap().data(); } /// @brief 停止共享(让主机可以访问全部内存的话什么都不用做) - unsafe fn unshare(_paddr: PhysAddr, _buffer: NonNull<[u8]>, _direction: BufferDirection) { + unsafe fn unshare( + _paddr: virtio_drivers::PhysAddr, + _buffer: NonNull<[u8]>, + _direction: BufferDirection, + ) { // Nothing to do, as the host already has access to all memory and we didn't copy the buffer // anywhere else. } diff --git a/kernel/src/exception/mod.rs b/kernel/src/exception/mod.rs index d4240a4e..5ab53bc4 100644 --- a/kernel/src/exception/mod.rs +++ b/kernel/src/exception/mod.rs @@ -19,15 +19,15 @@ pub trait InterruptArch: Send + Sync { #[derive(Debug, Clone, Copy)] pub struct IrqFlags { - flags: u64, + flags: usize, } impl IrqFlags { - pub fn new(flags: u64) -> Self { + pub fn new(flags: usize) -> Self { IrqFlags { flags } } - pub fn flags(&self) -> u64 { + pub fn flags(&self) -> usize { self.flags } } diff --git a/kernel/src/exception/softirq.rs b/kernel/src/exception/softirq.rs index 3cc6eff9..8eeb158f 100644 --- a/kernel/src/exception/softirq.rs +++ b/kernel/src/exception/softirq.rs @@ -46,6 +46,7 @@ pub fn softirq_init() -> Result<(), SystemError> { cpu_pending[i as usize] = VecStatus::default(); } } + kinfo!("Softirq initialized."); return Ok(()); } diff --git a/kernel/src/filesystem/procfs/mod.rs b/kernel/src/filesystem/procfs/mod.rs index b46f954c..c1146cfb 100644 --- a/kernel/src/filesystem/procfs/mod.rs +++ b/kernel/src/filesystem/procfs/mod.rs @@ -10,6 +10,7 @@ use alloc::{ }; use crate::{ + arch::asm::current::current_pcb, filesystem::vfs::{ core::{generate_inode_id, ROOT_INODE}, FileType, @@ -168,13 +169,15 @@ impl ProcFSInode { .to_owned(), ); - // 当前进程运行过程中占用内存的峰值 - let hiwater_vm: u64 = - unsafe { *(*pcb.mm).vmas }.vm_end - unsafe { *(*pcb.mm).vmas }.vm_start; + let binding = current_pcb().address_space().unwrap(); + let address_space_guard = binding.read(); + // todo: 当前进程运行过程中占用内存的峰值 + let hiwater_vm: u64 = 0; + // 进程代码段的大小 + let text = (address_space_guard.end_code - address_space_guard.start_code) / 1024; // 进程数据段的大小 - let text: u64 = unsafe { *pcb.mm }.code_addr_end - unsafe { *pcb.mm }.code_addr_start; - // 进程代码的大小 - let data: u64 = unsafe { *pcb.mm }.data_addr_end - unsafe { *pcb.mm }.data_addr_start; + let data = (address_space_guard.end_data - address_space_guard.start_data) / 1024; + drop(address_space_guard); pdata.append( &mut format!("\nVmPeak:\t{} kB", hiwater_vm) diff --git a/kernel/src/filesystem/sysfs/bus.rs b/kernel/src/filesystem/sysfs/bus.rs index 1676a66e..acd797f0 100644 --- a/kernel/src/filesystem/sysfs/bus.rs +++ b/kernel/src/filesystem/sysfs/bus.rs @@ -1,9 +1,5 @@ use super::{LockedSysFSInode, SYS_BUS_INODE}; -use crate::{ - filesystem::vfs::IndexNode, - kdebug, - syscall::SystemError, -}; +use crate::{filesystem::vfs::IndexNode, kdebug, syscall::SystemError}; use alloc::sync::Arc; /// @brief: 注册bus,在sys/bus下生成文件夹 @@ -58,20 +54,15 @@ pub fn sys_bus_init( /// @parameter bus_name: 总线名 /// name: 设备名 /// @return: 操作成功,返回device inode,操作失败,返回错误码 -pub fn bus_driver_register( - bus_name: &str, - name: &str, -) -> Result, SystemError> { +pub fn bus_driver_register(bus_name: &str, name: &str) -> Result, SystemError> { match SYS_BUS_INODE().find(bus_name) { Ok(platform) => match platform.find("drivers") { - Ok(device) => { - device - .as_any_ref() - .downcast_ref::() - .ok_or(SystemError::E2BIG) - .unwrap() - .add_dir(name) - } + Ok(device) => device + .as_any_ref() + .downcast_ref::() + .ok_or(SystemError::E2BIG) + .unwrap() + .add_dir(name), Err(_) => return Err(SystemError::EXDEV), }, Err(_) => return Err(SystemError::EXDEV), @@ -82,20 +73,15 @@ pub fn bus_driver_register( /// @parameter bus_name: 总线名 /// name: 驱动名 /// @return: 操作成功,返回drivers inode,操作失败,返回错误码 -pub fn bus_device_register( - bus_name: &str, - name: &str, -) -> Result, SystemError> { +pub fn bus_device_register(bus_name: &str, name: &str) -> Result, SystemError> { match SYS_BUS_INODE().find(bus_name) { Ok(platform) => match platform.find("devices") { - Ok(device) => { - device - .as_any_ref() - .downcast_ref::() - .ok_or(SystemError::E2BIG) - .unwrap() - .add_dir(name) - } + Ok(device) => device + .as_any_ref() + .downcast_ref::() + .ok_or(SystemError::E2BIG) + .unwrap() + .add_dir(name), Err(_) => return Err(SystemError::EXDEV), }, Err(_) => return Err(SystemError::EXDEV), diff --git a/kernel/src/filesystem/vfs/VFS.h b/kernel/src/filesystem/vfs/VFS.h index 4675d9b2..d7f78dd4 100644 --- a/kernel/src/filesystem/vfs/VFS.h +++ b/kernel/src/filesystem/vfs/VFS.h @@ -22,7 +22,7 @@ #define VFS_DPT_MBR 0 // MBR分区表 #define VFS_DPT_GPT 1 // GPT分区表 -#define VFS_MAX_PATHLEN 1024 +#define VFS_MAX_PATHLEN 4096 /** * @brief inode的属性 diff --git a/kernel/src/filesystem/vfs/file.rs b/kernel/src/filesystem/vfs/file.rs index 02d3eff8..c216360c 100644 --- a/kernel/src/filesystem/vfs/file.rs +++ b/kernel/src/filesystem/vfs/file.rs @@ -173,7 +173,7 @@ impl File { /// /// @param origin 调整的起始位置 pub fn lseek(&mut self, origin: SeekFrom) -> Result { - let file_type = self.inode.metadata().unwrap().file_type; + let file_type = self.inode.metadata()?.file_type; match file_type { FileType::Pipe | FileType::CharDevice => { return Err(SystemError::ESPIPE); diff --git a/kernel/src/head.S b/kernel/src/head.S index 985f8cf1..19677460 100644 --- a/kernel/src/head.S +++ b/kernel/src/head.S @@ -255,7 +255,7 @@ ENTRY(_start64) movq $0x1b, %rcx // 根据IA32_APIC_BASE.BSP[8]标志位判断处理器是否为apu rdmsr bt $8, %rax - jnc load_cr3 + jnc load_apu_cr3 // 2. 设置临时页表 // 最高级 @@ -263,7 +263,11 @@ ENTRY(_start64) mov $__PDPTE, %ebx or $0x3, %ebx mov %ebx, 0(%eax) - mov %ebx, 256(%eax) + + mov $__PML4E, %eax + // 加256个表项, 映射高地址 + add $2048, %eax + mov %ebx, 0(%eax) // 次级 mov $__PDPTE, %eax @@ -271,6 +275,28 @@ ENTRY(_start64) or $0x3, %ebx mov %ebx, 0(%eax) + // 次低级 + mov $__PDE, %eax + mov $50, %ecx + mov $__PT_S, %ebx + or $0x3, %ebx +.fill_pde_64: + mov %ebx, 0(%eax) + add $0x1000, %ebx + add $8, %eax + loop .fill_pde_64 + + // 最低级 + // 循环 512*50=25600 次,填满50页 + mov $25600, %ecx + mov $__PT_S, %eax + mov $0x3, %ebx +.fill_pt_64: + mov %ebx, 0(%eax) + add $0x1000, %ebx + add $8, %eax + loop .fill_pt_64 + // ==== 加载CR3寄存器 @@ -279,7 +305,20 @@ load_cr3: movq $__PML4E, %rax //设置页目录基地址 movq %rax, %cr3 - + jmp to_switch_seg + +load_apu_cr3: + // 由于内存管理模块重置了页表,因此ap核心初始化的时候,需要使用新的内核页表。 + // 这个页表的值由smp模块设置到__APU_START_CR3变量中 + + // 加载__APU_START_CR3中的值 + movq $__APU_START_CR3, %rax + movq 0(%rax), %rax + movq %rax, %cr3 + jmp to_switch_seg + +to_switch_seg: + movq switch_seg(%rip), %rax // 由于ljmp和lcall在GAS中不受支持,因此我们需要先伪造函数调用现场,通过lret的方式,给它跳转过去。才能更新cs寄存器 // 实在是太妙了!Amazing! @@ -481,73 +520,23 @@ ENTRY(head_stack_start) // 初始化页表 .align 0x1000 //设置为4k对齐 -//.org 0x1000 //设置页表位置为内核执行头程序的0x1000处 - __PML4E: - .quad 0x103007 // 用户访问,可读写,已存在, 地址在31~12位 - .fill 255,8,0 - .quad 0x103003 - .fill 255,8,0 - -.org 0x2000 - + .skip 0x1000 __PDPTE: + .skip 0x1000 - .quad 0x104003 // 用户访问,可读写,已存在 - .fill 511,8,0 - -.org 0x3000 - +// 三级页表 __PDE: + .skip 0x1000 - .quad 0x000083 // 用户访问,可读写,已存在 - .quad 0x200083 - .quad 0x400083 - .quad 0x600083 - .quad 0x800083 - .quad 0xa00083 - .quad 0xc00083 - .quad 0xe00083 - .quad 0x1000083 - .quad 0x1200083 - .quad 0x1400083 - .quad 0x1600083 - .quad 0x1800083 - .quad 0x1a00083 - .quad 0x1c00083 - .quad 0x1e00083 - .quad 0x2000083 - .quad 0x2200083 - .quad 0x2400083 - .quad 0x2600083 - .quad 0x2800083 - .quad 0x2a00083 - .quad 0x2c00083 - .quad 0x2e00083 - .quad 0x3000083 - .quad 0x3200083 - .quad 0x3400083 - .quad 0x3600083 +// 预留50个四级页表,总共表示100M的内存空间。这50个页表占用200KB的空间 +__PT_S: + .skip 0x32000 - .quad 0xe0000083 /*虚拟地址0x 3000000 初始情况下,帧缓冲区映射到这里*/ - .quad 0xe0200083 - .quad 0xe0400083 - .quad 0xe0600083 /*0x1000000*/ - .quad 0xe0800083 - .quad 0xe0a00083 - .quad 0xe0c00083 - .quad 0xe0e00083 - .quad 0xe1000083 - .quad 0xe1200083 - .quad 0xe1400083 - .quad 0xe1600083 - .quad 0xe1800083 - .quad 0xe1a00083 - .quad 0xe1c00083 - .quad 0xe1e00083 - .fill 468,8,0 - +.global __APU_START_CR3 +__APU_START_CR3: + .quad 0 // GDT表 diff --git a/kernel/src/include/bindings/wrapper.h b/kernel/src/include/bindings/wrapper.h index b7a0d376..f6cf2d59 100644 --- a/kernel/src/include/bindings/wrapper.h +++ b/kernel/src/include/bindings/wrapper.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -49,4 +50,4 @@ #include { + // 页存放entry的数量 + entry_num: usize, + // 下一个页面的地址 + next_page: PhysAddr, + phantom: PhantomData, +} + +impl Clone for PageList { + fn clone(&self) -> Self { + Self { + entry_num: self.entry_num, + next_page: self.next_page, + phantom: PhantomData, + } + } +} + +impl PageList { + #[allow(dead_code)] + fn empty() -> Self { + Self { + entry_num: 0, + next_page: PhysAddr::new(0), + phantom: PhantomData, + } + } + fn new(entry_num: usize, next_page: PhysAddr) -> Self { + Self { + entry_num, + next_page, + phantom: PhantomData, + } + } +} + +/// @brief: 用来表示 buddy 算法中的一个 buddy 块,整体存放在area的头部 +// 这种方式会出现对齐问题 +// #[repr(packed)] +#[repr(C)] +#[derive(Debug)] +pub struct BuddyAllocator { + // 存放每个阶的空闲“链表”的头部地址 + free_area: [PhysAddr; (MAX_ORDER - MIN_ORDER) as usize], + phantom: PhantomData, +} + +impl BuddyAllocator { + const BUDDY_ENTRIES: usize = + // 定义一个变量记录buddy表的大小 + (A::PAGE_SIZE - mem::size_of::>()) / mem::size_of::(); + + pub unsafe fn new(mut bump_allocator: BumpAllocator) -> Option { + let initial_free_pages = bump_allocator.usage().free(); + kdebug!("Free pages before init buddy: {:?}", initial_free_pages); + kdebug!("Buddy entries: {}", Self::BUDDY_ENTRIES); + // 最高阶的链表页数 + let max_order_linked_list_page_num = max( + 1, + (((initial_free_pages.data() * A::PAGE_SIZE) >> (MAX_ORDER - 1)) + Self::BUDDY_ENTRIES + - 1) + / Self::BUDDY_ENTRIES, + ); + + let mut free_area: [PhysAddr; (MAX_ORDER - MIN_ORDER) as usize] = + [PhysAddr::new(0); (MAX_ORDER - MIN_ORDER) as usize]; + + // Buddy初始占用的空间从bump分配 + for f in free_area.iter_mut() { + let curr_page = bump_allocator.allocate_one(); + // 保存每个阶的空闲链表的头部地址 + *f = curr_page.unwrap(); + // 清空当前页 + core::ptr::write_bytes(MMArch::phys_2_virt(*f)?.data() as *mut u8, 0, A::PAGE_SIZE); + + let page_list: PageList = PageList::new(0, PhysAddr::new(0)); + Self::write_page(*f, page_list); + } + + // 分配最高阶的链表页 + for _ in 1..max_order_linked_list_page_num { + let curr_page = bump_allocator.allocate_one().unwrap(); + // 清空当前页 + core::ptr::write_bytes( + MMArch::phys_2_virt(curr_page)?.data() as *mut u8, + 0, + A::PAGE_SIZE, + ); + + let page_list: PageList = + PageList::new(0, free_area[Self::order2index((MAX_ORDER - 1) as u8)]); + Self::write_page(curr_page, page_list); + free_area[Self::order2index((MAX_ORDER - 1) as u8)] = curr_page; + } + + let initial_bump_offset = bump_allocator.offset(); + let pages_to_buddy = bump_allocator.usage().free(); + kdebug!("pages_to_buddy {:?}", pages_to_buddy); + // kdebug!("initial_bump_offset {:#x}", initial_bump_offset); + let mut paddr = initial_bump_offset; + let mut remain_pages = pages_to_buddy; + // 设置entry,这里假设了bump_allocator当前offset之后,所有的area的地址是连续的. + // TODO: 这里需要修改,按照area来处理 + for i in MIN_ORDER..MAX_ORDER { + // kdebug!("i {i}, remain pages={}", remain_pages.data()); + if remain_pages.data() < (1 << (i - MIN_ORDER)) { + break; + } + + assert!(paddr & ((1 << i) - 1) == 0); + + if likely(i != MAX_ORDER - 1) { + // 要填写entry + if paddr & (1 << i) != 0 { + let page_list_paddr: PhysAddr = free_area[Self::order2index(i as u8)]; + let mut page_list: PageList = Self::read_page(page_list_paddr); + + A::write( + Self::entry_virt_addr(page_list_paddr, page_list.entry_num), + paddr, + ); + page_list.entry_num += 1; + Self::write_page(page_list_paddr, page_list); + + paddr += 1 << i; + remain_pages -= 1 << (i - MIN_ORDER); + }; + } else { + // 往最大的阶数的链表中添加entry(注意要考虑到最大阶数的链表可能有多页) + // 断言剩余页面数量是MAX_ORDER-1阶的整数倍 + + let mut entries = (remain_pages.data() * A::PAGE_SIZE) >> i; + let mut page_list_paddr: PhysAddr = free_area[Self::order2index(i as u8)]; + let block_size = 1usize << i; + + if entries > Self::BUDDY_ENTRIES { + // 在第一页填写一些entries + let num = entries % Self::BUDDY_ENTRIES; + entries -= num; + + let mut page_list: PageList = Self::read_page(page_list_paddr); + for _j in 0..num { + A::write( + Self::entry_virt_addr(page_list_paddr, page_list.entry_num), + paddr, + ); + page_list.entry_num += 1; + paddr += block_size; + remain_pages -= 1 << (i - MIN_ORDER); + } + page_list_paddr = page_list.next_page; + Self::write_page(page_list_paddr, page_list); + assert!(!page_list_paddr.is_null()); + } + + while entries > 0 { + let mut page_list: PageList = Self::read_page(page_list_paddr); + + for _ in 0..Self::BUDDY_ENTRIES { + A::write( + Self::entry_virt_addr(page_list_paddr, page_list.entry_num), + paddr, + ); + page_list.entry_num += 1; + paddr += block_size; + remain_pages -= 1 << (i - MIN_ORDER); + entries -= 1; + if entries == 0 { + break; + } + } + page_list_paddr = page_list.next_page; + Self::write_page(page_list_paddr, page_list); + + if likely(entries > 0) { + assert!(!page_list_paddr.is_null()); + } + } + } + } + + let mut remain_bytes = remain_pages.data() * A::PAGE_SIZE; + + assert!(remain_bytes < (1 << MAX_ORDER - 1)); + + for i in (MIN_ORDER..MAX_ORDER).rev() { + if remain_bytes & (1 << i) != 0 { + let page_list_paddr: PhysAddr = free_area[Self::order2index(i as u8)]; + let mut page_list: PageList = Self::read_page(page_list_paddr); + + A::write( + Self::entry_virt_addr(page_list_paddr, page_list.entry_num), + paddr, + ); + page_list.entry_num += 1; + Self::write_page(page_list_paddr, page_list); + + paddr += 1 << i; + remain_bytes -= 1 << i; + } + } + + assert!(remain_bytes == 0); + assert!(paddr == initial_bump_offset + pages_to_buddy.data() * A::PAGE_SIZE); + + // Self::print_free_area(free_area); + let allocator = Self { + free_area, + phantom: PhantomData, + }; + + Some(allocator) + } + /// 获取第j个entry的虚拟地址, + /// j从0开始计数 + pub fn entry_virt_addr(base_addr: PhysAddr, j: usize) -> VirtAddr { + let entry_virt_addr = unsafe { A::phys_2_virt(Self::entry_addr(base_addr, j)) }; + return entry_virt_addr.unwrap(); + } + pub fn entry_addr(base_addr: PhysAddr, j: usize) -> PhysAddr { + let entry_addr = base_addr + mem::size_of::>() + j * mem::size_of::(); + return entry_addr; + } + pub fn read_page(addr: PhysAddr) -> T { + let page_list = unsafe { A::read(A::phys_2_virt(addr).unwrap()) }; + return page_list; + } + + pub fn write_page(curr_page: PhysAddr, page_list: PageList) { + // 把物理地址转换为虚拟地址 + let virt_addr = unsafe { A::phys_2_virt(curr_page) }; + let virt_addr = virt_addr.unwrap(); + unsafe { A::write(virt_addr, page_list) }; + } + + /// 从order转换为free_area的下标 + /// + /// # 参数 + /// + /// - `order` - order + /// + /// # 返回值 + /// + /// free_area的下标 + #[inline] + fn order2index(order: u8) -> usize { + (order as usize - MIN_ORDER) as usize + } + + /// 从空闲链表的开头,取出1个指定阶数的伙伴块,如果没有,则返回None + /// + /// ## 参数 + /// + /// - `order` - 伙伴块的阶数 + fn pop_front(&mut self, order: u8) -> Option { + let mut alloc_in_specific_order = |spec_order: u8| { + // 先尝试在order阶的“空闲链表”的开头位置分配一个伙伴块 + let mut page_list_addr = self.free_area[Self::order2index(spec_order)]; + let mut page_list: PageList = Self::read_page(page_list_addr); + + // kdebug!("page_list={page_list:?}"); + + // 循环删除头部的空闲链表页 + while page_list.entry_num == 0 { + let next_page_list_addr = page_list.next_page; + // 找完了,都是空的 + if next_page_list_addr.is_null() { + return None; + } + + if !next_page_list_addr.is_null() { + // 此时page_list已经没有空闲伙伴块了,又因为非唯一页,需要删除该page_list + self.free_area[Self::order2index(spec_order)] = next_page_list_addr; + drop(page_list); + // kdebug!("FREE: page_list_addr={:b}", page_list_addr.data()); + unsafe { + self.buddy_free(page_list_addr, MMArch::PAGE_SHIFT as u8); + } + } + // 由于buddy_free可能导致首部的链表页发生变化,因此需要重新读取 + let next_page_list_addr = self.free_area[Self::order2index(spec_order)]; + assert!(!next_page_list_addr.is_null()); + page_list = Self::read_page(next_page_list_addr); + page_list_addr = next_page_list_addr; + } + + // 有空闲页面,直接分配 + if page_list.entry_num > 0 { + let entry: PhysAddr = unsafe { + A::read(Self::entry_virt_addr( + page_list_addr, + page_list.entry_num - 1, + )) + }; + if entry.is_null() { + kerror!( + "entry is null, entry={:?}, order={}, entry_num = {}", + entry, + spec_order, + page_list.entry_num - 1 + ); + } + // kdebug!("entry={entry:?}"); + // 更新page_list的entry_num + page_list.entry_num -= 1; + let tmp_current_entry_num = page_list.entry_num; + if page_list.entry_num == 0 { + if !page_list.next_page.is_null() { + // 此时page_list已经没有空闲伙伴块了,又因为非唯一页,需要删除该page_list + self.free_area[Self::order2index(spec_order)] = page_list.next_page; + drop(page_list); + unsafe { self.buddy_free(page_list_addr, MMArch::PAGE_SHIFT as u8) }; + } else { + Self::write_page(page_list_addr, page_list); + } + } else { + // 若entry_num不为0,说明该page_list还有空闲伙伴块,需要更新该page_list + // 把更新后的page_list写回 + Self::write_page(page_list_addr, page_list.clone()); + } + + // 检测entry 是否对齐 + if !entry.check_aligned(1 << spec_order) { + panic!("entry={:?} is not aligned, spec_order={spec_order}, page_list.entry_num={}", entry,tmp_current_entry_num); + } + return Some(entry); + } + return None; + }; + + let result: Option = alloc_in_specific_order(order as u8); + // kdebug!("result={:?}", result); + if result.is_some() { + return result; + } + // 尝试从更大的链表中分裂 + + let mut current_order = (order + 1) as usize; + let mut x: Option = None; + while current_order < MAX_ORDER { + x = alloc_in_specific_order(current_order as u8); + // kdebug!("current_order={:?}", current_order); + if x.is_some() { + break; + } + current_order += 1; + } + + // kdebug!("x={:?}", x); + // 如果找到一个大的块,就进行分裂 + if x.is_some() { + // 分裂到order阶 + while current_order > order as usize { + current_order -= 1; + // 把后面那半块放回空闲链表 + + let buddy = *x.as_ref().unwrap() + (1 << current_order); + // kdebug!("x={:?}, buddy={:?}", x, buddy); + // kdebug!("current_order={:?}, buddy={:?}", current_order, buddy); + unsafe { self.buddy_free(buddy, current_order as u8) }; + } + return x; + } + + return None; + } + + /// 从伙伴系统中分配count个页面 + /// + /// ## 参数 + /// + /// - `count`:需要分配的页面数 + /// + /// ## 返回值 + /// + /// 返回分配的页面的物理地址和页面数 + fn buddy_alloc(&mut self, count: PageFrameCount) -> Option<(PhysAddr, PageFrameCount)> { + assert!(count.data().is_power_of_two()); + // 计算需要分配的阶数 + let mut order = log2(count.data() as usize); + if count.data() & ((1 << order) - 1) != 0 { + order += 1; + } + let order = (order + MIN_ORDER) as u8; + if order as usize >= MAX_ORDER { + return None; + } + + // kdebug!("buddy_alloc: order = {}", order); + // 获取该阶数的一个空闲页面 + let free_addr = self.pop_front(order); + // kdebug!( + // "buddy_alloc: order = {}, free_addr = {:?}", + // order, + // free_addr + // ); + return free_addr + .map(|addr| (addr, PageFrameCount::new(1 << (order as usize - MIN_ORDER)))); + } + + /// 释放一个块 + /// + /// ## 参数 + /// + /// - `base` - 块的起始地址 + /// - `order` - 块的阶数 + unsafe fn buddy_free(&mut self, mut base: PhysAddr, order: u8) { + // kdebug!("buddy_free: base = {:?}, order = {}", base, order); + let mut order = order as usize; + + while order < MAX_ORDER { + // 检测地址是否合法 + if base.data() & ((1 << (order)) - 1) != 0 { + panic!( + "buddy_free: base is not aligned, base = {:#x}, order = {}", + base.data(), + order + ); + } + + // 在链表中寻找伙伴块 + // 伙伴块的地址是base ^ (1 << order) + let buddy_addr = PhysAddr::new(base.data() ^ (1 << order)); + + let first_page_list_paddr = self.free_area[Self::order2index(order as u8)]; + let mut page_list_paddr = first_page_list_paddr; + let mut page_list: PageList = Self::read_page(page_list_paddr); + let first_page_list = page_list.clone(); + + let mut buddy_entry_virt_vaddr = None; + let mut buddy_entry_page_list_paddr = None; + // 除非order是最大的,否则尝试查找伙伴块 + if likely(order != MAX_ORDER - 1) { + 'outer: loop { + for i in 0..page_list.entry_num { + let entry_virt_addr = Self::entry_virt_addr(page_list_paddr, i); + let entry: PhysAddr = unsafe { A::read(entry_virt_addr) }; + if entry == buddy_addr { + // 找到了伙伴块,记录该entry相关信息,然后退出查找 + buddy_entry_virt_vaddr = Some(entry_virt_addr); + buddy_entry_page_list_paddr = Some(page_list_paddr); + break 'outer; + } + } + if page_list.next_page.is_null() { + break; + } + page_list_paddr = page_list.next_page; + page_list = Self::read_page(page_list_paddr); + } + } + + // 如果没有找到伙伴块 + if buddy_entry_virt_vaddr.is_none() { + assert!( + page_list.entry_num <= Self::BUDDY_ENTRIES, + "buddy_free: page_list.entry_num > Self::BUDDY_ENTRIES" + ); + + // 当前第一个page_list没有空间了 + if first_page_list.entry_num == Self::BUDDY_ENTRIES { + // 如果当前order是最小的,那么就把这个块当作新的page_list使用 + let new_page_list_addr = if order == MIN_ORDER { + base + } else { + // 否则分配新的page_list + // 请注意,分配之后,有可能当前的entry_num会减1(伙伴块分裂),造成出现整个链表为null的entry数量为Self::BUDDY_ENTRIES+1的情况 + // 但是不影响,我们在后面插入链表项的时候,会处理这种情况,检查链表中的第2个页是否有空位 + self.buddy_alloc(PageFrameCount::new(1)) + .expect("buddy_alloc failed: no enough memory") + .0 + }; + + // 清空这个页面 + core::ptr::write_bytes( + A::phys_2_virt(new_page_list_addr) + .expect( + "Buddy free: failed to get virt address of [new_page_list_addr]", + ) + .as_ptr::(), + 0, + 1 << order, + ); + assert!( + first_page_list_paddr == self.free_area[Self::order2index(order as u8)] + ); + // 初始化新的page_list + let new_page_list = PageList::new(0, first_page_list_paddr); + Self::write_page(new_page_list_addr, new_page_list); + self.free_area[Self::order2index(order as u8)] = new_page_list_addr; + } + + // 由于上面可能更新了第一个链表页,因此需要重新获取这个值 + let first_page_list_paddr = self.free_area[Self::order2index(order as u8)]; + let first_page_list: PageList = Self::read_page(first_page_list_paddr); + + // 检查第二个page_list是否有空位 + let second_page_list = if first_page_list.next_page.is_null() { + None + } else { + Some(Self::read_page::>(first_page_list.next_page)) + }; + + let (paddr, mut page_list) = if let Some(second) = second_page_list { + // 第二个page_list有空位 + // 应当符合之前的假设:还有1个空位 + assert!(second.entry_num == Self::BUDDY_ENTRIES - 1); + + (first_page_list.next_page, second) + } else { + // 在第一个page list中分配 + (first_page_list_paddr, first_page_list) + }; + + // kdebug!("to write entry, page_list_base={paddr:?}, page_list.entry_num={}, value={base:?}", page_list.entry_num); + assert!(page_list.entry_num < Self::BUDDY_ENTRIES); + // 把要归还的块,写入到链表项中 + unsafe { A::write(Self::entry_virt_addr(paddr, page_list.entry_num), base) } + page_list.entry_num += 1; + Self::write_page(paddr, page_list); + return; + } else { + // 如果找到了伙伴块,合并,向上递归 + + // 伙伴块所在的表项的虚拟地址 + let buddy_entry_virt_addr = buddy_entry_virt_vaddr.unwrap(); + // 伙伴块所在的page_list的物理地址 + let buddy_entry_page_list_paddr = buddy_entry_page_list_paddr.unwrap(); + + let mut page_list_paddr = self.free_area[Self::order2index(order as u8)]; + let mut page_list = Self::read_page::>(page_list_paddr); + // 找第一个有空闲块的链表页。跳过空闲链表页。不进行回收的原因是担心出现死循环 + while page_list.entry_num == 0 { + if page_list.next_page.is_null() { + panic!( + "buddy_free: page_list.entry_num == 0 && page_list.next_page.is_null()" + ); + } + page_list_paddr = page_list.next_page; + page_list = Self::read_page(page_list_paddr); + } + + // 如果伙伴块不在第一个链表页,则把第一个链表中的某个空闲块替换到伙伴块的位置 + if page_list_paddr != buddy_entry_page_list_paddr { + let entry: PhysAddr = unsafe { + A::read(Self::entry_virt_addr( + page_list_paddr, + page_list.entry_num - 1, + )) + }; + // 把这个空闲块写入到伙伴块的位置 + unsafe { + A::write(buddy_entry_virt_addr, entry); + } + // 设置刚才那个entry为空 + unsafe { + A::write( + Self::entry_virt_addr(page_list_paddr, page_list.entry_num - 1), + PhysAddr::new(0), + ); + } + // 更新当前链表页的统计数据 + page_list.entry_num -= 1; + Self::write_page(page_list_paddr, page_list); + } else { + // 伙伴块所在的链表页就是第一个链表页 + let last_entry: PhysAddr = unsafe { + A::read(Self::entry_virt_addr( + page_list_paddr, + page_list.entry_num - 1, + )) + }; + + // 如果最后一个空闲块不是伙伴块,则把最后一个空闲块移动到伙伴块的位置 + // 否则后面的操作也将删除这个伙伴块 + if last_entry != buddy_addr { + unsafe { + A::write(buddy_entry_virt_addr, last_entry); + A::write( + Self::entry_virt_addr(page_list_paddr, page_list.entry_num - 1), + PhysAddr::new(0), + ); + } + } else { + unsafe { + A::write( + Self::entry_virt_addr(page_list_paddr, page_list.entry_num - 1), + PhysAddr::new(0), + ); + } + } + // 更新当前链表页的统计数据 + page_list.entry_num -= 1; + Self::write_page(page_list_paddr, page_list); + } + } + base = min(base, buddy_addr); + order += 1; + } + // 走到这一步,order应该为MAX_ORDER-1 + assert!(order == MAX_ORDER - 1); + } +} + +impl FrameAllocator for BuddyAllocator { + unsafe fn allocate(&mut self, count: PageFrameCount) -> Option<(PhysAddr, PageFrameCount)> { + return self.buddy_alloc(count); + } + + /// 释放一个块 + /// + /// ## 参数 + /// + /// - `base` - 块的起始地址 + /// - `count` - 块的页数(必须是2的幂) + /// + /// ## Panic + /// + /// 如果count不是2的幂,会panic + unsafe fn free(&mut self, base: PhysAddr, count: PageFrameCount) { + // 要求count是2的幂 + if unlikely(!count.data().is_power_of_two()) { + kwarn!("buddy free: count is not power of two"); + } + let mut order = log2(count.data() as usize); + if count.data() & ((1 << order) - 1) != 0 { + order += 1; + } + let order = (order + MIN_ORDER) as u8; + // kdebug!("free: base={:?}, count={:?}", base, count); + self.buddy_free(base, order); + } + + unsafe fn usage(&self) -> PageFrameUsage { + todo!("BuddyAllocator::usage") + } +} + +/// 一个用于计算整数的对数的函数,会向下取整。(由于内核不能进行浮点运算,因此需要这个函数) +fn log2(x: usize) -> usize { + let leading_zeros = x.leading_zeros() as usize; + let log2x = 63 - leading_zeros; + return log2x; +} diff --git a/kernel/src/mm/allocator/bump.rs b/kernel/src/mm/allocator/bump.rs new file mode 100644 index 00000000..02401749 --- /dev/null +++ b/kernel/src/mm/allocator/bump.rs @@ -0,0 +1,112 @@ +/// @Auther: Kong +/// @Date: 2023-03-27 06:54:08 +/// @FilePath: /DragonOS/kernel/src/mm/allocator/bump.rs +/// @Description: bump allocator线性分配器 +use super::page_frame::{FrameAllocator, PageFrameCount, PageFrameUsage}; +use crate::mm::{MemoryManagementArch, PhysAddr, PhysMemoryArea}; +use core::marker::PhantomData; + +/// 线性分配器 +pub struct BumpAllocator { + // 表示可用物理内存区域的数组。每个 PhysMemoryArea 结构体描述一个物理内存区域的起始地址和大小。 + areas: &'static [PhysMemoryArea], + // 表示当前分配的物理内存的偏移量. + offset: usize, + // 一个占位类型,用于标记 A 类型在结构体中的存在。但是,它并不会占用任何内存空间,因为它的大小为 0。 + phantom: PhantomData, +} + +/// 为BumpAllocator实现FrameAllocator +impl BumpAllocator { + /// @brief: 创建一个线性分配器 + /// @param Fareas 当前的内存区域 + /// @param offset 当前的偏移量 + /// @return 分配器本身 + pub fn new(areas: &'static [PhysMemoryArea], offset: usize) -> Self { + Self { + areas, + offset, + phantom: PhantomData, + } + } + // @brief 获取页帧使用情况 + pub fn areas(&self) -> &'static [PhysMemoryArea] { + return self.areas; + } + // @brief 获取当前分配的物理内存的偏移量 + pub fn offset(&self) -> usize { + return self.offset; + } +} + +impl FrameAllocator for BumpAllocator { + /// @brief: 分配count个物理页帧 + /// @param mut self + /// @param count 分配的页帧数量 + /// @return 分配后的物理地址 + unsafe fn allocate(&mut self, count: PageFrameCount) -> Option<(PhysAddr, PageFrameCount)> { + let mut offset = self.offset(); + // 遍历所有的物理内存区域 + for area in self.areas().iter() { + // 将area的base地址与PAGE_SIZE对齐,对齐时向上取整 + // let area_base = (area.base.data() + MMA::PAGE_SHIFT) & !(MMA::PAGE_SHIFT); + let area_base = (area.base.data() + (MMA::PAGE_SIZE - 1)) & !(MMA::PAGE_SIZE - 1); + // 将area的末尾地址与PAGE_SIZE对齐,对齐时向下取整 + // let area_end = (area.base.data() + area.size) & !(MMA::PAGE_SHIFT); + let area_end = (area.base.data() + area.size) & !(MMA::PAGE_SIZE - 1); + + // 如果offset大于area_end,说明当前的物理内存区域已经分配完了,需要跳到下一个物理内存区域 + if offset >= area_end { + continue; + } + + // 如果offset小于area_base ,说明当前的物理内存区域还没有分配过页帧,将offset设置为area_base + if offset < area_base { + offset = area_base; + } else if offset < area_end { + // 将offset对齐到PAGE_SIZE + offset = (offset + (MMA::PAGE_SIZE - 1)) & !(MMA::PAGE_SIZE - 1); + } + // 如果当前offset到area_end的距离大于等于count.data() * PAGE_SIZE,说明当前的物理内存区域足以分配count个页帧 + if offset + count.data() * MMA::PAGE_SIZE <= area_end { + let res_page_phys = offset; + // 将offset增加至分配后的内存 + self.offset = offset + count.data() * MMA::PAGE_SIZE; + + return Some((PhysAddr(res_page_phys), count)); + } + } + return None; + } + + unsafe fn free(&mut self, _address: PhysAddr, _count: PageFrameCount) { + // TODO: 支持释放页帧 + unimplemented!("BumpAllocator::free not implemented"); + } + /// @brief: 获取内存区域页帧的使用情况 + /// @param self + /// @return 页帧的使用情况 + unsafe fn usage(&self) -> PageFrameUsage { + let mut total = 0; + let mut used = 0; + for area in self.areas().iter() { + // 将area的base地址与PAGE_SIZE对齐,对其时向上取整 + let area_base = (area.base.data() + MMA::PAGE_SHIFT) & !(MMA::PAGE_SHIFT); + // 将area的末尾地址与PAGE_SIZE对齐,对其时向下取整 + let area_end = (area.base.data() + area.size) & !(MMA::PAGE_SHIFT); + + total += (area_end - area_base) >> MMA::PAGE_SHIFT; + // 如果offset大于area_end,说明当前物理区域被分配完,都需要加到used中 + if self.offset >= area_end { + used += (area_end - area_base) >> MMA::PAGE_SHIFT; + } else if self.offset < area_base { + // 如果offset小于area_base,说明当前物理区域还没有分配过页帧,都不需要加到used中 + continue; + } else { + used += (self.offset - area_base) >> MMA::PAGE_SHIFT; + } + } + let frame = PageFrameUsage::new(PageFrameCount::new(used), PageFrameCount::new(total)); + return frame; + } +} diff --git a/kernel/src/mm/allocator/kernel_allocator.rs b/kernel/src/mm/allocator/kernel_allocator.rs new file mode 100644 index 00000000..93d66396 --- /dev/null +++ b/kernel/src/mm/allocator/kernel_allocator.rs @@ -0,0 +1,101 @@ +use crate::{ + arch::mm::LockedFrameAllocator, + libs::align::page_align_up, + mm::{MMArch, MemoryManagementArch, VirtAddr}, +}; + +use core::{ + alloc::{AllocError, GlobalAlloc, Layout}, + intrinsics::unlikely, + ptr::NonNull, +}; + +use super::page_frame::{FrameAllocator, PageFrameCount}; + +/// 类kmalloc的分配器应当实现的trait +pub trait LocalAlloc { + unsafe fn local_alloc(&self, layout: Layout) -> *mut u8; + unsafe fn local_alloc_zeroed(&self, layout: Layout) -> *mut u8; + unsafe fn local_dealloc(&self, ptr: *mut u8, layout: Layout); +} + +pub struct KernelAllocator; + +impl KernelAllocator { + unsafe fn alloc_in_buddy(&self, layout: Layout) -> Result, AllocError> { + // 计算需要申请的页数,向上取整 + let count = (page_align_up(layout.size()) / MMArch::PAGE_SIZE).next_power_of_two(); + let page_frame_count = PageFrameCount::new(count); + let (phy_addr, allocated_frame_count) = LockedFrameAllocator + .allocate(page_frame_count) + .ok_or(AllocError)?; + + let virt_addr = unsafe { MMArch::phys_2_virt(phy_addr).ok_or(AllocError)? }; + if unlikely(virt_addr.is_null()) { + return Err(AllocError); + } + + let slice = unsafe { + core::slice::from_raw_parts_mut( + virt_addr.data() as *mut u8, + allocated_frame_count.data() * MMArch::PAGE_SIZE, + ) + }; + return Ok(NonNull::from(slice)); + } + + unsafe fn free_in_buddy(&self, ptr: *mut u8, layout: Layout) { + // 由于buddy分配的页数量是2的幂,因此释放的时候也需要按照2的幂向上取整。 + let count = (page_align_up(layout.size()) / MMArch::PAGE_SIZE).next_power_of_two(); + let page_frame_count = PageFrameCount::new(count); + let phy_addr = MMArch::virt_2_phys(VirtAddr::new(ptr as usize)).unwrap(); + LockedFrameAllocator.free(phy_addr, page_frame_count); + } +} + +/// 为内核SLAB分配器实现LocalAlloc的trait +impl LocalAlloc for KernelAllocator { + unsafe fn local_alloc(&self, layout: Layout) -> *mut u8 { + return self + .alloc_in_buddy(layout) + .map(|x| x.as_mut_ptr() as *mut u8) + .unwrap_or(core::ptr::null_mut() as *mut u8); + } + + unsafe fn local_alloc_zeroed(&self, layout: Layout) -> *mut u8 { + return self + .alloc_in_buddy(layout) + .map(|x| { + let ptr: *mut u8 = x.as_mut_ptr(); + core::ptr::write_bytes(ptr, 0, x.len()); + ptr + }) + .unwrap_or(core::ptr::null_mut() as *mut u8); + } + + unsafe fn local_dealloc(&self, ptr: *mut u8, layout: Layout) { + self.free_in_buddy(ptr, layout); + } +} + +/// 为内核slab分配器实现GlobalAlloc特性 +unsafe impl GlobalAlloc for KernelAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + return self.local_alloc(layout); + // self.local_alloc_zeroed(layout, 0) + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + self.local_alloc_zeroed(layout) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.local_dealloc(ptr, layout); + } +} + +/// 内存分配错误处理函数 +#[alloc_error_handler] +pub fn global_alloc_err_handler(layout: Layout) -> ! { + panic!("global_alloc_error, layout: {:?}", layout); +} diff --git a/kernel/src/mm/allocator/mod.rs b/kernel/src/mm/allocator/mod.rs new file mode 100644 index 00000000..88259f4a --- /dev/null +++ b/kernel/src/mm/allocator/mod.rs @@ -0,0 +1,5 @@ +pub mod buddy; +pub mod bump; +pub mod kernel_allocator; +pub mod page_frame; +pub mod slab; diff --git a/kernel/src/mm/allocator/page_frame.rs b/kernel/src/mm/allocator/page_frame.rs new file mode 100644 index 00000000..9be4538e --- /dev/null +++ b/kernel/src/mm/allocator/page_frame.rs @@ -0,0 +1,338 @@ +use core::{ + intrinsics::unlikely, + ops::{Add, AddAssign, Mul, Sub, SubAssign}, +}; + +use crate::{ + arch::{mm::LockedFrameAllocator, MMArch}, + mm::{MemoryManagementArch, PhysAddr, VirtAddr}, +}; + +/// @brief 物理页帧的表示 +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct PhysPageFrame { + /// 物理页页号 + number: usize, +} + +#[allow(dead_code)] +impl PhysPageFrame { + pub fn new(paddr: PhysAddr) -> Self { + return Self { + number: paddr.data() / MMArch::PAGE_SIZE, + }; + } + + /// @brief 获取当前页对应的物理地址 + pub fn phys_address(&self) -> PhysAddr { + return PhysAddr::new(self.number * MMArch::PAGE_SIZE); + } + + pub fn next_by(&self, n: usize) -> Self { + return Self { + number: self.number + n, + }; + } + + pub fn next(&self) -> Self { + return self.next_by(1); + } + + /// 构造物理页帧的迭代器,范围为[start, end) + pub fn iter_range(start: Self, end: Self) -> PhysPageFrameIter { + return PhysPageFrameIter::new(start, end); + } +} + +/// @brief 物理页帧的迭代器 +#[derive(Debug)] +pub struct PhysPageFrameIter { + current: PhysPageFrame, + /// 结束的物理页帧(不包含) + end: PhysPageFrame, +} + +impl PhysPageFrameIter { + pub fn new(start: PhysPageFrame, end: PhysPageFrame) -> Self { + return Self { + current: start, + end, + }; + } +} + +impl Iterator for PhysPageFrameIter { + type Item = PhysPageFrame; + + fn next(&mut self) -> Option { + if unlikely(self.current == self.end) { + return None; + } + let current = self.current.next(); + return Some(current); + } +} + +/// 虚拟页帧的表示 +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +pub struct VirtPageFrame { + /// 虚拟页页号 + number: usize, +} + +impl VirtPageFrame { + pub fn new(vaddr: VirtAddr) -> Self { + return Self { + number: vaddr.data() / MMArch::PAGE_SIZE, + }; + } + + /// 获取当前虚拟页对应的虚拟地址 + pub fn virt_address(&self) -> VirtAddr { + return VirtAddr::new(self.number * MMArch::PAGE_SIZE); + } + + pub fn next_by(&self, n: usize) -> Self { + return Self { + number: self.number + n, + }; + } + + pub fn next(&self) -> Self { + return self.next_by(1); + } + + /// 构造虚拟页帧的迭代器,范围为[start, end) + pub fn iter_range(start: Self, end: Self) -> VirtPageFrameIter { + return VirtPageFrameIter { + current: start, + end, + }; + } + + pub fn add(&self, n: PageFrameCount) -> Self { + return Self { + number: self.number + n.data(), + }; + } +} + +/// 虚拟页帧的迭代器 +#[derive(Debug)] +pub struct VirtPageFrameIter { + current: VirtPageFrame, + /// 结束的虚拟页帧(不包含) + end: VirtPageFrame, +} + +impl VirtPageFrameIter { + /// @brief 构造虚拟页帧的迭代器,范围为[start, end) + pub fn new(start: VirtPageFrame, end: VirtPageFrame) -> Self { + return Self { + current: start, + end, + }; + } +} + +impl Iterator for VirtPageFrameIter { + type Item = VirtPageFrame; + + fn next(&mut self) -> Option { + if unlikely(self.current == self.end) { + return None; + } + let current: VirtPageFrame = self.current; + self.current = self.current.next_by(1); + return Some(current); + } +} + +/// 页帧使用的数量 +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct PageFrameCount(usize); + +impl PageFrameCount { + // @brief 初始化PageFrameCount + pub const fn new(count: usize) -> Self { + return Self(count); + } + // @brief 获取页帧数量 + pub fn data(&self) -> usize { + return self.0; + } + + /// 计算这一段页帧占用的字节数 + pub fn bytes(&self) -> usize { + return self.0 * MMArch::PAGE_SIZE; + } + + /// 将字节数转换为页帧数量 + /// + /// 如果字节数不是页帧大小的整数倍,则返回None. 否则返回页帧数量 + pub fn from_bytes(bytes: usize) -> Option { + if bytes & MMArch::PAGE_OFFSET_MASK != 0 { + return None; + } else { + return Some(Self(bytes / MMArch::PAGE_SIZE)); + } + } +} + +impl Add for PageFrameCount { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + return Self(self.0 + rhs.0); + } +} + +impl AddAssign for PageFrameCount { + fn add_assign(&mut self, rhs: Self) { + self.0 += rhs.0; + } +} + +impl Sub for PageFrameCount { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + return Self(self.0 - rhs.0); + } +} + +impl SubAssign for PageFrameCount { + fn sub_assign(&mut self, rhs: Self) { + self.0 -= rhs.0; + } +} + +impl Mul for PageFrameCount { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + return Self(self.0 * rhs.0); + } +} + +impl Add for PageFrameCount { + type Output = Self; + + fn add(self, rhs: usize) -> Self::Output { + return Self(self.0 + rhs); + } +} + +impl AddAssign for PageFrameCount { + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +impl Sub for PageFrameCount { + type Output = Self; + + fn sub(self, rhs: usize) -> Self::Output { + return Self(self.0 - rhs); + } +} + +impl SubAssign for PageFrameCount { + fn sub_assign(&mut self, rhs: usize) { + self.0 -= rhs; + } +} + +impl Mul for PageFrameCount { + type Output = Self; + + fn mul(self, rhs: usize) -> Self::Output { + return Self(self.0 * rhs); + } +} + +// 页帧使用情况 +#[derive(Debug)] +pub struct PageFrameUsage { + used: PageFrameCount, + total: PageFrameCount, +} + +#[allow(dead_code)] +impl PageFrameUsage { + /// @brief: 初始化FrameUsage + /// @param PageFrameCount used 已使用的页帧数量 + /// @param PageFrameCount total 总的页帧数量 + pub fn new(used: PageFrameCount, total: PageFrameCount) -> Self { + return Self { used, total }; + } + // @brief 获取已使用的页帧数量 + pub fn used(&self) -> PageFrameCount { + return self.used; + } + // @brief 获取空闲的页帧数量 + pub fn free(&self) -> PageFrameCount { + return PageFrameCount(self.total.0 - self.used.0); + } + // @brief 获取总的页帧数量 + pub fn total(&self) -> PageFrameCount { + return self.total; + } +} + +/// 能够分配页帧的分配器需要实现的trait +pub trait FrameAllocator { + // @brief 分配count个页帧 + unsafe fn allocate(&mut self, count: PageFrameCount) -> Option<(PhysAddr, PageFrameCount)>; + + // @brief 通过地址释放count个页帧 + unsafe fn free(&mut self, address: PhysAddr, count: PageFrameCount); + // @brief 分配一个页帧 + unsafe fn allocate_one(&mut self) -> Option { + return self.allocate(PageFrameCount::new(1)).map(|(addr, _)| addr); + } + // @brief 通过地址释放一个页帧 + unsafe fn free_one(&mut self, address: PhysAddr) { + return self.free(address, PageFrameCount::new(1)); + } + // @brief 获取页帧使用情况 + unsafe fn usage(&self) -> PageFrameUsage; +} + +/// @brief 通过一个 &mut T 的引用来对一个实现了 FrameAllocator trait 的类型进行调用,使代码更加灵活 +impl FrameAllocator for &mut T { + unsafe fn allocate(&mut self, count: PageFrameCount) -> Option<(PhysAddr, PageFrameCount)> { + return T::allocate(self, count); + } + unsafe fn free(&mut self, address: PhysAddr, count: PageFrameCount) { + return T::free(self, address, count); + } + unsafe fn allocate_one(&mut self) -> Option { + return T::allocate_one(self); + } + unsafe fn free_one(&mut self, address: PhysAddr) { + return T::free_one(self, address); + } + unsafe fn usage(&self) -> PageFrameUsage { + return T::usage(self); + } +} + +/// @brief 从全局的页帧分配器中分配连续count个页帧 +/// +/// @param count 请求分配的页帧数量 +pub unsafe fn allocate_page_frames(count: PageFrameCount) -> Option<(PhysAddr, PageFrameCount)> { + let frame = unsafe { LockedFrameAllocator.allocate(count)? }; + return Some(frame); +} + +/// @brief 向全局页帧分配器释放连续count个页帧 +/// +/// @param frame 要释放的第一个页帧 +/// @param count 要释放的页帧数量 (必须是2的n次幂) +pub unsafe fn deallocate_page_frames(frame: PhysPageFrame, count: PageFrameCount) { + unsafe { + LockedFrameAllocator.free(frame.phys_address(), count); + } +} diff --git a/kernel/src/mm/allocator/slab.rs b/kernel/src/mm/allocator/slab.rs new file mode 100644 index 00000000..7b461b0b --- /dev/null +++ b/kernel/src/mm/allocator/slab.rs @@ -0,0 +1,123 @@ +//! 当前slab分配器暂时不使用,等待后续完善后合并主线 +#![allow(dead_code)] + +use core::alloc::Layout; + +// 定义Slab,用来存放空闲块 +pub struct Slab { + block_size: usize, + free_block_list: FreeBlockList, +} + +impl Slab { + /// @brief: 初始化一个slab + /// @param {usize} start_addr + /// @param {usize} slab_size + /// @param {usize} block_size + pub unsafe fn new(start_addr: usize, slab_size: usize, block_size: usize) -> Slab { + let blocks_num = slab_size / block_size; + return Slab { + block_size: block_size, + free_block_list: FreeBlockList::new(start_addr, block_size, blocks_num), + }; + } + + /// @brief: 获取slab中可用的block数 + pub fn used_blocks(&self) -> usize { + return self.free_block_list.len(); + } + + /// @brief: 扩大free_block_list + /// @param {*} mut + /// @param {usize} start_addr + /// @param {usize} slab_size + pub fn grow(&mut self, start_addr: usize, slab_size: usize) { + let num_of_blocks = slab_size / self.block_size; + let mut block_list = + unsafe { FreeBlockList::new(start_addr, self.block_size, num_of_blocks) }; + // 将新链表接到原链表的后面 + while let Some(block) = block_list.pop() { + self.free_block_list.push(block); + } + } + /// @brief: 从slab中分配一个block + /// @return 分配的内存地址 + pub fn allocate(&mut self, _layout: Layout) -> Option<*mut u8> { + match self.free_block_list.pop() { + Some(block) => return Some(block.addr() as *mut u8), + None => return None, + } + } + /// @brief: 将block归还给slab + pub fn free(&mut self, ptr: *mut u8) { + let ptr = ptr as *mut FreeBlock; + unsafe { + self.free_block_list.push(&mut *ptr); + } + } +} +/// slab中的空闲块 +struct FreeBlockList { + len: usize, + head: Option<&'static mut FreeBlock>, +} + +impl FreeBlockList { + unsafe fn new(start_addr: usize, block_size: usize, num_of_blocks: usize) -> FreeBlockList { + let mut new_list = FreeBlockList::new_empty(); + for i in (0..num_of_blocks).rev() { + // 从后往前分配,避免内存碎片 + let new_block = (start_addr + i * block_size) as *mut FreeBlock; + new_list.push(&mut *new_block); + } + return new_list; + } + + fn new_empty() -> FreeBlockList { + return FreeBlockList { len: 0, head: None }; + } + + fn len(&self) -> usize { + return self.len; + } + + /// @brief: 将空闲块从链表中弹出 + fn pop(&mut self) -> Option<&'static mut FreeBlock> { + // 从链表中弹出一个空闲块 + let block = self.head.take().map(|node| { + self.head = node.next.take(); + self.len -= 1; + node + }); + return block; + } + + /// @brief: 将空闲块压入链表 + fn push(&mut self, free_block: &'static mut FreeBlock) { + free_block.next = self.head.take(); + self.len += 1; + self.head = Some(free_block); + } + + fn is_empty(&self) -> bool { + return self.head.is_none(); + } +} + +impl Drop for FreeBlockList { + fn drop(&mut self) { + while let Some(_) = self.pop() {} + } +} + +struct FreeBlock { + next: Option<&'static mut FreeBlock>, +} + +impl FreeBlock { + /// @brief: 获取FreeBlock的地址 + /// @return {*} + fn addr(&self) -> usize { + return self as *const _ as usize; + } +} diff --git a/kernel/src/mm/c_adapter.rs b/kernel/src/mm/c_adapter.rs new file mode 100644 index 00000000..de98900b --- /dev/null +++ b/kernel/src/mm/c_adapter.rs @@ -0,0 +1,135 @@ +//! 这是暴露给C的接口,用于在C语言中使用Rust的内存分配器。 + +use core::intrinsics::unlikely; + +use alloc::vec::Vec; +use hashbrown::HashMap; + +use crate::{ + arch::mm::LowAddressRemapping, + include::bindings::bindings::{gfp_t, PAGE_U_S}, + kerror, + libs::{align::page_align_up, spinlock::SpinLock}, + mm::MMArch, + syscall::SystemError, +}; + +use super::{ + allocator::page_frame::PageFrameCount, kernel_mapper::KernelMapper, no_init::pseudo_map_phys, + page::PageFlags, MemoryManagementArch, PhysAddr, VirtAddr, +}; + +lazy_static! { + // 用于记录内核分配给C的空间信息 + static ref C_ALLOCATION_MAP: SpinLock> = SpinLock::new(HashMap::new()); +} + +/// [EXTERN TO C] Use pseudo mapper to map physical memory to virtual memory. +#[no_mangle] +pub unsafe extern "C" fn rs_pseudo_map_phys(vaddr: usize, paddr: usize, size: usize) { + let vaddr = VirtAddr::new(vaddr); + let paddr = PhysAddr::new(paddr); + let count = PageFrameCount::new(page_align_up(size) / MMArch::PAGE_SIZE); + pseudo_map_phys(vaddr, paddr, count); +} + +/// [EXTERN TO C] Use kernel mapper to map physical memory to virtual memory. +#[no_mangle] +pub unsafe extern "C" fn rs_map_phys(vaddr: usize, paddr: usize, size: usize, flags: usize) { + let mut vaddr = VirtAddr::new(vaddr); + let mut paddr = PhysAddr::new(paddr); + let count = PageFrameCount::new(page_align_up(size) / MMArch::PAGE_SIZE); + // kdebug!("rs_map_phys: vaddr: {vaddr:?}, paddr: {paddr:?}, count: {count:?}, flags: {flags:?}"); + + let mut page_flags: PageFlags = PageFlags::new().set_execute(true).set_write(true); + if flags & PAGE_U_S as usize != 0 { + page_flags = page_flags.set_user(true); + } + + let mut kernel_mapper = KernelMapper::lock(); + let mut kernel_mapper = kernel_mapper.as_mut(); + assert!(kernel_mapper.is_some()); + for _i in 0..count.data() { + let flusher = kernel_mapper + .as_mut() + .unwrap() + .map_phys(vaddr, paddr, page_flags) + .unwrap(); + + flusher.flush(); + + vaddr += MMArch::PAGE_SIZE; + paddr += MMArch::PAGE_SIZE; + } +} + +#[no_mangle] +pub unsafe extern "C" fn kzalloc(size: usize, _gfp: gfp_t) -> usize { + // kdebug!("kzalloc: size: {size}"); + return do_kmalloc(size, true); +} + +#[no_mangle] +pub unsafe extern "C" fn kmalloc(size: usize, _gfp: gfp_t) -> usize { + // kdebug!("kmalloc: size: {size}"); + // 由于C代码不规范,因此都全部清空 + return do_kmalloc(size, true); +} + +fn do_kmalloc(size: usize, zero: bool) -> usize { + let space: Vec = if zero { + vec![0u8; size] + } else { + let mut v = Vec::with_capacity(size); + unsafe { + v.set_len(size); + } + v + }; + + assert!(space.len() == size); + let (ptr, len, cap) = space.into_raw_parts(); + if !ptr.is_null() { + let vaddr = VirtAddr::new(ptr as usize); + let len = len as usize; + let cap = cap as usize; + let mut guard = C_ALLOCATION_MAP.lock(); + if unlikely(guard.contains_key(&vaddr)) { + drop(guard); + unsafe { + drop(Vec::from_raw_parts(vaddr.data() as *mut u8, len, cap)); + } + panic!( + "do_kmalloc: vaddr {:?} already exists in C Allocation Map, query size: {size}, zero: {zero}", + vaddr + ); + } + // 插入到C Allocation Map中 + guard.insert(vaddr, (vaddr, len, cap)); + return vaddr.data(); + } else { + return SystemError::ENOMEM.to_posix_errno() as i64 as usize; + } +} + +#[no_mangle] +pub unsafe extern "C" fn kfree(vaddr: usize) -> usize { + let vaddr = VirtAddr::new(vaddr); + let mut guard = C_ALLOCATION_MAP.lock(); + let p = guard.remove(&vaddr); + drop(guard); + + if p.is_none() { + kerror!("kfree: vaddr {:?} not found in C Allocation Map", vaddr); + return SystemError::EINVAL.to_posix_errno() as i64 as usize; + } + let (vaddr, len, cap) = p.unwrap(); + drop(Vec::from_raw_parts(vaddr.data() as *mut u8, len, cap)); + return 0; +} + +#[no_mangle] +pub unsafe extern "C" fn rs_unmap_at_low_addr() -> usize { + LowAddressRemapping::unmap_at_low_address(true); + return 0; +} diff --git a/kernel/src/mm/internal.h b/kernel/src/mm/internal.h deleted file mode 100644 index f50902d2..00000000 --- a/kernel/src/mm/internal.h +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -#include "mm.h" - - -// 当vma被成功合并后的返回值 -#define __VMA_MERGED 1 - -/** - * @brief 将vma结构体插入mm_struct的链表之中 - * - * @param mm 内存空间分布结构体 - * @param vma 待插入的VMA结构体 - * @param prev 链表的前一个结点 - */ -void __vma_link_list(struct mm_struct *mm, struct vm_area_struct *vma, struct vm_area_struct *prev); - -/** - * @brief 将vma给定结构体从vma链表的结点之中删除 - * - * @param mm 内存空间分布结构体 - * @param vma 待插入的VMA结构体 - */ -void __vma_unlink_list(struct mm_struct *mm, struct vm_area_struct *vma); - -/** - * @brief 获取指定虚拟地址处映射的物理地址 - * - * @param mm 内存空间分布结构体 - * @param vaddr 虚拟地址 - * @return uint64_t 已映射的物理地址 - */ -uint64_t __mm_get_paddr(struct mm_struct *mm, uint64_t vaddr); - -/** - * @brief 创建anon_vma,并将其与页面结构体进行绑定 - * 若提供的页面结构体指针为NULL,则只创建,不绑定 - * - * @param page 页面结构体的指针 - * @param lock_page 是否将页面结构体加锁 - * @return struct anon_vma_t* 创建好的anon_vma - */ -struct anon_vma_t *__anon_vma_create_alloc(struct Page *page, bool lock_page); - -/** - * @brief 释放anon vma结构体 - * - * @param anon_vma 待释放的anon_vma结构体 - * @return int 返回码 - */ -int __anon_vma_free(struct anon_vma_t *anon_vma); - -/** - * @brief 将指定的vma加入到anon_vma的管理范围之中 - * - * @param anon_vma 页面的anon_vma - * @param vma 待加入的vma - * @return int 返回码 - */ -int __anon_vma_add(struct anon_vma_t *anon_vma, struct vm_area_struct *vma); - -/** - * @brief 从anon_vma的管理范围中删除指定的vma - * (在进入这个函数之前,应该要对anon_vma加锁) - * @param vma 将要取消对应的anon_vma管理的vma结构体 - * @return int 返回码 - */ -int __anon_vma_del(struct vm_area_struct *vma); - -/** - * @brief 创建mmio对应的页结构体 - * - * @param paddr 物理地址 - * @return struct Page* 创建成功的page - */ -struct Page* __create_mmio_page_struct(uint64_t paddr); - -// 判断给定的两个值是否跨越了2M边界 -#define CROSS_2M_BOUND(val1, val2) ((val1 & PAGE_2M_MASK) != (val2 & PAGE_2M_MASK)) \ No newline at end of file diff --git a/kernel/src/mm/kernel_mapper.rs b/kernel/src/mm/kernel_mapper.rs new file mode 100644 index 00000000..2aa525a7 --- /dev/null +++ b/kernel/src/mm/kernel_mapper.rs @@ -0,0 +1,145 @@ +use super::{page::PageFlags, PageTableKind, PhysAddr, VirtAddr}; +use crate::{ + arch::{ + asm::irqflags::{local_irq_restore, local_irq_save}, + mm::{LockedFrameAllocator, PageMapper}, + }, + libs::align::page_align_up, + mm::allocator::page_frame::PageFrameCount, + mm::{MMArch, MemoryManagementArch}, + smp::core::smp_get_processor_id, + syscall::SystemError, +}; +use core::{ + ops::Deref, + sync::atomic::{compiler_fence, AtomicUsize, Ordering}, +}; + +/// 标志当前没有处理器持有内核映射器的锁 +/// 之所以需要这个标志,是因为AtomicUsize::new(0)会把0当作一个处理器的id +const KERNEL_MAPPER_NO_PROCESSOR: usize = !0; +/// 当前持有内核映射器锁的处理器 +static KERNEL_MAPPER_LOCK_OWNER: AtomicUsize = AtomicUsize::new(KERNEL_MAPPER_NO_PROCESSOR); +/// 内核映射器的锁计数器 +static KERNEL_MAPPER_LOCK_COUNT: AtomicUsize = AtomicUsize::new(0); + +pub struct KernelMapper { + /// 内核空间映射器 + mapper: PageMapper, + /// 标记当前映射器是否为只读 + readonly: bool, +} + +impl KernelMapper { + fn lock_cpu(cpuid: usize, mapper: PageMapper) -> Self { + loop { + match KERNEL_MAPPER_LOCK_OWNER.compare_exchange_weak( + KERNEL_MAPPER_NO_PROCESSOR, + cpuid, + Ordering::Acquire, + Ordering::Relaxed, + ) { + Ok(_) => break, + // 当前处理器已经持有了锁 + Err(id) if id == cpuid => break, + // either CAS failed, or some other hardware thread holds the lock + Err(_) => core::hint::spin_loop(), + } + } + + let prev_count = KERNEL_MAPPER_LOCK_COUNT.fetch_add(1, Ordering::Relaxed); + compiler_fence(Ordering::Acquire); + + // 本地核心已经持有过锁,因此标记当前加锁获得的映射器为只读 + let readonly = prev_count > 0; + + return Self { mapper, readonly }; + } + + /// @brief 锁定内核映射器, 并返回一个内核映射器对象 + #[inline(always)] + pub fn lock() -> Self { + let cpuid = smp_get_processor_id() as usize; + let mapper = unsafe { PageMapper::current(PageTableKind::Kernel, LockedFrameAllocator) }; + return Self::lock_cpu(cpuid, mapper); + } + + /// @brief 获取内核映射器的page mapper的可变引用。如果当前映射器为只读,则返回 None + #[inline(always)] + pub fn as_mut(&mut self) -> Option<&mut PageMapper> { + if self.readonly { + return None; + } else { + return Some(&mut self.mapper); + } + } + + /// @brief 获取内核映射器的page mapper的不可变引用 + #[inline(always)] + pub fn as_ref(&self) -> &PageMapper { + return &self.mapper; + } + + /// 映射一段物理地址到指定的虚拟地址。 + /// + /// ## 参数 + /// + /// - `vaddr`: 要映射的虚拟地址 + /// - `paddr`: 要映射的物理地址 + /// - `size`: 要映射的大小(字节,必须是页大小的整数倍,否则会向上取整) + /// - `flags`: 页面标志 + /// - `flush`: 是否刷新TLB + /// + /// ## 返回 + /// + /// - 成功:返回Ok(()) + /// - 失败: 如果当前映射器为只读,则返回EAGAIN_OR_EWOULDBLOCK + pub unsafe fn map_phys_with_size( + &mut self, + mut vaddr: VirtAddr, + mut paddr: PhysAddr, + size: usize, + flags: PageFlags, + flush: bool, + ) -> Result<(), SystemError> { + if self.readonly { + return Err(SystemError::EAGAIN_OR_EWOULDBLOCK); + } + + let count = PageFrameCount::new(page_align_up(size) / MMArch::PAGE_SIZE); + // kdebug!("kernel mapper: map_phys: vaddr: {vaddr:?}, paddr: {paddr:?}, count: {count:?}, flags: {flags:?}"); + + for _ in 0..count.data() { + let flusher = self.mapper.map_phys(vaddr, paddr, flags).unwrap(); + + if flush { + flusher.flush(); + } + + vaddr += MMArch::PAGE_SIZE; + paddr += MMArch::PAGE_SIZE; + } + return Ok(()); + } +} + +impl Drop for KernelMapper { + fn drop(&mut self) { + // 为了防止fetch_sub和store之间,由于中断,导致store错误清除了owner,导致错误,因此需要关中断。 + let flags = local_irq_save(); + let prev_count = KERNEL_MAPPER_LOCK_COUNT.fetch_sub(1, Ordering::Relaxed); + if prev_count == 1 { + KERNEL_MAPPER_LOCK_OWNER.store(KERNEL_MAPPER_NO_PROCESSOR, Ordering::Release); + } + local_irq_restore(flags); + compiler_fence(Ordering::Release); + } +} + +impl Deref for KernelMapper { + type Target = PageMapper; + + fn deref(&self) -> &Self::Target { + return self.as_ref(); + } +} diff --git a/kernel/src/mm/mm-stat.c b/kernel/src/mm/mm-stat.c deleted file mode 100644 index 43856b65..00000000 --- a/kernel/src/mm/mm-stat.c +++ /dev/null @@ -1,196 +0,0 @@ -/** - * @file mm-stat.c - * @author longjin(longjin@RinGoTek.cn) - * @brief 查询内存信息 - * @version 0.1 - * @date 2022-08-06 - * - * @copyright Copyright (c) 2022 - * - */ - -#include "mm.h" -#include "slab.h" -#include -#include - -extern const struct slab kmalloc_cache_group[16]; - -static int __empty_2m_pages(int zone); -static int __count_in_using_2m_pages(int zone); -static uint64_t __count_kmalloc_free(); -static uint64_t __count_kmalloc_using(); -static uint64_t __count_kmalloc_total(); -uint64_t sys_mm_stat(struct pt_regs *regs); - -/** - * @brief 获取指定zone中的空闲2M页的数量 - * - * @param zone 内存zone号 - * @return int 空闲2M页数量 - */ -static int __count_empty_2m_pages(int zone) -{ - int zone_start = 0, zone_end = 0; - - uint64_t attr = 0; - switch (zone) - { - case ZONE_DMA: - // DMA区域 - zone_start = 0; - zone_end = ZONE_DMA_INDEX; - attr |= PAGE_PGT_MAPPED; - break; - case ZONE_NORMAL: - zone_start = ZONE_DMA_INDEX; - zone_end = ZONE_NORMAL_INDEX; - attr |= PAGE_PGT_MAPPED; - break; - case ZONE_UNMAPPED_IN_PGT: - zone_start = ZONE_NORMAL_INDEX; - zone_end = ZONE_UNMAPPED_INDEX; - attr = 0; - break; - default: - kerror("In __count_empty_2m_pages: param: zone invalid."); - // 返回错误码 - return -EINVAL; - break; - } - - uint64_t result = 0; - for (int i = zone_start; i <= zone_end; ++i) - { - result += (memory_management_struct.zones_struct + i)->count_pages_free; - } - return result; -} - -/** - * @brief 获取指定zone中的正在使用的2M页的数量 - * - * @param zone 内存zone号 - * @return int 空闲2M页数量 - */ -static int __count_in_using_2m_pages(int zone) -{ - int zone_start = 0, zone_end = 0; - - uint64_t attr = 0; - switch (zone) - { - case ZONE_DMA: - // DMA区域 - zone_start = 0; - zone_end = ZONE_DMA_INDEX; - attr |= PAGE_PGT_MAPPED; - break; - case ZONE_NORMAL: - zone_start = ZONE_DMA_INDEX; - zone_end = ZONE_NORMAL_INDEX; - attr |= PAGE_PGT_MAPPED; - break; - case ZONE_UNMAPPED_IN_PGT: - zone_start = ZONE_NORMAL_INDEX; - zone_end = ZONE_UNMAPPED_INDEX; - attr = 0; - break; - default: - kerror("In __count_in_using_2m_pages: param: zone invalid."); - // 返回错误码 - return -EINVAL; - break; - } - - uint64_t result = 0; - for (int i = zone_start; i <= zone_end; ++i) - { - result += (memory_management_struct.zones_struct + i)->count_pages_using; - } - return result; -} - -/** - * @brief 计算kmalloc缓冲区中的空闲内存 - * - * @return uint64_t 空闲内存(字节) - */ -static uint64_t __count_kmalloc_free() -{ - uint64_t result = 0; - for (int i = 0; i < sizeof(kmalloc_cache_group) / sizeof(struct slab); ++i) - { - result += kmalloc_cache_group[i].size * kmalloc_cache_group[i].count_total_free; - } - return result; -} - -/** - * @brief 计算kmalloc缓冲区中已使用的内存 - * - * @return uint64_t 已使用的内存(字节) - */ -static uint64_t __count_kmalloc_using() -{ - uint64_t result = 0; - for (int i = 0; i < sizeof(kmalloc_cache_group) / sizeof(struct slab); ++i) - { - result += kmalloc_cache_group[i].size * kmalloc_cache_group[i].count_total_using; - } - return result; -} - -/** - * @brief 计算kmalloc缓冲区中总共占用的内存 - * - * @return uint64_t 缓冲区占用的内存(字节) - */ -static uint64_t __count_kmalloc_total() -{ - uint64_t result = 0; - for (int i = 0; i < sizeof(kmalloc_cache_group) / sizeof(struct slab); ++i) - { - result += kmalloc_cache_group[i].size * - (kmalloc_cache_group[i].count_total_free + kmalloc_cache_group[i].count_total_using); - } - return result; -} - -/** - * @brief 获取系统当前的内存信息(未上锁,不一定精准) - * - * @return struct mm_stat_t 内存信息结构体 - */ -struct mm_stat_t mm_stat() -{ - struct mm_stat_t tmp = {0}; - // 统计物理页的信息 - tmp.used = __count_in_using_2m_pages(ZONE_NORMAL) * PAGE_2M_SIZE; - tmp.free = __count_empty_2m_pages(ZONE_NORMAL) * PAGE_2M_SIZE; - tmp.total = tmp.used + tmp.free; - tmp.shared = 0; - // 统计kmalloc slab中的信息 - tmp.cache_free = __count_kmalloc_free(); - tmp.cache_used = __count_kmalloc_using(); - tmp.available = tmp.free + tmp.cache_free; - return tmp; -} - -/** - * @brief 获取内存信息的系统调用 - * - * @param r8 返回的内存信息结构体的地址 - * @return uint64_t - */ -uint64_t sys_do_mstat(struct mm_stat_t *dst, bool from_user) -{ - if (dst == NULL) - return -EFAULT; - struct mm_stat_t stat = mm_stat(); - if (from_user) - copy_to_user((void *)dst, &stat, sizeof(struct mm_stat_t)); - else - memcpy((void *)dst, &stat, sizeof(struct mm_stat_t)); - return 0; -} \ No newline at end of file diff --git a/kernel/src/mm/mm-types.h b/kernel/src/mm/mm-types.h index 96891944..6799086b 100644 --- a/kernel/src/mm/mm-types.h +++ b/kernel/src/mm/mm-types.h @@ -4,179 +4,4 @@ #include #include -struct mm_struct; -struct anon_vma_t; typedef uint64_t vm_flags_t; - -/** - * @brief 内存页表结构体 - * - */ -typedef struct -{ - unsigned long pml4t; -} pml4t_t; - -typedef struct -{ - unsigned long pdpt; -} pdpt_t; - -typedef struct -{ - unsigned long pdt; -} pdt_t; - -typedef struct -{ - unsigned long pt; -} pt_t; - -// Address Range Descriptor Structure 地址范围描述符 -struct ARDS -{ - ul BaseAddr; // 基地址 - ul Length; // 内存长度 以字节为单位 - unsigned int type; // 本段内存的类型 - // type=1 表示可以被操作系统使用 - // type=2 ARR - 内存使用中或被保留,操作系统不能使用 - // 其他 未定义,操作系统需要将其视为ARR -} __attribute__((packed)); // 修饰该结构体不会生成对齐空间,改用紧凑格式 - -struct memory_desc -{ - - struct ARDS e820[32]; // 物理内存段结构数组 - ul len_e820; // 物理内存段长度 - - ul *bmp; // 物理空间页映射位图 - ul bmp_len; // bmp的长度 - ul bits_size; // 物理地址空间页数量 - - struct Page *pages_struct; - ul count_pages; // struct page结构体的总数 - ul pages_struct_len; // pages_struct链表的长度 - - struct Zone *zones_struct; - ul count_zones; // zone结构体的数量 - ul zones_struct_len; // zones_struct列表的长度 - - ul kernel_code_start, kernel_code_end; // 内核程序代码段起始地址、结束地址 - ul kernel_data_end, rodata_end; // 内核程序数据段结束地址、 内核程序只读段结束地址 - uint64_t start_brk; // 堆地址的起始位置 - - ul end_of_struct; // 内存页管理结构的结束地址 -}; - -struct Zone -{ - // 指向内存页的指针 - struct Page *pages_group; - ul count_pages; // 本区域的struct page结构体总数 - - // 本内存区域的起始、结束的页对齐地址 - ul zone_addr_start; - ul zone_addr_end; - ul zone_length; // 区域长度 - - // 本区域空间的属性 - ul attr; - - struct memory_desc *gmd_struct; - - // 本区域正在使用中和空闲中的物理页面数量 - ul count_pages_using; - ul count_pages_free; - - // 物理页被引用次数 - ul total_pages_link; -}; - -struct Page -{ - // 本页所属的内存域结构体 - struct Zone *zone; - // 本页对应的物理地址 - ul addr_phys; - // 页面属性 - ul attr; - // 页面被引用的次数 - ul ref_counts; - // 本页的创建时间 - ul age; - - struct anon_vma_t *anon_vma; // 本页对应的anon_vma - - spinlock_t op_lock; // 页面操作锁 - -}; - -/** - * @brief 虚拟内存区域(VMA)结构体 - * - */ -struct vm_area_struct -{ - struct vm_area_struct *vm_prev, *vm_next; - - // 虚拟内存区域的范围是一个左闭右开的区间:[vm_start, vm_end) - uint64_t vm_start; // 区域的起始地址 - uint64_t vm_end; // 区域的结束地址 - struct mm_struct *vm_mm; // 虚拟内存区域对应的mm结构体 - vm_flags_t vm_flags; // 虚拟内存区域的标志位, 具体可选值请见mm.h - - - struct List anon_vma_list; // anon_vma的链表结点 - struct anon_vma_t * anon_vma; // 属于的anon_vma - - struct vm_operations_t *vm_ops; // 操作方法 - atomic_t ref_count; // 引用计数 - pgoff_t page_offset; // 起始地址在当前VMA所占的2M物理页中的偏移量 - void *private_data; -}; - -/** - * @brief 内存空间分布结构体 - * 包含了进程内存空间分布的信息 - */ -struct mm_struct -{ - pml4t_t *pgd; // 内存页表指针 - struct vm_area_struct *vmas; // VMA列表 - // 代码段空间 - uint64_t code_addr_start, code_addr_end; - // 数据段空间 - uint64_t data_addr_start, data_addr_end; - // 只读数据段空间 - uint64_t rodata_addr_start, rodata_addr_end; - // BSS段的空间 - uint64_t bss_start, bss_end; - // 动态内存分配区(堆区域) - uint64_t brk_start, brk_end; - // 应用层栈基地址 - uint64_t stack_start; -}; - -/** - * @brief 匿名vma对象的结构体 - * - * anon_vma与每个内存页结构体进行一对一绑定 - * anon_vma也连接着一切使用到该内存页的vma,当发生页面换出时,应当更新与该page相关的所有vma在页表中的映射信息。 - */ -struct anon_vma_t -{ - // anon vma的操作信号量 - semaphore_t sem; - - /** - * 记录当前有多少个vma与该anon_vma关联,当vma被释放时, - * 应当检查这个值。当该值为0时,应当释放anon_vma结构体 - */ - atomic_t ref_count; - - // todo: 把下面的循环链表更换成红黑树 - // 与当前anon_vma相关的vma的列表 - struct List vma_list; - // 当前anon vma对应的page - struct Page* page; -}; \ No newline at end of file diff --git a/kernel/src/mm/mm.c b/kernel/src/mm/mm.c deleted file mode 100644 index b4452266..00000000 --- a/kernel/src/mm/mm.c +++ /dev/null @@ -1,686 +0,0 @@ -#include "mm.h" -#include "mm-types.h" -#include "mmio.h" -#include "slab.h" -#include -#include -#include -#include -#include -#include -#include - -uint64_t mm_Total_Memory = 0; -uint64_t mm_total_2M_pages = 0; -struct mm_struct initial_mm = {0}; - -struct memory_desc memory_management_struct = {{0}, 0}; - -/** - * @brief 从页表中获取pdt页表项的内容 - * - * @param proc_page_table_addr 页表的地址 - * @param is_phys 页表地址是否为物理地址 - * @param virt_addr_start 要清除的虚拟地址的起始地址 - * @param length 要清除的区域的长度 - * @param clear 是否清除标志位 - */ -uint64_t mm_get_PDE(ul proc_page_table_addr, bool is_phys, ul virt_addr, bool clear); - -/** - * @brief 检查页表是否存在不为0的页表项 - * - * @param ptr 页表基指针 - * @return int8_t 存在 -> 1 - * 不存在 -> 0 - */ -int8_t mm_check_page_table(uint64_t *ptr) -{ - for (int i = 0; i < 512; ++i, ++ptr) - { - if (*ptr != 0) - return 1; - } - return 0; -} - -void mm_init() -{ - kinfo("Initializing memory management unit..."); - // 设置内核程序不同部分的起止地址 - memory_management_struct.kernel_code_start = (ul)&_text; - memory_management_struct.kernel_code_end = (ul)&_etext; - memory_management_struct.kernel_data_end = (ul)&_edata; - memory_management_struct.rodata_end = (ul)&_erodata; - memory_management_struct.start_brk = (ul)&_end; - - struct multiboot_mmap_entry_t mb2_mem_info[512]; - int count; - - multiboot2_iter(multiboot2_get_memory, mb2_mem_info, &count); - io_mfence(); - for (int i = 0; i < count; ++i) - { - io_mfence(); - // 可用的内存 - if (mb2_mem_info->type == 1) - mm_Total_Memory += mb2_mem_info->len; - - // kdebug("[i=%d] mb2_mem_info[i].type=%d, mb2_mem_info[i].addr=%#018lx", i, mb2_mem_info[i].type, mb2_mem_info[i].addr); - // 保存信息到mms - memory_management_struct.e820[i].BaseAddr = mb2_mem_info[i].addr; - memory_management_struct.e820[i].Length = mb2_mem_info[i].len; - memory_management_struct.e820[i].type = mb2_mem_info[i].type; - memory_management_struct.len_e820 = i; - - // 脏数据 - if (mb2_mem_info[i].type > 4 || mb2_mem_info[i].len == 0 || mb2_mem_info[i].type < 1) - break; - } - printk("[ INFO ] Total amounts of RAM : %ld bytes\n", mm_Total_Memory); - - // 计算有效内存页数 - io_mfence(); - for (int i = 0; i < memory_management_struct.len_e820; ++i) - { - if (memory_management_struct.e820[i].type != 1) - continue; - io_mfence(); - // 将内存段的起始物理地址按照2M进行对齐 - ul addr_start = PAGE_2M_ALIGN(memory_management_struct.e820[i].BaseAddr); - // 将内存段的终止物理地址的低2M区域清空,以实现对齐 - ul addr_end = ((memory_management_struct.e820[i].BaseAddr + memory_management_struct.e820[i].Length) & PAGE_2M_MASK); - - // 内存段不可用 - if (addr_end <= addr_start) - continue; - io_mfence(); - mm_total_2M_pages += ((addr_end - addr_start) >> PAGE_2M_SHIFT); - } - kinfo("Total amounts of 2M pages : %ld.", mm_total_2M_pages); - - // 物理地址空间的最大地址(包含了物理内存、内存空洞、ROM等) - ul max_addr = memory_management_struct.e820[memory_management_struct.len_e820].BaseAddr + memory_management_struct.e820[memory_management_struct.len_e820].Length; - // 初始化mms的bitmap - // bmp的指针指向截止位置的4k对齐的上边界(防止修改了别的数据) - io_mfence(); - memory_management_struct.bmp = (unsigned long *)((memory_management_struct.start_brk + PAGE_4K_SIZE - 1) & PAGE_4K_MASK); - memory_management_struct.bits_size = max_addr >> PAGE_2M_SHIFT; // 物理地址空间的最大页面数 - memory_management_struct.bmp_len = (((unsigned long)(max_addr >> PAGE_2M_SHIFT) + sizeof(unsigned long) * 8 - 1) / 8) & (~(sizeof(unsigned long) - 1)); // bmp由多少个unsigned long变量组成 - io_mfence(); - - // 初始化bitmap, 先将整个bmp空间全部置位。稍后再将可用物理内存页复位。 - memset(memory_management_struct.bmp, 0xff, memory_management_struct.bmp_len); - io_mfence(); - // 初始化内存页结构 - // 将页结构映射于bmp之后 - memory_management_struct.pages_struct = (struct Page *)(((unsigned long)memory_management_struct.bmp + memory_management_struct.bmp_len + PAGE_4K_SIZE - 1) & PAGE_4K_MASK); - - memory_management_struct.count_pages = max_addr >> PAGE_2M_SHIFT; - memory_management_struct.pages_struct_len = ((max_addr >> PAGE_2M_SHIFT) * sizeof(struct Page) + sizeof(long) - 1) & (~(sizeof(long) - 1)); - // 将pages_struct全部清空,以备后续初始化 - memset(memory_management_struct.pages_struct, 0x00, memory_management_struct.pages_struct_len); // init pages memory - - io_mfence(); - // 初始化内存区域 - memory_management_struct.zones_struct = (struct Zone *)(((ul)memory_management_struct.pages_struct + memory_management_struct.pages_struct_len + PAGE_4K_SIZE - 1) & PAGE_4K_MASK); - io_mfence(); - // 由于暂时无法计算zone结构体的数量,因此先将其设为0 - memory_management_struct.count_zones = 0; - io_mfence(); - // zones-struct 成员变量暂时按照5个来计算 - memory_management_struct.zones_struct_len = (10 * sizeof(struct Zone) + sizeof(ul) - 1) & (~(sizeof(ul) - 1)); - io_mfence(); - memset(memory_management_struct.zones_struct, 0x00, memory_management_struct.zones_struct_len); - - // ==== 遍历e820数组,完成成员变量初始化工作 === - - for (int i = 0; i < memory_management_struct.len_e820; ++i) - { - io_mfence(); - if (memory_management_struct.e820[i].type != 1) // 不是操作系统可以使用的物理内存 - continue; - ul addr_start = PAGE_2M_ALIGN(memory_management_struct.e820[i].BaseAddr); - ul addr_end = (memory_management_struct.e820[i].BaseAddr + memory_management_struct.e820[i].Length) & PAGE_2M_MASK; - - if (addr_end <= addr_start) - continue; - - // zone init - struct Zone *z = memory_management_struct.zones_struct + memory_management_struct.count_zones; - ++memory_management_struct.count_zones; - - z->zone_addr_start = addr_start; - z->zone_addr_end = addr_end; - z->zone_length = addr_end - addr_start; - - z->count_pages_using = 0; - z->count_pages_free = (addr_end - addr_start) >> PAGE_2M_SHIFT; - z->total_pages_link = 0; - - z->attr = 0; - z->gmd_struct = &memory_management_struct; - - z->count_pages = (addr_end - addr_start) >> PAGE_2M_SHIFT; - z->pages_group = (struct Page *)(memory_management_struct.pages_struct + (addr_start >> PAGE_2M_SHIFT)); - - // 初始化页 - struct Page *p = z->pages_group; - - for (int j = 0; j < z->count_pages; ++j, ++p) - { - p->zone = z; - p->addr_phys = addr_start + PAGE_2M_SIZE * j; - p->attr = 0; - - p->ref_counts = 0; - p->age = 0; - - // 将bmp中对应的位 复位 - *(memory_management_struct.bmp + ((p->addr_phys >> PAGE_2M_SHIFT) >> 6)) ^= (1UL << ((p->addr_phys >> PAGE_2M_SHIFT) % 64)); - } - } - - // 初始化0~2MB的物理页 - // 由于这个区间的内存由多个内存段组成,因此不会被以上代码初始化,需要我们手动配置page[0]。 - io_mfence(); - memory_management_struct.pages_struct->zone = memory_management_struct.zones_struct; - memory_management_struct.pages_struct->addr_phys = 0UL; - set_page_attr(memory_management_struct.pages_struct, PAGE_PGT_MAPPED | PAGE_KERNEL_INIT | PAGE_KERNEL); - memory_management_struct.pages_struct->ref_counts = 1; - memory_management_struct.pages_struct->age = 0; - // 将第0页的标志位给置上 - //*(memory_management_struct.bmp) |= 1UL; - - // 计算zone结构体的总长度(按照64位对齐) - memory_management_struct.zones_struct_len = (memory_management_struct.count_zones * sizeof(struct Zone) + sizeof(ul) - 1) & (~(sizeof(ul) - 1)); - - ZONE_DMA_INDEX = 0; - ZONE_NORMAL_INDEX = memory_management_struct.count_zones ; - ZONE_UNMAPPED_INDEX = 0; - - //kdebug("ZONE_DMA_INDEX=%d\tZONE_NORMAL_INDEX=%d\tZONE_UNMAPPED_INDEX=%d", ZONE_DMA_INDEX, ZONE_NORMAL_INDEX, ZONE_UNMAPPED_INDEX); - // 设置内存页管理结构的地址,预留了一段空间,防止内存越界。 - memory_management_struct.end_of_struct = (ul)((ul)memory_management_struct.zones_struct + memory_management_struct.zones_struct_len + sizeof(long) * 32) & (~(sizeof(long) - 1)); - - // 初始化内存管理单元结构所占的物理页的结构体 - ul mms_max_page = (virt_2_phys(memory_management_struct.end_of_struct) >> PAGE_2M_SHIFT); // 内存管理单元所占据的序号最大的物理页 - // kdebug("mms_max_page=%ld", mms_max_page); - - struct Page *tmp_page = NULL; - ul page_num; - // 第0个page已经在上方配置 - for (ul j = 1; j <= mms_max_page; ++j) - { - barrier(); - tmp_page = memory_management_struct.pages_struct + j; - page_init(tmp_page, PAGE_PGT_MAPPED | PAGE_KERNEL | PAGE_KERNEL_INIT); - barrier(); - page_num = tmp_page->addr_phys >> PAGE_2M_SHIFT; - *(memory_management_struct.bmp + (page_num >> 6)) |= (1UL << (page_num % 64)); - ++tmp_page->zone->count_pages_using; - --tmp_page->zone->count_pages_free; - } - - kinfo("Memory management unit initialize complete!"); - - flush_tlb(); - // todo: 在这里增加代码,暂时停止视频输出,否则可能会导致图像数据写入slab的区域,从而造成异常 - // 初始化slab内存池 - slab_init(); - page_table_init(); - - initial_mm.pgd = (pml4t_t *)get_CR3(); - - initial_mm.code_addr_start = memory_management_struct.kernel_code_start; - initial_mm.code_addr_end = memory_management_struct.kernel_code_end; - - initial_mm.data_addr_start = (ul)&_data; - initial_mm.data_addr_end = memory_management_struct.kernel_data_end; - - initial_mm.rodata_addr_start = (ul)&_rodata; - initial_mm.rodata_addr_end = (ul)&_erodata; - initial_mm.bss_start = (uint64_t)&_bss; - initial_mm.bss_end = (uint64_t)&_ebss; - - initial_mm.brk_start = memory_management_struct.start_brk; - initial_mm.brk_end = current_pcb->addr_limit; - - initial_mm.stack_start = _stack_start; - initial_mm.vmas = NULL; - - - - mmio_init(); -} - -/** - * @brief 初始化内存页 - * - * @param page 内存页结构体 - * @param flags 标志位 - * 本函数只负责初始化内存页,允许对同一页面进行多次初始化 - * 而维护计数器及置位bmp标志位的功能,应当在分配页面的时候手动完成 - * @return unsigned long - */ -unsigned long page_init(struct Page *page, ul flags) -{ - page->attr |= flags; - // 若页面的引用计数为0或是共享页,增加引用计数 - if ((!page->ref_counts) || (page->attr & PAGE_SHARED)) - { - ++page->ref_counts; - barrier(); - if (page->zone) - ++page->zone->total_pages_link; - } - page->anon_vma = NULL; - spin_init(&(page->op_lock)); - return 0; -} - -/** - * @brief 从已初始化的页结构中搜索符合申请条件的、连续num个struct page - * - * @param zone_select 选择内存区域, 可选项:dma, mapped in pgt(normal), unmapped in pgt - * @param num 需要申请的连续内存页的数量 num<64 - * @param flags 将页面属性设置成flag - * @return struct Page* - */ -struct Page *alloc_pages(unsigned int zone_select, int num, ul flags) -{ - ul zone_start = 0, zone_end = 0; - if (num >= 64 && num <= 0) - { - kerror("alloc_pages(): num is invalid."); - return NULL; - } - - ul attr = flags; - switch (zone_select) - { - case ZONE_DMA: - // DMA区域 - zone_start = 0; - zone_end = ZONE_DMA_INDEX; - attr |= PAGE_PGT_MAPPED; - break; - case ZONE_NORMAL: - zone_start = ZONE_DMA_INDEX; - zone_end = ZONE_NORMAL_INDEX; - attr |= PAGE_PGT_MAPPED; - break; - case ZONE_UNMAPPED_IN_PGT: - zone_start = ZONE_NORMAL_INDEX; - zone_end = ZONE_UNMAPPED_INDEX; - attr = 0; - break; - - default: - kerror("In alloc_pages: param: zone_select incorrect."); - // 返回空 - return NULL; - break; - } - - for (int i = zone_start; i < zone_end; ++i) - { - if ((memory_management_struct.zones_struct + i)->count_pages_free < num) - continue; - - struct Zone *z = memory_management_struct.zones_struct + i; - // 区域对应的起止页号 - ul page_start = (z->zone_addr_start >> PAGE_2M_SHIFT); - ul page_end = (z->zone_addr_end >> PAGE_2M_SHIFT); - - ul tmp = 64 - page_start % 64; - for (ul j = page_start; j < page_end; j += ((j % 64) ? tmp : 64)) - { - // 按照bmp中的每一个元素进行查找 - // 先将p定位到bmp的起始元素 - ul *p = memory_management_struct.bmp + (j >> 6); - - ul shift = j % 64; - ul tmp_num = ((1UL << num) - 1); - for (ul k = shift; k < 64; ++k) - { - // 寻找连续num个空页 - if (!((k ? ((*p >> k) | (*(p + 1) << (64 - k))) : *p) & tmp_num)) - - { - ul start_page_num = j + k - shift; // 计算得到要开始获取的内存页的页号 - for (ul l = 0; l < num; ++l) - { - struct Page *x = memory_management_struct.pages_struct + start_page_num + l; - - // 分配页面,手动配置属性及计数器 - // 置位bmp - *(memory_management_struct.bmp + ((x->addr_phys >> PAGE_2M_SHIFT) >> 6)) |= (1UL << (x->addr_phys >> PAGE_2M_SHIFT) % 64); - ++(z->count_pages_using); - --(z->count_pages_free); - page_init(x, attr); - } - // 成功分配了页面,返回第一个页面的指针 - // kwarn("start page num=%d\n", start_page_num); - return (struct Page *)(memory_management_struct.pages_struct + start_page_num); - } - } - } - } - kBUG("Cannot alloc page, ZONE=%d\tnums=%d, mm_total_2M_pages=%d", zone_select, num, mm_total_2M_pages); - return NULL; -} - -/** - * @brief 清除页面的引用计数, 计数为0时清空除页表已映射以外的所有属性 - * - * @param p 物理页结构体 - * @return unsigned long - */ -unsigned long page_clean(struct Page *p) -{ - --p->ref_counts; - --p->zone->total_pages_link; - - // 若引用计数为空,则清空除PAGE_PGT_MAPPED以外的所有属性 - if (!p->ref_counts) - { - p->attr &= PAGE_PGT_MAPPED; - } - return 0; -} - -/** - * @brief Get the page's attr - * - * @param page 内存页结构体 - * @return ul 属性 - */ -ul get_page_attr(struct Page *page) -{ - if (page == NULL) - { - kBUG("get_page_attr(): page == NULL"); - return EPAGE_NULL; - } - else - return page->attr; -} - -/** - * @brief Set the page's attr - * - * @param page 内存页结构体 - * @param flags 属性 - * @return ul 错误码 - */ -ul set_page_attr(struct Page *page, ul flags) -{ - if (page == NULL) - { - kBUG("get_page_attr(): page == NULL"); - return EPAGE_NULL; - } - else - { - page->attr = flags; - return 0; - } -} -/** - * @brief 释放连续number个内存页 - * - * @param page 第一个要被释放的页面的结构体 - * @param number 要释放的内存页数量 number<64 - */ - -void free_pages(struct Page *page, int number) -{ - if (page == NULL) - { - kerror("free_pages() page is invalid."); - return; - } - - if (number >= 64 || number <= 0) - { - kerror("free_pages(): number %d is invalid.", number); - return; - } - - ul page_num; - for (int i = 0; i < number; ++i, ++page) - { - page_num = page->addr_phys >> PAGE_2M_SHIFT; - // 复位bmp - *(memory_management_struct.bmp + (page_num >> 6)) &= ~(1UL << (page_num % 64)); - // 更新计数器 - --page->zone->count_pages_using; - ++page->zone->count_pages_free; - page->attr = 0; - } - - return; -} - -/** - * @brief 重新初始化页表的函数 - * 将所有物理页映射到线性地址空间 - */ -void page_table_init() -{ - kinfo("Re-Initializing page table..."); - ul *global_CR3 = get_CR3(); - - int js = 0; - ul *tmp_addr; - for (int i = 0; i < memory_management_struct.count_zones; ++i) - { - struct Zone *z = memory_management_struct.zones_struct + i; - struct Page *p = z->pages_group; - - if (i == ZONE_UNMAPPED_INDEX && ZONE_UNMAPPED_INDEX != 0) - break; - - for (int j = 0; j < z->count_pages; ++j) - { - mm_map_proc_page_table((uint64_t)get_CR3(), true, (ul)phys_2_virt(p->addr_phys), p->addr_phys, PAGE_2M_SIZE, PAGE_KERNEL_PAGE, false, true, false); - - ++p; - ++js; - } - } - - - barrier(); - // ========= 在IDLE进程的顶层页表中添加对内核地址空间的映射 ===================== - - // 由于IDLE进程的顶层页表的高地址部分会被后续进程所复制,为了使所有进程能够共享相同的内核空间, - // 因此需要先在IDLE进程的顶层页表内映射二级页表 - - uint64_t *idle_pml4t_vaddr = (uint64_t *)phys_2_virt((uint64_t)get_CR3() & (~0xfffUL)); - - for (int i = 256; i < 512; ++i) - { - uint64_t *tmp = idle_pml4t_vaddr + i; - barrier(); - if (*tmp == 0) - { - void *pdpt = kmalloc(PAGE_4K_SIZE, 0); - barrier(); - memset(pdpt, 0, PAGE_4K_SIZE); - barrier(); - set_pml4t(tmp, mk_pml4t(virt_2_phys(pdpt), PAGE_KERNEL_PGT)); - } - } - barrier(); - flush_tlb(); - kinfo("Page table Initialized. Affects:%d", js); -} - -/** - * @brief 从页表中获取pdt页表项的内容 - * - * @param proc_page_table_addr 页表的地址 - * @param is_phys 页表地址是否为物理地址 - * @param virt_addr_start 要清除的虚拟地址的起始地址 - * @param length 要清除的区域的长度 - * @param clear 是否清除标志位 - */ -uint64_t mm_get_PDE(ul proc_page_table_addr, bool is_phys, ul virt_addr, bool clear) -{ - ul *tmp; - if (is_phys) - tmp = phys_2_virt((ul *)((ul)proc_page_table_addr & (~0xfffUL)) + ((virt_addr >> PAGE_GDT_SHIFT) & 0x1ff)); - else - tmp = (ul *)((ul)proc_page_table_addr & (~0xfffUL)) + ((virt_addr >> PAGE_GDT_SHIFT) & 0x1ff); - - // pml4页表项为0 - if (*tmp == 0) - return 0; - - tmp = phys_2_virt((ul *)(*tmp & (~0xfffUL)) + ((virt_addr >> PAGE_1G_SHIFT) & 0x1ff)); - - // pdpt页表项为0 - if (*tmp == 0) - return 0; - - // 读取pdt页表项 - tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(virt_addr) >> PAGE_2M_SHIFT) & 0x1ff))); - - if (clear) // 清除页表项的标志位 - return *tmp & (~0x1fff); - else - return *tmp; -} - -/** - * @brief 从mms中寻找Page结构体 - * - * @param phys_addr - * @return struct Page* - */ -static struct Page *mm_find_page(uint64_t phys_addr, uint32_t zone_select) -{ - uint32_t zone_start, zone_end; - switch (zone_select) - { - case ZONE_DMA: - // DMA区域 - zone_start = 0; - zone_end = ZONE_DMA_INDEX; - break; - case ZONE_NORMAL: - zone_start = ZONE_DMA_INDEX; - zone_end = ZONE_NORMAL_INDEX; - break; - case ZONE_UNMAPPED_IN_PGT: - zone_start = ZONE_NORMAL_INDEX; - zone_end = ZONE_UNMAPPED_INDEX; - break; - - default: - kerror("In mm_find_page: param: zone_select incorrect."); - // 返回空 - return NULL; - break; - } - - for (int i = zone_start; i <= zone_end; ++i) - { - if ((memory_management_struct.zones_struct + i)->count_pages_using == 0) - continue; - - struct Zone *z = memory_management_struct.zones_struct + i; - - // 区域对应的起止页号 - ul page_start = (z->zone_addr_start >> PAGE_2M_SHIFT); - ul page_end = (z->zone_addr_end >> PAGE_2M_SHIFT); - - ul tmp = 64 - page_start % 64; - for (ul j = page_start; j < page_end; j += ((j % 64) ? tmp : 64)) - { - // 按照bmp中的每一个元素进行查找 - // 先将p定位到bmp的起始元素 - ul *p = memory_management_struct.bmp + (j >> 6); - - ul shift = j % 64; - for (ul k = shift; k < 64; ++k) - { - if ((*p >> k) & 1) // 若当前页已分配 - { - uint64_t page_num = j + k - shift; - struct Page *x = memory_management_struct.pages_struct + page_num; - - if (x->addr_phys == phys_addr) // 找到对应的页 - return x; - } - } - } - } - return NULL; -} - -/** - * @brief 调整堆区域的大小(暂时只能增加堆区域) - * - * @todo 缩小堆区域 - * @param old_brk_end_addr 原本的堆内存区域的结束地址 - * @param offset 新的地址相对于原地址的偏移量 - * @return uint64_t - */ -uint64_t mm_do_brk(uint64_t old_brk_end_addr, int64_t offset) -{ - - uint64_t end_addr = PAGE_2M_ALIGN(old_brk_end_addr + offset); - if (offset >= 0) - { - for (uint64_t i = old_brk_end_addr; i < end_addr; i += PAGE_2M_SIZE) - { - struct vm_area_struct *vma = NULL; - mm_create_vma(current_pcb->mm, i, PAGE_2M_SIZE, VM_USER | VM_ACCESS_FLAGS, NULL, &vma); - mm_map(current_pcb->mm, i, PAGE_2M_SIZE, alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys); - // mm_map_vma(vma, alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys, 0, PAGE_2M_SIZE); - } - current_pcb->mm->brk_end = end_addr; - } - else - { - - // 释放堆内存 - for (uint64_t i = end_addr; i < old_brk_end_addr; i += PAGE_2M_SIZE) - { - uint64_t phys = mm_get_PDE((uint64_t)phys_2_virt((uint64_t)current_pcb->mm->pgd), false, i, true); - - // 找到对应的页 - struct Page *p = mm_find_page(phys, ZONE_NORMAL); - if (p == NULL) - { - kerror("cannot find page addr=%#018lx", phys); - return end_addr; - } - - free_pages(p, 1); - } - - mm_unmap_proc_table((uint64_t)phys_2_virt((uint64_t)current_pcb->mm->pgd), false, end_addr, PAGE_2M_ALIGN(ABS(offset))); - // 在页表中取消映射 - } - return end_addr; -} - -/** - * @brief 创建mmio对应的页结构体 - * - * @param paddr 物理地址 - * @return struct Page* 创建成功的page - */ -struct Page *__create_mmio_page_struct(uint64_t paddr) -{ - struct Page *p = (struct Page *)kzalloc(sizeof(struct Page), 0); - if (p == NULL) - return NULL; - p->addr_phys = paddr; - page_init(p, PAGE_DEVICE); - return p; -} \ No newline at end of file diff --git a/kernel/src/mm/mm.h b/kernel/src/mm/mm.h index 1ae69383..8dec99b0 100644 --- a/kernel/src/mm/mm.h +++ b/kernel/src/mm/mm.h @@ -6,9 +6,9 @@ #include #include -// 每个页表的项数 -// 64位下,每个页表4k,每条页表项8B,故一个页表有512条 -#define PTRS_PER_PGT 512 +extern void rs_pseudo_map_phys(uint64_t virt_addr, uint64_t phys_addr, uint64_t size); +extern void rs_map_phys(uint64_t virt_addr, uint64_t phys_addr, uint64_t size, uint64_t flags); +extern uint64_t rs_unmap_at_low_addr(); // 内核层的起始地址 #define PAGE_OFFSET 0xffff800000000000UL @@ -39,9 +39,6 @@ // 虚拟地址与物理地址转换 #define virt_2_phys(addr) ((unsigned long)(addr)-PAGE_OFFSET) #define phys_2_virt(addr) ((unsigned long *)((unsigned long)(addr) + PAGE_OFFSET)) -// 获取对应的页结构体 -#define Virt_To_2M_Page(kaddr) (memory_management_struct.pages_struct + (virt_2_phys(kaddr) >> PAGE_2M_SHIFT)) -#define Phy_to_2M_Page(kaddr) (memory_management_struct.pages_struct + ((unsigned long)(kaddr) >> PAGE_2M_SHIFT)) // 在这个地址以上的虚拟空间,用来进行特殊的映射 #define SPECIAL_MEMOEY_MAPPING_VIRT_ADDR_BASE 0xffffa00000000000UL @@ -49,7 +46,6 @@ #define IO_APIC_MAPPING_OFFSET 0xfec00000UL #define LOCAL_APIC_MAPPING_OFFSET 0xfee00000UL #define AHCI_MAPPING_OFFSET 0xff200000UL // AHCI 映射偏移量,之后使用了4M的地址 -#define XHCI_MAPPING_OFFSET 0x100000000 // XHCI控制器映射偏移量(后方请预留1GB的虚拟空间来映射不同的controller) // ===== 内存区域属性 ===== // DMA区域 @@ -138,59 +134,21 @@ #define PAGE_USER_4K_PAGE (PAGE_U_S | PAGE_R_W | PAGE_PRESENT) -// ===== 错误码定义 ==== -// 物理页结构体为空 -#define EPAGE_NULL 1 - /** * @brief 刷新TLB的宏定义 * 由于任何写入cr3的操作都会刷新TLB,因此这个宏定义可以刷新TLB */ -#define flush_tlb() \ - do \ - { \ - ul tmp; \ - io_mfence(); \ - __asm__ __volatile__("movq %%cr3, %0\n\t" \ - "movq %0, %%cr3\n\t" \ - : "=r"(tmp)::"memory"); \ - \ +#define flush_tlb() \ + do \ + { \ + ul tmp; \ + io_mfence(); \ + __asm__ __volatile__("movq %%cr3, %0\n\t" \ + "movq %0, %%cr3\n\t" \ + : "=r"(tmp)::"memory"); \ + \ } while (0); -/** - * @brief 系统内存信息结构体(单位:字节) - * - */ -struct mm_stat_t -{ - uint64_t total; // 计算机的总内存数量大小 - uint64_t used; // 已使用的内存大小 - uint64_t free; // 空闲物理页所占的内存大小 - uint64_t shared; // 共享的内存大小 - uint64_t cache_used; // 位于slab缓冲区中的已使用的内存大小 - uint64_t cache_free; // 位于slab缓冲区中的空闲的内存大小 - uint64_t available; // 系统总空闲内存大小(包括kmalloc缓冲区) -}; - -/** - * @brief 虚拟内存区域的操作方法的结构体 - * - */ -struct vm_operations_t -{ - /** - * @brief vm area 被打开时的回调函数 - * - */ - void (*open)(struct vm_area_struct *area); - /** - * @brief vm area将要被移除的时候,将会调用该回调函数 - * - */ - void (*close)(struct vm_area_struct *area); -}; - -extern struct memory_desc memory_management_struct; // 导出内核程序的几个段的起止地址 extern char _text; @@ -203,26 +161,6 @@ extern char _bss; extern char _ebss; extern char _end; -// 每个区域的索引 - -int ZONE_DMA_INDEX = 0; -int ZONE_NORMAL_INDEX = 0; -int ZONE_UNMAPPED_INDEX = 0; - -// 初始化内存管理单元 -void mm_init(); - -/** - * @brief 初始化内存页 - * - * @param page 内存页结构体 - * @param flags 标志位 - * 本函数只负责初始化内存页,允许对同一页面进行多次初始化 - * 而维护计数器及置位bmp标志位的功能,应当在分配页面的时候手动完成 - * @return unsigned long - */ -unsigned long page_init(struct Page *page, ul flags); - /** * @brief 读取CR3寄存器的值(存储了页目录的基地址) * @@ -231,70 +169,11 @@ unsigned long page_init(struct Page *page, ul flags); unsigned long *get_CR3() { ul *tmp; - __asm__ __volatile__("movq %%cr3, %0\n\t" : "=r"(tmp)::"memory"); + __asm__ __volatile__("movq %%cr3, %0\n\t" + : "=r"(tmp)::"memory"); return tmp; } -/** - * @brief 从已初始化的页结构中搜索符合申请条件的、连续num个struct page - * - * @param zone_select 选择内存区域, 可选项:dma, mapped in pgt(normal), unmapped in pgt - * @param num 需要申请的内存页的数量 num<64 - * @param flags 将页面属性设置成flag - * @return struct Page* - */ -struct Page *alloc_pages(unsigned int zone_select, int num, ul flags); - -/** - * @brief 清除页面的引用计数, 计数为0时清空除页表已映射以外的所有属性 - * - * @param p 物理页结构体 - * @return unsigned long - */ -unsigned long page_clean(struct Page *page); - -/** - * @brief 释放连续number个内存页 - * - * @param page 第一个要被释放的页面的结构体 - * @param number 要释放的内存页数量 number<64 - */ -void free_pages(struct Page *page, int number); - -/** - * @brief Get the page's attr - * - * @param page 内存页结构体 - * @return ul 属性 - */ -ul get_page_attr(struct Page *page); - -/** - * @brief Set the page's attr - * - * @param page 内存页结构体 - * @param flags 属性 - * @return ul 错误码 - */ -ul set_page_attr(struct Page *page, ul flags); - -#define mk_pml4t(addr, attr) ((unsigned long)(addr) | (unsigned long)(attr)) -/** - * @brief 设置pml4页表的页表项 - * @param pml4tptr pml4页表项的地址 - * @param pml4val pml4页表项的值 - */ -#define set_pml4t(pml4tptr, pml4tval) (*(pml4tptr) = (pml4tval)) - -#define mk_pdpt(addr, attr) ((unsigned long)(addr) | (unsigned long)(attr)) -#define set_pdpt(pdptptr, pdptval) (*(pdptptr) = (pdptval)) - -#define mk_pdt(addr, attr) ((unsigned long)(addr) | (unsigned long)(attr)) -#define set_pdt(pdtptr, pdtval) (*(pdtptr) = (pdtval)) - -#define mk_pt(addr, attr) ((unsigned long)(addr) | (unsigned long)(attr)) -#define set_pt(ptptr, ptval) (*(ptptr) = (ptval)) - /* * vm_area_struct中的vm_flags的可选值 * 对应的结构体请见mm-types.h @@ -312,233 +191,3 @@ ul set_page_attr(struct Page *page, ul flags); /* VMA basic access permission flags */ #define VM_ACCESS_FLAGS (VM_READ | VM_WRITE | VM_EXEC) - -/** - * @brief 初始化虚拟内存区域结构体 - * - * @param vma - * @param mm - */ -static inline void vma_init(struct vm_area_struct *vma, struct mm_struct *mm) -{ - memset(vma, 0, sizeof(struct vm_area_struct)); - vma->vm_mm = mm; - vma->vm_prev = vma->vm_next = NULL; - vma->vm_ops = NULL; - list_init(&vma->anon_vma_list); -} - -/** - * @brief 判断给定的vma是否为当前进程所属的vma - * - * @param vma 给定的vma结构体 - * @return true - * @return false - */ -static inline bool vma_is_foreign(struct vm_area_struct *vma) -{ - if (current_pcb->mm == NULL) - return true; - if (current_pcb->mm != vma->vm_mm) - return true; - return false; -} - -static inline bool vma_is_accessible(struct vm_area_struct *vma) -{ - return vma->vm_flags & VM_ACCESS_FLAGS; -} - -/** - * @brief 获取一块新的vma结构体,并将其与指定的mm进行绑定 - * - * @param mm 与VMA绑定的内存空间分布结构体 - * @return struct vm_area_struct* 新的VMA - */ -struct vm_area_struct *vm_area_alloc(struct mm_struct *mm); - -/** - * @brief 释放vma结构体 - * - * @param vma 待释放的vma结构体 - */ -void vm_area_free(struct vm_area_struct *vma); - -/** - * @brief 从链表中删除指定的vma结构体 - * - * @param vma - */ -void vm_area_del(struct vm_area_struct *vma); - -/** - * @brief 查找第一个符合“addr < vm_end”条件的vma - * - * @param mm 内存空间分布结构体 - * @param addr 虚拟地址 - * @return struct vm_area_struct* 符合条件的vma - */ -struct vm_area_struct *vma_find(struct mm_struct *mm, uint64_t addr); - -/** - * @brief 插入vma - * - * @param mm - * @param vma - * @return int - */ -int vma_insert(struct mm_struct *mm, struct vm_area_struct *vma); - -/** - * @brief 重新初始化页表的函数 - * 将所有物理页映射到线性地址空间 - */ -void page_table_init(); - -/** - * @brief 将物理地址映射到页表的函数 - * - * @param virt_addr_start 要映射到的虚拟地址的起始位置 - * @param phys_addr_start 物理地址的起始位置 - * @param length 要映射的区域的长度(字节) - * @param flags 标志位 - * @param use4k 是否使用4k页 - */ -int mm_map_phys_addr(ul virt_addr_start, ul phys_addr_start, ul length, ul flags, bool use4k); - -/** - * @brief 将将物理地址填写到进程的页表的函数 - * - * @param proc_page_table_addr 页表的基地址 - * @param is_phys 页表的基地址是否为物理地址 - * @param virt_addr_start 要映射到的虚拟地址的起始位置 - * @param phys_addr_start 物理地址的起始位置 - * @param length 要映射的区域的长度(字节) - * @param user 用户态是否可访问 - * @param flush 是否刷新tlb - * @param use4k 是否使用4k页 - */ -int mm_map_proc_page_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul phys_addr_start, ul length, - ul flags, bool user, bool flush, bool use4k); - -int mm_map_phys_addr_user(ul virt_addr_start, ul phys_addr_start, ul length, ul flags); - -/** - * @brief 从页表中清除虚拟地址的映射 - * - * @param proc_page_table_addr 页表的地址 - * @param is_phys 页表地址是否为物理地址 - * @param virt_addr_start 要清除的虚拟地址的起始地址 - * @param length 要清除的区域的长度 - */ -void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul length); - -/** - * @brief 取消当前进程的页表中的虚拟地址映射 - * - * @param virt_addr 虚拟地址 - * @param length 地址长度 - */ -#define mm_unmap_addr(virt_addr, length) ({ mm_unmap_proc_table((uint64_t)get_CR3(), true, virt_addr, length); }) - -/** - * @brief 创建VMA - * - * @param mm 要绑定的内存空间分布结构体 - * @param vaddr 起始虚拟地址 - * @param length 长度(字节) - * @param vm_flags vma的标志 - * @param vm_ops vma的操作接口 - * @param res_vma 返回的vma指针 - * @return int 错误码 - */ -int mm_create_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, vm_flags_t vm_flags, - struct vm_operations_t *vm_ops, struct vm_area_struct **res_vma); - -/** - * @brief 将指定的物理地址映射到指定的vma处 - * - * @param vma 要进行映射的VMA结构体 - * @param paddr 起始物理地址 - * @param offset 要映射的起始位置在vma中的偏移量 - * @param length 要映射的长度 - * @return int 错误码 - */ -int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr, uint64_t offset, uint64_t length); - -/** - * @brief 在页表中映射物理地址到指定的虚拟地址(需要页表中已存在对应的vma) - * - * @param mm 内存管理结构体 - * @param vaddr 虚拟地址 - * @param length 长度(字节) - * @param paddr 物理地址 - * @return int 返回码 - */ -int mm_map(struct mm_struct *mm, uint64_t vaddr, uint64_t length, uint64_t paddr); - -/** - * @brief 在页表中取消指定的vma的映射 - * - * @param mm 指定的mm - * @param vma 待取消映射的vma - * @param paddr 返回的被取消映射的起始物理地址 - * @return int 返回码 - */ -int mm_unmap_vma(struct mm_struct *mm, struct vm_area_struct *vma, uint64_t *paddr); - -/** - * @brief 解除一段虚拟地址的映射(这些地址必须在vma中存在) - * - * @param mm 内存空间结构体 - * @param vaddr 起始地址 - * @param length 结束地址 - * @param destroy 是否释放vma结构体 - * @return int 错误码 - */ -int mm_unmap(struct mm_struct *mm, uint64_t vaddr, uint64_t length, bool destroy); - -/** - * @brief 检测是否为有效的2M页(物理内存页) - * - * @param paddr 物理地址 - * @return int8_t 是 -> 1 - * 否 -> 0 - */ -int8_t mm_is_2M_page(uint64_t paddr); - -/** - * @brief 检查页表是否存在不为0的页表项 - * - * @param ptr 页表基指针 - * @return int8_t 存在 -> 1 - * 不存在 -> 0 - */ -int8_t mm_check_page_table(uint64_t *ptr); - -/** - * @brief 调整堆区域的大小(暂时只能增加堆区域) - * - * @todo 缩小堆区域 - * @param old_brk_end_addr 原本的堆内存区域的结束地址 - * @param offset 新的地址相对于原地址的偏移量 - * @return uint64_t - */ -uint64_t mm_do_brk(uint64_t old_brk_end_addr, int64_t offset); - -/** - * @brief 获取系统当前的内存信息(未上锁,不一定精准) - * - * @return struct mm_stat_t 内存信息结构体 - */ -struct mm_stat_t mm_stat(); - -/** - * @brief 检测指定地址是否已经被映射 - * - * @param page_table_phys_addr 页表的物理地址 - * @param virt_addr 要检测的地址 - * @return true 已经被映射 - * @return false - */ -bool mm_check_mapped(ul page_table_phys_addr, uint64_t virt_addr); \ No newline at end of file diff --git a/kernel/src/mm/mmap.c b/kernel/src/mm/mmap.c deleted file mode 100644 index d39c45ca..00000000 --- a/kernel/src/mm/mmap.c +++ /dev/null @@ -1,582 +0,0 @@ -#include "mm.h" -#include "slab.h" -#include "internal.h" -#include -#include - -extern uint64_t mm_total_2M_pages; - -/** - * @brief 虚拟地址长度所需要的entry数量 - * - */ -typedef struct -{ - int64_t num_PML4E; - int64_t num_PDPTE; - int64_t num_PDE; - int64_t num_PTE; -} mm_pgt_entry_num_t; - -/** - * @brief 计算虚拟地址长度对应的页表entry数量 - * - * @param length 长度 - * @param ent 返回的entry数量结构体 - */ -static void mm_calculate_entry_num(uint64_t length, mm_pgt_entry_num_t *ent) -{ - if (ent == NULL) - return; - ent->num_PML4E = (length + (1UL << PAGE_GDT_SHIFT) - 1) >> PAGE_GDT_SHIFT; - ent->num_PDPTE = (length + PAGE_1G_SIZE - 1) >> PAGE_1G_SHIFT; - ent->num_PDE = (length + PAGE_2M_SIZE - 1) >> PAGE_2M_SHIFT; - ent->num_PTE = (length + PAGE_4K_SIZE - 1) >> PAGE_4K_SHIFT; -} - -/** - * @brief 将物理地址映射到页表的函数 - * - * @param virt_addr_start 要映射到的虚拟地址的起始位置 - * @param phys_addr_start 物理地址的起始位置 - * @param length 要映射的区域的长度(字节) - * @param flags 标志位 - * @param use4k 是否使用4k页 - */ -int mm_map_phys_addr(ul virt_addr_start, ul phys_addr_start, ul length, ul flags, bool use4k) -{ - uint64_t global_CR3 = (uint64_t)get_CR3(); - - return mm_map_proc_page_table(global_CR3, true, virt_addr_start, phys_addr_start, length, flags, false, true, use4k); -} - -int mm_map_phys_addr_user(ul virt_addr_start, ul phys_addr_start, ul length, ul flags) -{ - uint64_t global_CR3 = (uint64_t)get_CR3(); - return mm_map_proc_page_table(global_CR3, true, virt_addr_start, phys_addr_start, length, flags, true, true, false); -} - -/** - * @brief 将将物理地址填写到进程的页表的函数 - * - * @param proc_page_table_addr 页表的基地址 - * @param is_phys 页表的基地址是否为物理地址 - * @param virt_addr_start 要映射到的虚拟地址的起始位置 - * @param phys_addr_start 物理地址的起始位置 - * @param length 要映射的区域的长度(字节) - * @param user 用户态是否可访问 - * @param flush 是否刷新tlb - * @param use4k 是否使用4k页 - */ -int mm_map_proc_page_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul phys_addr_start, ul length, ul flags, bool user, bool flush, bool use4k) -{ - - // 计算线性地址对应的pml4页表项的地址 - mm_pgt_entry_num_t pgt_num; - mm_calculate_entry_num(length, &pgt_num); - - // 已映射的内存大小 - uint64_t length_mapped = 0; - - // 对user标志位进行校正 - if ((flags & PAGE_U_S) != 0) - user = true; - else - user = false; - - uint64_t pml4e_id = ((virt_addr_start >> PAGE_GDT_SHIFT) & 0x1ff); - uint64_t *pml4_ptr; - if (is_phys) - pml4_ptr = phys_2_virt((ul *)((ul)proc_page_table_addr & (~0xfffUL))); - else - pml4_ptr = (ul *)((ul)proc_page_table_addr & (~0xfffUL)); - - // 循环填写顶层页表 - for (; (pgt_num.num_PML4E > 0) && pml4e_id < 512; ++pml4e_id) - { - // 剩余需要处理的pml4E -1 - --(pgt_num.num_PML4E); - - ul *pml4e_ptr = pml4_ptr + pml4e_id; - - // 创建新的二级页表 - if (*pml4e_ptr == 0) - { - ul *virt_addr = kmalloc(PAGE_4K_SIZE, 0); - memset(virt_addr, 0, PAGE_4K_SIZE); - set_pml4t(pml4e_ptr, mk_pml4t(virt_2_phys(virt_addr), (user ? PAGE_USER_PGT : PAGE_KERNEL_PGT))); - } - - uint64_t pdpte_id = (((virt_addr_start + length_mapped) >> PAGE_1G_SHIFT) & 0x1ff); - uint64_t *pdpt_ptr = (uint64_t *)phys_2_virt(*pml4e_ptr & (~0xfffUL)); - - // 循环填写二级页表 - for (; (pgt_num.num_PDPTE > 0) && pdpte_id < 512; ++pdpte_id) - { - --pgt_num.num_PDPTE; - uint64_t *pdpte_ptr = (pdpt_ptr + pdpte_id); - - // 创建新的三级页表 - if (*pdpte_ptr == 0) - { - ul *virt_addr = kmalloc(PAGE_4K_SIZE, 0); - memset(virt_addr, 0, PAGE_4K_SIZE); - set_pdpt(pdpte_ptr, mk_pdpt(virt_2_phys(virt_addr), (user ? PAGE_USER_DIR : PAGE_KERNEL_DIR))); - } - - uint64_t pde_id = (((virt_addr_start + length_mapped) >> PAGE_2M_SHIFT) & 0x1ff); - uint64_t *pd_ptr = (uint64_t *)phys_2_virt(*pdpte_ptr & (~0xfffUL)); - - // 循环填写三级页表,初始化2M物理页 - for (; (pgt_num.num_PDE > 0) && pde_id < 512; ++pde_id) - { - --pgt_num.num_PDE; - // 计算当前2M物理页对应的pdt的页表项的物理地址 - ul *pde_ptr = pd_ptr + pde_id; - // ====== 使用4k页 ======= - if (unlikely(use4k)) - { - // kdebug("use 4k"); - if (*pde_ptr == 0) - { - // 创建四级页表 - uint64_t *vaddr = kmalloc(PAGE_4K_SIZE, 0); - memset(vaddr, 0, PAGE_4K_SIZE); - set_pdt(pde_ptr, mk_pdt(virt_2_phys(vaddr), (user ? PAGE_USER_PDE : PAGE_KERNEL_PDE))); - } - else if (unlikely(*pde_ptr & (1 << 7))) - { - // 当前页表项已经被映射了2MB物理页 - goto failed; - } - - uint64_t pte_id = (((virt_addr_start + length_mapped) >> PAGE_4K_SHIFT) & 0x1ff); - uint64_t *pt_ptr = (uint64_t *)phys_2_virt(*pde_ptr & (~0xfffUL)); - - // 循环填写4级页表,初始化4K页 - for (; (pgt_num.num_PTE > 0) && pte_id < 512; ++pte_id) - { - --pgt_num.num_PTE; - uint64_t *pte_ptr = pt_ptr + pte_id; - - if (unlikely(*pte_ptr != 0)) - kwarn("pte already exists."); - else - set_pt(pte_ptr, mk_pt((ul)phys_addr_start + length_mapped, flags | (user ? PAGE_USER_4K_PAGE : PAGE_KERNEL_4K_PAGE))); - length_mapped += PAGE_4K_SIZE; - } - } - // ======= 使用2M页 ======== - else - { - if (unlikely((*pde_ptr != 0) && user == true)) - { - // 如果是用户态可访问的页,则释放当前新获取的物理页 - if (likely((((ul)phys_addr_start + length_mapped) >> PAGE_2M_SHIFT) < mm_total_2M_pages)) // 校验是否为内存中的物理页 - free_pages(Phy_to_2M_Page((ul)phys_addr_start + length_mapped), 1); - length_mapped += PAGE_2M_SIZE; - continue; - } - // 页面写穿,禁止缓存 - set_pdt(pde_ptr, mk_pdt((ul)phys_addr_start + length_mapped, flags | (user ? PAGE_USER_PAGE : PAGE_KERNEL_PAGE))); - length_mapped += PAGE_2M_SIZE; - } - } - } - } - if (likely(flush)) - flush_tlb(); - return 0; -failed:; - kerror("Map memory failed. use4k=%d, vaddr=%#018lx, paddr=%#018lx", use4k, virt_addr_start, phys_addr_start); - return -EFAULT; -} - -/** - * @brief 从页表中清除虚拟地址的映射 - * - * @param proc_page_table_addr 页表的地址 - * @param is_phys 页表地址是否为物理地址 - * @param virt_addr_start 要清除的虚拟地址的起始地址 - * @param length 要清除的区域的长度 - */ -void mm_unmap_proc_table(ul proc_page_table_addr, bool is_phys, ul virt_addr_start, ul length) -{ - - // 计算线性地址对应的pml4页表项的地址 - mm_pgt_entry_num_t pgt_num; - mm_calculate_entry_num(length, &pgt_num); - // 已取消映射的内存大小 - uint64_t length_unmapped = 0; - - uint64_t pml4e_id = ((virt_addr_start >> PAGE_GDT_SHIFT) & 0x1ff); - uint64_t *pml4_ptr; - if (is_phys) - pml4_ptr = phys_2_virt((ul *)((ul)proc_page_table_addr & (~0xfffUL))); - else - pml4_ptr = (ul *)((ul)proc_page_table_addr & (~0xfffUL)); - - // 循环填写顶层页表 - for (; (pgt_num.num_PML4E > 0) && pml4e_id < 512; ++pml4e_id) - { - // 剩余需要处理的pml4E -1 - --(pgt_num.num_PML4E); - - ul *pml4e_ptr = NULL; - pml4e_ptr = pml4_ptr + pml4e_id; - - // 二级页表不存在 - if (*pml4e_ptr == 0) - { - continue; - } - - uint64_t pdpte_id = (((virt_addr_start + length_unmapped) >> PAGE_1G_SHIFT) & 0x1ff); - uint64_t *pdpt_ptr = (uint64_t *)phys_2_virt(*pml4e_ptr & (~0xfffUL)); - // kdebug("pdpt_ptr=%#018lx", pdpt_ptr); - - // 循环处理二级页表 - for (; (pgt_num.num_PDPTE > 0) && pdpte_id < 512; ++pdpte_id) - { - --pgt_num.num_PDPTE; - uint64_t *pdpte_ptr = (pdpt_ptr + pdpte_id); - // kdebug("pgt_num.num_PDPTE=%ld pdpte_ptr=%#018lx", pgt_num.num_PDPTE, pdpte_ptr); - - // 三级页表为空 - if (*pdpte_ptr == 0) - { - continue; - } - - uint64_t pde_id = (((virt_addr_start + length_unmapped) >> PAGE_2M_SHIFT) & 0x1ff); - uint64_t *pd_ptr = (uint64_t *)phys_2_virt(*pdpte_ptr & (~0xfffUL)); - // kdebug("pd_ptr=%#018lx, *pd_ptr=%#018lx", pd_ptr, *pd_ptr); - - // 循环处理三级页表 - for (; (pgt_num.num_PDE > 0) && pde_id < 512; ++pde_id) - { - --pgt_num.num_PDE; - // 计算当前2M物理页对应的pdt的页表项的物理地址 - ul *pde_ptr = pd_ptr + pde_id; - - // 存在4级页表 - if (((*pde_ptr) & (1 << 7)) == 0) - { - // 存在4K页 - uint64_t pte_id = (((virt_addr_start + length_unmapped) >> PAGE_4K_SHIFT) & 0x1ff); - uint64_t *pt_ptr = (uint64_t *)phys_2_virt(*pde_ptr & (~0xfffUL)); - // 循环处理4K页表 - for (; pgt_num.num_PTE > 0 && pte_id < 512; ++pte_id) - { - uint64_t *pte_ptr = pt_ptr + pte_id; - --pgt_num.num_PTE; - *pte_ptr = 0; - length_unmapped += PAGE_4K_SIZE; - } - - // 4级页表已经空了,释放页表 - if (unlikely(mm_check_page_table(pt_ptr)) == 0) - { - *pde_ptr = 0; - kfree(pt_ptr); - } - } - else - { - *pde_ptr = 0; - length_unmapped += PAGE_2M_SIZE; - pgt_num.num_PTE -= 512; - } - } - - // 3级页表已经空了,释放页表 - if (unlikely(mm_check_page_table(pd_ptr)) == 0) - { - *pdpte_ptr = 0; - kfree(pd_ptr); - } - } - // 2级页表已经空了,释放页表 - if (unlikely(mm_check_page_table(pdpt_ptr)) == 0) - { - *pml4e_ptr = 0; - kfree(pdpt_ptr); - } - } - flush_tlb(); -} - -/** - * @brief 创建VMA - * - * @param mm 要绑定的内存空间分布结构体 - * @param vaddr 起始虚拟地址 - * @param length 长度(字节) - * @param vm_flags vma的标志 - * @param vm_ops vma的操作接口 - * @param res_vma 返回的vma指针 - * @return int 错误码 - */ -int mm_create_vma(struct mm_struct *mm, uint64_t vaddr, uint64_t length, vm_flags_t vm_flags, struct vm_operations_t *vm_ops, struct vm_area_struct **res_vma) -{ - int retval = 0; - // 输入的地址如果不是4K对齐,则报错 - if (unlikely(vaddr & (PAGE_4K_SIZE - 1))) - return -EINVAL; - - struct vm_area_struct *vma = vm_area_alloc(mm); - if (unlikely(vma == NULL)) - return -ENOMEM; - vma->vm_ops = vm_ops; - vma->vm_flags = vm_flags; - vma->vm_start = vaddr; - vma->vm_end = vaddr + length; - // 将VMA加入mm的链表 - retval = vma_insert(mm, vma); - if (retval == -EEXIST || retval == __VMA_MERGED) // 之前已经存在了相同的vma,直接返回 - { - *res_vma = vma_find(mm, vma->vm_start); - kfree(vma); - if (retval == -EEXIST) - return -EEXIST; - else - return 0; - } - - if (res_vma != NULL) - *res_vma = vma; - return 0; -} - -/** - * @brief 将指定的物理地址映射到指定的vma处 - * - * @param vma 要进行映射的VMA结构体 - * @param paddr 起始物理地址 - * @param offset 要映射的起始位置在vma中的偏移量 - * @param length 要映射的长度 - * @return int 错误码 - */ -int mm_map_vma(struct vm_area_struct *vma, uint64_t paddr, uint64_t offset, uint64_t length) -{ - int retval = 0; - uint64_t mapped = 0; - BUG_ON((offset & (PAGE_4K_SIZE - 1)) != 0); - length = PAGE_4K_ALIGN(length); // 将length按照4K进行对齐 - // 获取物理地址对应的页面 - struct Page *pg; - uint64_t page_flags = 0; - if (vma->vm_flags & VM_IO) // 对于mmio的内存,创建新的page结构体 - { - page_flags = PAGE_PWT | PAGE_PCD; - if (unlikely(vma->anon_vma == NULL || vma->anon_vma->page == NULL)) - pg = __create_mmio_page_struct(paddr); - else - pg = vma->anon_vma->page; - } - else - pg = Phy_to_2M_Page(paddr); - - if (unlikely(pg->anon_vma == NULL)) // 若页面不存在anon_vma,则为页面创建anon_vma - { - spin_lock(&pg->op_lock); - if (unlikely(pg->anon_vma == NULL)) - __anon_vma_create_alloc(pg, false); - spin_unlock(&pg->op_lock); - } - barrier(); - // 将anon vma与vma进行绑定 - __anon_vma_add(pg->anon_vma, vma); - barrier(); - // 长度超过界限 - BUG_ON(vma->vm_start + offset + length > vma->vm_end); - - /* - todo: 限制页面的读写权限 - */ - - // ==== 将地址映射到页表 ==== - uint64_t len_4k, len_2m; - // 将地址使用4k页填补,使得地址按照2M对齐 - len_4k = PAGE_2M_ALIGN(vma->vm_start + offset) - (vma->vm_start + offset); - if (len_4k > 0) - len_4k = (len_4k > length) ? length : len_4k; - if (len_4k) - { - if (vma->vm_flags & VM_USER) - page_flags |= PAGE_USER_4K_PAGE; - else - page_flags |= PAGE_KERNEL_4K_PAGE; - - // 这里直接设置user标志位为false,因为该函数内部会对其进行自动校正 - retval = mm_map_proc_page_table((uint64_t)vma->vm_mm->pgd, true, vma->vm_start + offset, paddr, len_4k, page_flags, false, false, true); - if (unlikely(retval != 0)) - goto failed; - - mapped += len_4k; - length -= len_4k; - } - - len_4k = length % PAGE_2M_SIZE; - len_2m = length / PAGE_2M_SIZE; - - // 映射连续的2M页 - if (likely(len_2m > 0)) - { - if (vma->vm_flags & VM_USER) - page_flags |= PAGE_USER_PAGE; - else - page_flags |= PAGE_KERNEL_PAGE; - // 这里直接设置user标志位为false,因为该函数内部会对其进行自动校正 - retval = mm_map_proc_page_table((uint64_t)vma->vm_mm->pgd, true, vma->vm_start + offset + mapped, paddr + mapped, len_2m, page_flags, false, false, false); - - if (unlikely(retval != 0)) - goto failed; - mapped += len_2m; - } - // 最后再使用4K页填补 - if (likely(len_4k > 0)) - { - - if (vma->vm_flags & VM_USER) - page_flags |= PAGE_USER_4K_PAGE; - else - page_flags |= PAGE_KERNEL_4K_PAGE; - - // 这里直接设置user标志位为false,因为该函数内部会对其进行自动校正 - retval = mm_map_proc_page_table((uint64_t)vma->vm_mm->pgd, true, vma->vm_start + offset + mapped, paddr + mapped, len_4k, page_flags, false, false, true); - - if (unlikely(retval != 0)) - goto failed; - mapped += len_4k; - } - - if (vma->vm_flags & VM_IO) - vma->page_offset = 0; - - flush_tlb(); - return 0; -failed:; - kdebug("map VMA failed."); - return retval; -} - -/** - * @brief 在页表中映射物理地址到指定的虚拟地址(需要页表中已存在对应的vma) - * - * @param mm 内存管理结构体 - * @param vaddr 虚拟地址 - * @param length 长度(字节) - * @param paddr 物理地址 - * @return int 返回码 - */ -int mm_map(struct mm_struct *mm, uint64_t vaddr, uint64_t length, uint64_t paddr) -{ - int retval = 0; - uint64_t offset = 0; - for (uint64_t mapped = 0; mapped < length;) - { - - struct vm_area_struct *vma = vma_find(mm, vaddr + mapped); - if (unlikely(vma == NULL)) - { - kerror("Map addr failed: vma not found. At address: %#018lx, pid=%ld", vaddr + mapped, current_pcb->pid); - return -EINVAL; - } - - // if (unlikely(vma->vm_start != (vaddr + mapped))) - // { - // kerror("Map addr failed: addr_start is not equal to current: %#018lx.", vaddr + mapped); - // return -EINVAL; - // } - - offset = vaddr + mapped - vma->vm_start; - uint64_t m_len = vma->vm_end - vma->vm_start - offset; - // kdebug("start=%#018lx, offset=%ld", vma->vm_start, offset); - retval = mm_map_vma(vma, paddr + mapped, offset, m_len); - if (unlikely(retval != 0)) - goto failed; - - mapped += m_len; - } - return 0; -failed:; - kerror("Map addr failed."); - return retval; -} - -/** - * @brief 在页表中取消指定的vma的映射 - * - * @param mm 指定的mm - * @param vma 待取消映射的vma - * @param paddr 返回的被取消映射的起始物理地址 - * @return int 返回码 - */ -int mm_unmap_vma(struct mm_struct *mm, struct vm_area_struct *vma, uint64_t *paddr) -{ - // 确保vma对应的mm与指定的mm相一致 - if (unlikely(vma->vm_mm != mm)) - return -EINVAL; - struct anon_vma_t *anon = vma->anon_vma; - if (paddr != NULL) - *paddr = __mm_get_paddr(mm, vma->vm_start); - if (anon == NULL) - kwarn("anon is NULL"); - semaphore_down(&anon->sem); - - mm_unmap_proc_table((uint64_t)mm->pgd, true, vma->vm_start, vma->vm_end - vma->vm_start); - __anon_vma_del(vma); - /** todo: 这里应该会存在bug,应修复。 - * 若anon_vma的等待队列上有其他的进程,由于anon_vma被释放 - * 这些在等待队列上的进程将无法被唤醒。 - */ - list_init(&vma->anon_vma_list); - - semaphore_up(&anon->sem); - - return 0; -} - -/** - * @brief 解除一段虚拟地址的映射(这些地址必须在vma中存在) - * - * @param mm 内存空间结构体 - * @param vaddr 起始地址 - * @param length 结束地址 - * @param destroy 是否释放vma结构体 - * @return int 错误码 - */ -int mm_unmap(struct mm_struct *mm, uint64_t vaddr, uint64_t length, bool destroy) -{ - int retval = 0; - for (uint64_t unmapped = 0; unmapped < length;) - { - struct vm_area_struct *vma = vma_find(mm, vaddr + unmapped); - if (unlikely(vma == NULL)) - { - kerror("Unmap addr failed: vma not found. At address: %#018lx, pid=%ld", vaddr + unmapped, current_pcb->pid); - return -EINVAL; - } - - if (unlikely(vma->vm_start != (vaddr + unmapped))) - { - kerror("Unmap addr failed: addr_start is not equal to current: %#018lx.", vaddr + unmapped); - return -EINVAL; - } - if (vma->anon_vma != NULL) - mm_unmap_vma(mm, vma, NULL); - - unmapped += vma->vm_end - vma->vm_start; - // 释放vma结构体 - if (destroy) - { - vm_area_del(vma); - vm_area_free(vma); - } - } - return 0; -failed:; - kerror("Unmap addr failed."); - return retval; -} diff --git a/kernel/src/mm/mmio.c b/kernel/src/mm/mmio.c deleted file mode 100644 index 3be277d0..00000000 --- a/kernel/src/mm/mmio.c +++ /dev/null @@ -1,9 +0,0 @@ -#include "mmio.h" -#include -extern void __mmio_buddy_init(); - -void mmio_init() -{ - __mmio_buddy_init(); - kinfo("mmio_init success"); -} diff --git a/kernel/src/mm/mmio.h b/kernel/src/mm/mmio.h index e7bdbd91..78706442 100644 --- a/kernel/src/mm/mmio.h +++ b/kernel/src/mm/mmio.h @@ -1,7 +1,5 @@ #pragma once #include "mm.h" -extern void mmio_buddy_init(); -extern void mmio_create(); +extern void mmio_create(uint32_t size, uint64_t vm_flagsu, uint64_t* res_vaddr, uint64_t* res_length); extern int mmio_release(int vaddr, int length); -void mmio_init(); diff --git a/kernel/src/mm/mmio_buddy.rs b/kernel/src/mm/mmio_buddy.rs index 53ea362c..71ca26fc 100644 --- a/kernel/src/mm/mmio_buddy.rs +++ b/kernel/src/mm/mmio_buddy.rs @@ -1,16 +1,19 @@ use crate::libs::spinlock::{SpinLock, SpinLockGuard}; +use crate::mm::kernel_mapper::KernelMapper; use crate::syscall::SystemError; use crate::{ arch::asm::current::current_pcb, - include::bindings::bindings::{ - initial_mm, mm_create_vma, mm_unmap, vm_area_del, vm_area_free, vm_area_struct, vm_flags_t, - vma_find, MMIO_BASE, MMIO_TOP, PAGE_1G_SHIFT, PAGE_1G_SIZE, PAGE_2M_SIZE, PAGE_4K_SHIFT, - PAGE_4K_SIZE, VM_DONTCOPY, VM_IO, - }, - kdebug, kerror, + include::bindings::bindings::{vm_flags_t, PAGE_1G_SHIFT, PAGE_4K_SHIFT, PAGE_4K_SIZE}, + kdebug, + mm::{MMArch, MemoryManagementArch}, }; -use alloc::{boxed::Box, collections::LinkedList, vec::Vec}; -use core::{mem, ptr::null_mut}; +use crate::{kerror, kinfo, kwarn}; +use alloc::{collections::LinkedList, vec::Vec}; +use core::mem; +use core::mem::MaybeUninit; +use core::sync::atomic::{compiler_fence, Ordering}; + +use super::VirtAddr; // 最大的伙伴块的幂 const MMIO_BUDDY_MAX_EXP: u32 = PAGE_1G_SHIFT; @@ -19,8 +22,15 @@ const MMIO_BUDDY_MIN_EXP: u32 = PAGE_4K_SHIFT; // 内存池数组的范围 const MMIO_BUDDY_REGION_COUNT: u32 = MMIO_BUDDY_MAX_EXP - MMIO_BUDDY_MIN_EXP + 1; -lazy_static! { - pub static ref MMIO_POOL: MmioBuddyMemPool = MmioBuddyMemPool::new(); +const MMIO_BASE: VirtAddr = VirtAddr::new(0xffffa10000000000); +const MMIO_TOP: VirtAddr = VirtAddr::new(0xffffa20000000000); + +const PAGE_1G_SIZE: usize = 1 << 30; + +static mut __MMIO_POOL: Option = None; + +pub fn mmio_pool() -> &'static mut MmioBuddyMemPool { + unsafe { __MMIO_POOL.as_mut().unwrap() } } pub enum MmioResult { @@ -32,25 +42,49 @@ pub enum MmioResult { } /// @brief buddy内存池 +#[derive(Debug)] pub struct MmioBuddyMemPool { - pool_start_addr: u64, - pool_size: u64, + pool_start_addr: VirtAddr, + pool_size: usize, free_regions: [SpinLock; MMIO_BUDDY_REGION_COUNT as usize], } -impl Default for MmioBuddyMemPool { - fn default() -> Self { - MmioBuddyMemPool { - pool_start_addr: MMIO_BASE as u64, - pool_size: (MMIO_TOP - MMIO_BASE) as u64, - free_regions: unsafe { mem::zeroed() }, - } - } -} + impl MmioBuddyMemPool { fn new() -> Self { - return MmioBuddyMemPool { - ..Default::default() + let mut free_regions: [MaybeUninit>; + MMIO_BUDDY_REGION_COUNT as usize] = unsafe { MaybeUninit::uninit().assume_init() }; + for i in 0..MMIO_BUDDY_REGION_COUNT { + free_regions[i as usize] = MaybeUninit::new(SpinLock::new(MmioFreeRegionList::new())); + } + let free_regions = unsafe { + mem::transmute::<_, [SpinLock; MMIO_BUDDY_REGION_COUNT as usize]>( + free_regions, + ) }; + + let pool = MmioBuddyMemPool { + pool_start_addr: MMIO_BASE, + pool_size: MMIO_TOP - MMIO_BASE, + free_regions, + }; + kdebug!("MMIO buddy pool init: created"); + + let cnt_1g_blocks = (MMIO_TOP - MMIO_BASE) >> 30; + let mut vaddr_base = MMIO_BASE; + kdebug!("total 1G blocks: {cnt_1g_blocks}"); + for _i in 0..cnt_1g_blocks { + compiler_fence(Ordering::SeqCst); + match pool.give_back_block(vaddr_base, PAGE_1G_SHIFT) { + Ok(_) => { + vaddr_base += PAGE_1G_SIZE; + } + Err(_) => { + panic!("MMIO buddy pool init failed"); + } + } + } + kdebug!("MMIO buddy pool init success"); + return pool; } /// @brief 创建新的地址区域结构体 @@ -58,9 +92,12 @@ impl MmioBuddyMemPool { /// @param vaddr 虚拟地址 /// /// @return 创建好的地址区域结构体 - fn create_region(&self, vaddr: u64) -> Box { - let mut region: Box = Box::new(MmioBuddyAddrRegion::new()); - region.vaddr = vaddr; + fn create_region(&self, vaddr: VirtAddr) -> MmioBuddyAddrRegion { + // kdebug!("create_region for vaddr: {vaddr:?}"); + + let region: MmioBuddyAddrRegion = MmioBuddyAddrRegion::new(vaddr); + + // kdebug!("create_region for vaddr: {vaddr:?} OK!!!"); return region; } @@ -75,16 +112,16 @@ impl MmioBuddyMemPool { /// @return Ok(i32) 返回0 /// /// @return Err(SystemError) 返回错误码 - fn give_back_block(&self, vaddr: u64, exp: u32) -> Result { + fn give_back_block(&self, vaddr: VirtAddr, exp: u32) -> Result { // 确保内存对齐,低位都要为0 - if (vaddr & ((1 << exp) - 1)) != 0 { + if (vaddr.data() & ((1 << exp) - 1)) != 0 { return Err(SystemError::EINVAL); } - let region: Box = self.create_region(vaddr); + let region: MmioBuddyAddrRegion = self.create_region(vaddr); // 加入buddy - let list_guard: &mut SpinLockGuard = - &mut self.free_regions[exp2index(exp)].lock(); - self.push_block(region, list_guard); + let mut list_guard = self.free_regions[exp2index(exp)].lock(); + + self.push_block(region, &mut list_guard); return Ok(0); } @@ -97,12 +134,12 @@ impl MmioBuddyMemPool { /// @param list_guard 【exp-1】对应的链表 fn split_block( &self, - region: Box, + region: MmioBuddyAddrRegion, exp: u32, low_list_guard: &mut SpinLockGuard, ) { - let vaddr: u64 = self.calculate_block_vaddr(region.vaddr, exp - 1); - let new_region: Box = self.create_region(vaddr); + let vaddr = self.calculate_block_vaddr(region.vaddr, exp - 1); + let new_region: MmioBuddyAddrRegion = self.create_region(vaddr); self.push_block(region, low_list_guard); self.push_block(new_region, low_list_guard); } @@ -113,7 +150,7 @@ impl MmioBuddyMemPool { /// /// @param list_guard exp对应的链表 /// - /// @return Ok(Box) 符合要求的内存区域。 + /// @return Ok(MmioBuddyAddrRegion) 符合要求的内存区域。 /// /// @return Err(MmioResult) /// - 没有满足要求的内存块时,返回ENOFOUND @@ -123,7 +160,7 @@ impl MmioBuddyMemPool { &self, exp: u32, list_guard: &mut SpinLockGuard, - ) -> Result, MmioResult> { + ) -> Result { // 申请范围错误 if exp < MMIO_BUDDY_MIN_EXP || exp > MMIO_BUDDY_MAX_EXP { kdebug!("query_addr_region: exp wrong"); @@ -256,12 +293,9 @@ impl MmioBuddyMemPool { /// /// @param exp 内存区域的大小(2^exp) /// - /// @return Ok(Box)符合要求的内存块信息结构体。 + /// @return Ok(MmioBuddyAddrRegion)符合要求的内存块信息结构体。 /// @return Err(MmioResult) 没有满足要求的内存块时,返回__query_addr_region的错误码。 - fn mmio_buddy_query_addr_region( - &self, - exp: u32, - ) -> Result, MmioResult> { + fn mmio_buddy_query_addr_region(&self, exp: u32) -> Result { let list_guard: &mut SpinLockGuard = &mut self.free_regions[exp2index(exp)].lock(); match self.query_addr_region(exp, list_guard) { @@ -279,7 +313,7 @@ impl MmioBuddyMemPool { /// @param list_guard 目标链表 fn push_block( &self, - region: Box, + region: MmioBuddyAddrRegion, list_guard: &mut SpinLockGuard, ) { list_guard.list.push_back(region); @@ -288,8 +322,8 @@ impl MmioBuddyMemPool { /// @brief 根据地址和内存块大小,计算伙伴块虚拟内存的地址 #[inline(always)] - fn calculate_block_vaddr(&self, vaddr: u64, exp: u32) -> u64 { - return vaddr ^ (1 << exp); + fn calculate_block_vaddr(&self, vaddr: VirtAddr, exp: u32) -> VirtAddr { + return VirtAddr::new(vaddr.data() ^ (1 << exp as usize)); } /// @brief 寻找并弹出指定内存块的伙伴块 @@ -306,10 +340,10 @@ impl MmioBuddyMemPool { /// - 没有找到伙伴块,返回ENOFOUND fn pop_buddy_block( &self, - vaddr: u64, + vaddr: VirtAddr, exp: u32, list_guard: &mut SpinLockGuard, - ) -> Result, MmioResult> { + ) -> Result { if list_guard.list.len() == 0 { return Err(MmioResult::ISEMPTY); } else { @@ -317,7 +351,7 @@ impl MmioBuddyMemPool { let buddy_vaddr = self.calculate_block_vaddr(vaddr, exp); // element 只会有一个元素 - let mut element: Vec> = list_guard + let mut element: Vec = list_guard .list .drain_filter(|x| x.vaddr == buddy_vaddr) .collect(); @@ -335,13 +369,13 @@ impl MmioBuddyMemPool { /// /// @param list_guard 【exp】对应的链表 /// - /// @return Ok(Box) 内存块信息结构体的引用。 + /// @return Ok(MmioBuddyAddrRegion) 内存块信息结构体的引用。 /// /// @return Err(MmioResult) 当链表为空,无法删除时,返回ISEMPTY fn pop_block( &self, list_guard: &mut SpinLockGuard, - ) -> Result, MmioResult> { + ) -> Result { if !list_guard.list.is_empty() { list_guard.num_free -= 1; return Ok(list_guard.list.pop_back().unwrap()); @@ -377,17 +411,15 @@ impl MmioBuddyMemPool { break; } // 获取内存块 - let vaddr: u64 = list_guard.list.back().unwrap().vaddr; + let vaddr: VirtAddr = list_guard.list.back().unwrap().vaddr; // 获取伙伴内存块 match self.pop_buddy_block(vaddr, exp, list_guard) { Err(err) => { return Err(err); } Ok(buddy_region) => { - let region: Box = list_guard.list.pop_back().unwrap(); - let copy_region: Box = Box::new(MmioBuddyAddrRegion { - vaddr: region.vaddr, - }); + let region: MmioBuddyAddrRegion = list_guard.list.pop_back().unwrap(); + let copy_region = region.clone(); // 在两块内存都被取出之后才进行合并 match self.merge_blocks(region, buddy_region, exp, high_list_guard) { Err(err) => { @@ -415,8 +447,8 @@ impl MmioBuddyMemPool { /// @return Err(MmioResult) 两个内存块不是伙伴块,返回EINVAL fn merge_blocks( &self, - region_1: Box, - region_2: Box, + region_1: MmioBuddyAddrRegion, + region_2: MmioBuddyAddrRegion, exp: u32, high_list_guard: &mut SpinLockGuard, ) -> Result { @@ -444,102 +476,43 @@ impl MmioBuddyMemPool { /// @return Err(SystemError) 失败返回错误码 pub fn create_mmio( &self, - size: u32, - vm_flags: vm_flags_t, + size: usize, + _vm_flags: vm_flags_t, res_vaddr: *mut u64, res_length: *mut u64, ) -> Result { if size > PAGE_1G_SIZE || size == 0 { return Err(SystemError::EPERM); } - let mut retval: i32 = 0; + let retval: i32 = 0; // 计算前导0 - let mut size_exp: u32 = 31 - size.leading_zeros(); + #[cfg(target_arch = "x86_64")] + let mut size_exp: u32 = 63 - size.leading_zeros(); // 记录最终申请的空间大小 - let mut new_size: u32 = size; + let mut new_size = size; // 对齐要申请的空间大小 // 如果要申请的空间大小小于4k,则分配4k if size_exp < PAGE_4K_SHIFT { - new_size = PAGE_4K_SIZE; + new_size = PAGE_4K_SIZE as usize; size_exp = PAGE_4K_SHIFT; } else if (new_size & (!(1 << size_exp))) != 0 { // 向左对齐空间大小 size_exp += 1; new_size = 1 << size_exp; } - match MMIO_POOL.mmio_buddy_query_addr_region(size_exp) { + match self.mmio_buddy_query_addr_region(size_exp) { Ok(region) => { - unsafe { - *res_vaddr = region.vaddr; - *res_length = new_size as u64; - } - // 创建vma - let flags: u64 = vm_flags | (VM_IO | VM_DONTCOPY) as u64; - let len_4k: u64 = (new_size % PAGE_2M_SIZE) as u64; - let len_2m: u64 = new_size as u64 - len_4k; - let mut loop_i: u64 = 0; - // 先分配2M的vma - loop { - if loop_i >= len_2m { - break; - } - let vma: *mut *mut vm_area_struct = null_mut(); - retval = unsafe { - mm_create_vma( - &mut initial_mm, - region.vaddr + loop_i, - PAGE_2M_SIZE.into(), - flags, - null_mut(), - vma, - ) - }; - if retval != 0 { - kdebug!( - "failed to create mmio 2m vma. pid = {:?}", - current_pcb().pid - ); - unsafe { - vm_area_del(*vma); - vm_area_free(*vma); - } - return Err(SystemError::from_posix_errno(retval).unwrap()); - } - loop_i += PAGE_2M_SIZE as u64; - } - // 分配4K的vma - loop_i = len_2m; - loop { - if loop_i >= size as u64 { - break; - } - let vma: *mut *mut vm_area_struct = null_mut(); - retval = unsafe { - mm_create_vma( - &mut initial_mm, - region.vaddr + loop_i, - PAGE_4K_SIZE.into(), - flags, - null_mut(), - vma, - ) - }; - if retval != 0 { - kdebug!( - "failed to create mmio 4k vma. pid = {:?}", - current_pcb().pid - ); - unsafe { - vm_area_del(*vma); - vm_area_free(*vma); - } - return Err(SystemError::from_posix_errno(retval).unwrap()); - } - loop_i += PAGE_4K_SIZE as u64; - } + // todo: 是否需要创建vma?或者用新重写的机制去做? + // kdebug!( + // "create_mmio: vaddr = {:?}, length = {}", + // region.vaddr, + // new_size + // ); + unsafe { *res_vaddr = region.vaddr.data() as u64 }; + unsafe { *res_length = new_size as u64 }; } Err(_) => { - kdebug!("failed to create mmio vma.pid = {:?}", current_pcb().pid); + kerror!("failed to create mmio. pid = {:?}", current_pcb().pid); return Err(SystemError::ENOMEM); } } @@ -555,83 +528,62 @@ impl MmioBuddyMemPool { /// @return Ok(i32) 成功返回0 /// /// @return Err(SystemError) 失败返回错误码 - pub fn release_mmio(&self, vaddr: u64, length: u64) -> Result { - //先将要释放的空间取消映射 - unsafe { - mm_unmap(&mut initial_mm, vaddr, length, false); + pub fn release_mmio(&self, vaddr: VirtAddr, length: usize) -> Result { + assert!(vaddr.check_aligned(MMArch::PAGE_SIZE)); + assert!(length & (MMArch::PAGE_SIZE - 1) == 0); + if vaddr < self.pool_start_addr + || vaddr.data() >= self.pool_start_addr.data() + self.pool_size + { + return Err(SystemError::EINVAL); } - let mut loop_i: u64 = 0; - loop { - if loop_i >= length { - break; - } - // 获取要释放的vma的结构体 - let vma: *mut vm_area_struct = unsafe { vma_find(&mut initial_mm, vaddr + loop_i) }; - if vma == null_mut() { - kdebug!( - "mmio_release failed: vma not found. At address: {:?}, pid = {:?}", - vaddr + loop_i, - current_pcb().pid - ); - return Err(SystemError::EINVAL); - } - // 检查vma起始地址是否正确 - if unsafe { (*vma).vm_start != (vaddr + loop_i) } { - kdebug!( - "mmio_release failed: addr_start is not equal to current: {:?}. pid = {:?}", - vaddr + loop_i, - current_pcb().pid - ); - return Err(SystemError::EINVAL); - } - // 将vma对应空间归还 - match MMIO_POOL.give_back_block(unsafe { (*vma).vm_start }, unsafe { - 31 - ((*vma).vm_end - (*vma).vm_start).leading_zeros() - }) { - Ok(_) => { - loop_i += unsafe { (*vma).vm_end - (*vma).vm_start }; - unsafe { - vm_area_del(vma); - vm_area_free(vma); - } - } - Err(err) => { - // vma对应空间没有成功归还的话,就不删除vma - kdebug!( - "mmio_release give_back failed: pid = {:?}", - current_pcb().pid - ); - return Err(err); - } - } + // todo: 重构MMIO管理机制,创建类似全局的manager之类的,管理MMIO的空间? + + // 暂时认为传入的vaddr都是正确的 + let page_count = length / MMArch::PAGE_SIZE; + // 取消映射 + let mut bindings = KernelMapper::lock(); + let mut kernel_mapper = bindings.as_mut(); + if kernel_mapper.is_none() { + kwarn!("release_mmio: kernel_mapper is read only"); + return Err(SystemError::EAGAIN_OR_EWOULDBLOCK); } + + for i in 0..page_count { + unsafe { + kernel_mapper + .as_mut() + .unwrap() + .unmap(vaddr + i * MMArch::PAGE_SIZE, true) + }; + } + + // todo: 归还到buddy + return Ok(0); } } /// @brief mmio伙伴系统内部的地址区域结构体 -pub struct MmioBuddyAddrRegion { - vaddr: u64, +#[derive(Debug, Clone)] +struct MmioBuddyAddrRegion { + vaddr: VirtAddr, } impl MmioBuddyAddrRegion { - pub fn new() -> Self { - return MmioBuddyAddrRegion { - ..Default::default() - }; + pub fn new(vaddr: VirtAddr) -> Self { + return MmioBuddyAddrRegion { vaddr }; } -} -impl Default for MmioBuddyAddrRegion { - fn default() -> Self { - MmioBuddyAddrRegion { - vaddr: Default::default(), - } + + #[allow(dead_code)] + pub fn vaddr(&self) -> VirtAddr { + return self.vaddr; } } /// @brief 空闲页数组结构体 +#[derive(Debug)] pub struct MmioFreeRegionList { /// 存储mmio_buddy的地址链表 - list: LinkedList>, + list: LinkedList, /// 空闲块的数量 num_free: i64, } @@ -652,25 +604,6 @@ impl Default for MmioFreeRegionList { } } -/// @brief 初始化mmio的伙伴系统 -#[no_mangle] -pub extern "C" fn __mmio_buddy_init() { - // 创建一堆1GB的地址块 - let cnt_1g_blocks: u32 = ((MMIO_TOP - MMIO_BASE) / PAGE_1G_SIZE as i64) as u32; - let mut vaddr_base: u64 = MMIO_BASE as u64; - for _ in 0..cnt_1g_blocks { - match MMIO_POOL.give_back_block(vaddr_base, PAGE_1G_SHIFT) { - Ok(_) => { - vaddr_base += PAGE_1G_SIZE as u64; - } - Err(_) => { - kerror!("__mmio_buddy_init failed"); - return; - } - } - } -} - /// @brief 将内存对象大小的幂转换成内存池中的数组的下标 /// /// @param exp内存大小 @@ -681,6 +614,15 @@ fn exp2index(exp: u32) -> usize { return (exp - 12) as usize; } +pub fn mmio_init() { + kdebug!("Initializing MMIO buddy memory pool..."); + // 初始化mmio内存池 + unsafe { + __MMIO_POOL = Some(MmioBuddyMemPool::new()); + } + + kinfo!("MMIO buddy memory pool init done"); +} /// @brief 创建一块mmio区域,并将vma绑定到initial_mm /// /// @param size mmio区域的大小(字节) @@ -699,7 +641,8 @@ pub extern "C" fn mmio_create( res_vaddr: *mut u64, res_length: *mut u64, ) -> i32 { - if let Err(err) = MMIO_POOL.create_mmio(size, vm_flags, res_vaddr, res_length) { + // kdebug!("mmio_create"); + if let Err(err) = mmio_pool().create_mmio(size as usize, vm_flags, res_vaddr, res_length) { return err.to_posix_errno(); } else { return 0; @@ -717,9 +660,7 @@ pub extern "C" fn mmio_create( /// @return Err(i32) 失败返回错误码 #[no_mangle] pub extern "C" fn mmio_release(vaddr: u64, length: u64) -> i32 { - if let Err(err) = MMIO_POOL.release_mmio(vaddr, length) { - return err.to_posix_errno(); - } else { - return 0; - } + return mmio_pool() + .release_mmio(VirtAddr::new(vaddr as usize), length as usize) + .unwrap_or_else(|err| err.to_posix_errno()); } diff --git a/kernel/src/mm/mod.rs b/kernel/src/mm/mod.rs index 69f36ea5..8364e3c9 100644 --- a/kernel/src/mm/mod.rs +++ b/kernel/src/mm/mod.rs @@ -1,9 +1,63 @@ -use crate::include::bindings::bindings::{mm_struct, process_control_block, PAGE_OFFSET}; +use alloc::sync::Arc; + +use crate::{ + arch::MMArch, + include::bindings::bindings::{process_control_block, PAGE_OFFSET}, + syscall::SystemError, +}; + +use core::{ + cmp, + fmt::Debug, + intrinsics::unlikely, + ops::{Add, AddAssign, Sub, SubAssign}, + ptr, + sync::atomic::{AtomicBool, Ordering}, +}; + +use self::{ + allocator::page_frame::{VirtPageFrame, VirtPageFrameIter}, + page::round_up_to_page_size, + ucontext::{AddressSpace, UserMapper}, +}; pub mod allocator; +pub mod c_adapter; pub mod gfp; +pub mod kernel_mapper; pub mod mmio_buddy; +pub mod no_init; +pub mod page; pub mod syscall; +pub mod ucontext; + +/// 内核INIT进程的用户地址空间结构体(仅在process_init中初始化) +static mut __INITIAL_PROCESS_ADDRESS_SPACE: Option> = None; + +/// 获取内核INIT进程的用户地址空间结构体 +#[allow(non_snake_case)] +#[inline(always)] +pub fn INITIAL_PROCESS_ADDRESS_SPACE() -> Arc { + unsafe { + return __INITIAL_PROCESS_ADDRESS_SPACE + .as_ref() + .expect("INITIAL_PROCESS_ADDRESS_SPACE is null") + .clone(); + } +} + +/// 设置内核INIT进程的用户地址空间结构体全局变量 +#[allow(non_snake_case)] +pub unsafe fn set_INITIAL_PROCESS_ADDRESS_SPACE(address_space: Arc) { + static INITIALIZED: AtomicBool = AtomicBool::new(false); + if INITIALIZED + .compare_exchange(false, true, Ordering::SeqCst, Ordering::Acquire) + .is_err() + { + panic!("INITIAL_PROCESS_ADDRESS_SPACE is already initialized"); + } + __INITIAL_PROCESS_ADDRESS_SPACE = Some(address_space); +} /// @brief 将内核空间的虚拟地址转换为物理地址 #[inline(always)] @@ -17,10 +71,561 @@ pub fn phys_2_virt(addr: usize) -> usize { addr + PAGE_OFFSET as usize } -// ====== 重构内存管理后,请删除18-24行 ====== +#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)] +pub enum PageTableKind { + /// 用户可访问的页表 + User, + /// 内核页表 + Kernel, +} + +/// 物理内存地址 +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)] +#[repr(transparent)] +pub struct PhysAddr(usize); + +impl PhysAddr { + #[inline(always)] + pub const fn new(address: usize) -> Self { + Self(address) + } + + /// @brief 获取物理地址的值 + #[inline(always)] + pub fn data(&self) -> usize { + self.0 + } + + /// @brief 将物理地址加上一个偏移量 + #[inline(always)] + pub fn add(self, offset: usize) -> Self { + Self(self.0 + offset) + } + + /// @brief 判断物理地址是否按照指定要求对齐 + #[inline(always)] + pub fn check_aligned(&self, align: usize) -> bool { + return self.0 & (align - 1) == 0; + } + + #[inline(always)] + pub fn is_null(&self) -> bool { + return self.0 == 0; + } +} + +impl Debug for PhysAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "PhysAddr({:#x})", self.0) + } +} + +impl core::ops::Add for PhysAddr { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: usize) -> Self::Output { + return Self(self.0 + rhs); + } +} + +impl core::ops::AddAssign for PhysAddr { + #[inline(always)] + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +impl core::ops::Add for PhysAddr { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: PhysAddr) -> Self::Output { + return Self(self.0 + rhs.0); + } +} + +impl core::ops::AddAssign for PhysAddr { + #[inline(always)] + fn add_assign(&mut self, rhs: PhysAddr) { + self.0 += rhs.0; + } +} + +impl core::ops::Sub for PhysAddr { + type Output = Self; + + #[inline(always)] + fn sub(self, rhs: usize) -> Self::Output { + return Self(self.0 - rhs); + } +} + +impl core::ops::SubAssign for PhysAddr { + #[inline(always)] + fn sub_assign(&mut self, rhs: usize) { + self.0 -= rhs; + } +} + +impl core::ops::Sub for PhysAddr { + type Output = usize; + + #[inline(always)] + fn sub(self, rhs: PhysAddr) -> Self::Output { + return self.0 - rhs.0; + } +} + +impl core::ops::SubAssign for PhysAddr { + #[inline(always)] + fn sub_assign(&mut self, rhs: PhysAddr) { + self.0 -= rhs.0; + } +} + +/// 虚拟内存地址 +#[derive(Clone, Copy, Eq, Ord, PartialEq, PartialOrd, Hash)] +#[repr(transparent)] +pub struct VirtAddr(usize); + +impl VirtAddr { + #[inline(always)] + pub const fn new(address: usize) -> Self { + return Self(address); + } + + /// @brief 获取虚拟地址的值 + #[inline(always)] + pub fn data(&self) -> usize { + return self.0; + } + + /// @brief 判断虚拟地址的类型 + #[inline(always)] + pub fn kind(&self) -> PageTableKind { + if self.check_user() { + return PageTableKind::User; + } else { + return PageTableKind::Kernel; + } + } + + /// @brief 判断虚拟地址是否按照指定要求对齐 + #[inline(always)] + pub fn check_aligned(&self, align: usize) -> bool { + return self.0 & (align - 1) == 0; + } + + /// @brief 判断虚拟地址是否在用户空间 + #[inline(always)] + pub fn check_user(&self) -> bool { + if self < &MMArch::USER_END_VADDR { + return true; + } else { + return false; + } + } + + #[inline(always)] + pub fn as_ptr(self) -> *mut T { + return self.0 as *mut T; + } + + #[inline(always)] + pub fn is_null(&self) -> bool { + return self.0 == 0; + } +} + +impl Add for VirtAddr { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: VirtAddr) -> Self::Output { + return Self(self.0 + rhs.0); + } +} + +impl Add for VirtAddr { + type Output = Self; + + #[inline(always)] + fn add(self, rhs: usize) -> Self::Output { + return Self(self.0 + rhs); + } +} + +impl Sub for VirtAddr { + type Output = usize; + + #[inline(always)] + fn sub(self, rhs: VirtAddr) -> Self::Output { + return self.0 - rhs.0; + } +} + +impl Sub for VirtAddr { + type Output = Self; + + #[inline(always)] + fn sub(self, rhs: usize) -> Self::Output { + return Self(self.0 - rhs); + } +} + +impl AddAssign for VirtAddr { + #[inline(always)] + fn add_assign(&mut self, rhs: usize) { + self.0 += rhs; + } +} + +impl AddAssign for VirtAddr { + #[inline(always)] + fn add_assign(&mut self, rhs: VirtAddr) { + self.0 += rhs.0; + } +} + +impl SubAssign for VirtAddr { + #[inline(always)] + fn sub_assign(&mut self, rhs: usize) { + self.0 -= rhs; + } +} + +impl SubAssign for VirtAddr { + #[inline(always)] + fn sub_assign(&mut self, rhs: VirtAddr) { + self.0 -= rhs.0; + } +} + +impl Debug for VirtAddr { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "VirtAddr({:#x})", self.0) + } +} + +/// @brief 物理内存区域 +#[derive(Clone, Copy, Debug)] +pub struct PhysMemoryArea { + /// 物理基地址 + pub base: PhysAddr, + /// 该区域的物理内存大小 + pub size: usize, +} + +pub trait MemoryManagementArch: Clone + Copy + Debug { + /// 页面大小的shift(假如页面4K,那么这个值就是12,因为2^12=4096) + const PAGE_SHIFT: usize; + /// 每个页表的页表项数目。(以2^n次幂来表示)假如有512个页表项,那么这个值就是9 + const PAGE_ENTRY_SHIFT: usize; + /// 页表层级数量 + const PAGE_LEVELS: usize; + + /// 页表项的有效位的index(假如页表项的第0-51位有效,那么这个值就是52) + const ENTRY_ADDRESS_SHIFT: usize; + /// 页面的页表项的默认值 + const ENTRY_FLAG_DEFAULT_PAGE: usize; + /// 页表的页表项的默认值 + const ENTRY_FLAG_DEFAULT_TABLE: usize; + /// 页表项的present位被置位之后的值 + const ENTRY_FLAG_PRESENT: usize; + /// 页表项为read only时的值 + const ENTRY_FLAG_READONLY: usize; + /// 页表项为可读写状态的值 + const ENTRY_FLAG_READWRITE: usize; + /// 页面项标记页面为user page的值 + const ENTRY_FLAG_USER: usize; + /// 页面项标记页面为write through的值 + const ENTRY_FLAG_WRITE_THROUGH: usize; + /// 页面项标记页面为cache disable的值 + const ENTRY_FLAG_CACHE_DISABLE: usize; + /// 标记当前页面不可执行的标志位(Execute disable)(也就是说,不能从这段内存里面获取处理器指令) + const ENTRY_FLAG_NO_EXEC: usize; + /// 标记当前页面可执行的标志位(Execute enable) + const ENTRY_FLAG_EXEC: usize; + + /// 虚拟地址与物理地址的偏移量 + const PHYS_OFFSET: usize; + + /// 每个页面的大小 + const PAGE_SIZE: usize = 1 << Self::PAGE_SHIFT; + /// 通过这个mask,获取地址的页内偏移量 + const PAGE_OFFSET_MASK: usize = Self::PAGE_SIZE - 1; + /// 页表项的地址、数据部分的shift。 + /// 打个比方,如果这个值为52,那么意味着页表项的[0, 52)位,用于表示地址以及其他的标志位 + const PAGE_ADDRESS_SHIFT: usize = Self::PAGE_LEVELS * Self::PAGE_ENTRY_SHIFT + Self::PAGE_SHIFT; + /// 最大的虚拟地址(对于不同的架构,由于上述PAGE_ADDRESS_SHIFT可能包括了reserved bits, 事实上能表示的虚拟地址应该比这个值要小) + const PAGE_ADDRESS_SIZE: usize = 1 << Self::PAGE_ADDRESS_SHIFT; + /// 页表项的值与这个常量进行与运算,得到的结果是所填写的物理地址 + const PAGE_ADDRESS_MASK: usize = Self::PAGE_ADDRESS_SIZE - Self::PAGE_SIZE; + /// 每个页表项的大小 + const PAGE_ENTRY_SIZE: usize = 1 << (Self::PAGE_SHIFT - Self::PAGE_ENTRY_SHIFT); + /// 每个页表的页表项数目 + const PAGE_ENTRY_NUM: usize = 1 << Self::PAGE_ENTRY_SHIFT; + /// 该字段用于根据虚拟地址,获取该虚拟地址在对应的页表中是第几个页表项 + const PAGE_ENTRY_MASK: usize = Self::PAGE_ENTRY_NUM - 1; + + const PAGE_NEGATIVE_MASK: usize = !((Self::PAGE_ADDRESS_SIZE) - 1); + + const ENTRY_ADDRESS_SIZE: usize = 1 << Self::ENTRY_ADDRESS_SHIFT; + /// 该mask用于获取页表项中地址字段 + const ENTRY_ADDRESS_MASK: usize = Self::ENTRY_ADDRESS_SIZE - Self::PAGE_SIZE; + /// 这个mask用于获取页表项中的flags + const ENTRY_FLAGS_MASK: usize = !Self::ENTRY_ADDRESS_MASK; + + /// 用户空间的最高地址 + const USER_END_VADDR: VirtAddr; + /// 用户堆的起始地址 + const USER_BRK_START: VirtAddr; + /// 用户栈起始地址(向下生长,不包含该值) + const USER_STACK_START: VirtAddr; + + /// @brief 用于初始化内存管理模块与架构相关的信息。 + /// 该函数应调用其他模块的接口,生成内存区域结构体,提供给BumpAllocator使用 + unsafe fn init() -> &'static [PhysMemoryArea]; + + /// @brief 读取指定虚拟地址的值,并假设它是类型T的指针 + #[inline(always)] + unsafe fn read(address: VirtAddr) -> T { + return ptr::read(address.data() as *const T); + } + + /// @brief 将value写入到指定的虚拟地址 + #[inline(always)] + unsafe fn write(address: VirtAddr, value: T) { + ptr::write(address.data() as *mut T, value); + } + + #[inline(always)] + unsafe fn write_bytes(address: VirtAddr, value: u8, count: usize) { + ptr::write_bytes(address.data() as *mut u8, value, count); + } + + /// @brief 刷新TLB中,关于指定虚拟地址的条目 + unsafe fn invalidate_page(address: VirtAddr); + + /// @brief 刷新TLB中,所有的条目 + unsafe fn invalidate_all(); + + /// @brief 获取顶级页表的物理地址 + unsafe fn table(table_kind: PageTableKind) -> PhysAddr; + + /// @brief 设置顶级页表的物理地址到处理器中 + unsafe fn set_table(table_kind: PageTableKind, table: PhysAddr); + + /// @brief 将物理地址转换为虚拟地址. + /// + /// @param phys 物理地址 + /// + /// @return 转换后的虚拟地址。如果转换失败,返回None + #[inline(always)] + unsafe fn phys_2_virt(phys: PhysAddr) -> Option { + if let Some(vaddr) = phys.data().checked_add(Self::PHYS_OFFSET) { + return Some(VirtAddr::new(vaddr)); + } else { + return None; + } + } + + /// 将虚拟地址转换为物理地址 + /// + /// ## 参数 + /// + /// - `virt` 虚拟地址 + /// + /// ## 返回值 + /// + /// 转换后的物理地址。如果转换失败,返回None + #[inline(always)] + unsafe fn virt_2_phys(virt: VirtAddr) -> Option { + if let Some(paddr) = virt.data().checked_sub(Self::PHYS_OFFSET) { + return Some(PhysAddr::new(paddr)); + } else { + return None; + } + } + + /// @brief 判断指定的虚拟地址是否正确(符合规范) + fn virt_is_valid(virt: VirtAddr) -> bool; + + /// 获取内存管理初始化时,创建的第一个内核页表的地址 + fn initial_page_table() -> PhysAddr; + + /// 初始化新的usermapper,为用户进程创建页表 + fn setup_new_usermapper() -> Result; +} + +/// @brief 虚拟地址范围 +/// 该结构体用于表示一个虚拟地址范围,包括起始地址与大小 +/// +/// 请注意与VMA进行区分,该结构体被VMA所包含 +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct VirtRegion { + start: VirtAddr, + size: usize, +} + +#[allow(dead_code)] +impl VirtRegion { + /// # 创建一个新的虚拟地址范围 + pub fn new(start: VirtAddr, size: usize) -> Self { + VirtRegion { start, size } + } + + /// 获取虚拟地址范围的起始地址 + #[inline(always)] + pub fn start(&self) -> VirtAddr { + self.start + } + + /// 获取虚拟地址范围的截止地址(不包括返回的地址) + #[inline(always)] + pub fn end(&self) -> VirtAddr { + return self.start().add(self.size); + } + + /// # Create a new VirtRegion from a range [start, end) + /// + /// If end <= start, return None + pub fn between(start: VirtAddr, end: VirtAddr) -> Option { + if unlikely(end.data() <= start.data()) { + return None; + } + let size = end.data() - start.data(); + return Some(VirtRegion::new(start, size)); + } + + /// # 取两个虚拟地址范围的交集 + /// + /// 如果两个虚拟地址范围没有交集,返回None + pub fn intersect(&self, other: &VirtRegion) -> Option { + let start = self.start.max(other.start); + let end = self.end().min(other.end()); + return VirtRegion::between(start, end); + } + + /// 设置虚拟地址范围的起始地址 + #[inline(always)] + pub fn set_start(&mut self, start: VirtAddr) { + self.start = start; + } + + #[inline(always)] + pub fn size(&self) -> usize { + self.size + } + + /// 设置虚拟地址范围的大小 + #[inline(always)] + pub fn set_size(&mut self, size: usize) { + self.size = size; + } + + /// 判断虚拟地址范围是否为空 + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.size == 0 + } + + /// 将虚拟地址区域的大小向上对齐到页大小 + #[inline(always)] + pub fn round_up_size_to_page(self) -> Self { + return VirtRegion::new(self.start, round_up_to_page_size(self.size)); + } + + /// 判断两个虚拟地址范围是否由于具有交集而导致冲突 + #[inline(always)] + pub fn collide(&self, other: &VirtRegion) -> bool { + return self.intersect(other).is_some(); + } + + pub fn iter_pages(&self) -> VirtPageFrameIter { + return VirtPageFrame::iter_range( + VirtPageFrame::new(self.start), + VirtPageFrame::new(self.end()), + ); + } + + /// 获取[self.start(), region.start())的虚拟地址范围 + /// + /// 如果self.start() >= region.start(),返回None + pub fn before(self, region: &VirtRegion) -> Option { + return Self::between(self.start(), region.start()); + } + + /// 获取[region.end(),self.end())的虚拟地址范围 + /// + /// 如果 self.end() >= region.end() ,返回None + pub fn after(self, region: &VirtRegion) -> Option { + // if self.end() > region.end() none + return Self::between(region.end(), self.end()); + } + + /// 把当前虚拟地址范围内的某个虚拟地址,转换为另一个虚拟地址范围内的虚拟地址 + /// + /// 如果vaddr不在当前虚拟地址范围内,返回None + /// + /// 如果vaddr在当前虚拟地址范围内,返回vaddr在new_base中的虚拟地址 + pub fn rebase(self, vaddr: VirtAddr, new_base: &VirtRegion) -> Option { + if !self.contains(vaddr) { + return None; + } + let offset = vaddr.data() - self.start().data(); + let new_start = new_base.start().data() + offset; + return Some(VirtAddr::new(new_start)); + } + + /// 判断虚拟地址范围是否包含指定的虚拟地址 + pub fn contains(&self, addr: VirtAddr) -> bool { + return self.start() <= addr && addr < self.end(); + } + + /// 创建当前虚拟地址范围的页面迭代器 + pub fn pages(&self) -> VirtPageFrameIter { + return VirtPageFrame::iter_range( + VirtPageFrame::new(self.start()), + VirtPageFrame::new(self.end()), + ); + } +} + +impl PartialOrd for VirtRegion { + fn partial_cmp(&self, other: &Self) -> Option { + return self.start.partial_cmp(&other.start); + } +} + +impl Ord for VirtRegion { + fn cmp(&self, other: &Self) -> cmp::Ordering { + return self.start.cmp(&other.start); + } +} + +/// ## 判断虚拟地址是否超出了用户空间 +/// +/// 如果虚拟地址超出了用户空间,返回Err(SystemError::EFAULT). +/// 如果end < start,返回Err(SystemError::EOVERFLOW) +/// +/// 否则返回Ok(()) +pub fn verify_area(addr: VirtAddr, size: usize) -> Result<(), SystemError> { + let end = addr.add(size); + if unlikely(end.data() < addr.data()) { + return Err(SystemError::EOVERFLOW); + } + + if !addr.check_user() || !end.check_user() { + return Err(SystemError::EFAULT); + } + + return Ok(()); +} +// ====== 重构内存管理、进程管理后,请删除这几行 BEGIN ====== //BUG pcb问题 unsafe impl Send for process_control_block {} unsafe impl Sync for process_control_block {} -unsafe impl Send for mm_struct {} -unsafe impl Sync for mm_struct {} +// ====== 重构内存管理后,请删除这几行 END ======= diff --git a/kernel/src/mm/no_init.rs b/kernel/src/mm/no_init.rs new file mode 100644 index 00000000..352cd396 --- /dev/null +++ b/kernel/src/mm/no_init.rs @@ -0,0 +1,79 @@ +//! 该文件用于系统启动早期,内存管理器初始化之前,提供一些简单的内存映射功能 +//! +//! 这里假设在内核引导文件中,已经填写了前100M的内存映射关系,因此这里不需要任何动态分配。 +//! +//! 映射关系为: +//! +//! 虚拟地址 0-100M与虚拟地址 0x8000_0000_0000 - 0x8000_0640_0000 之间具有重映射关系。 +//! 也就是说,他们的第二级页表在最顶级页表中,占用了第0和第256个页表项。 +//! + +use crate::mm::{MMArch, MemoryManagementArch, PhysAddr}; +use core::marker::PhantomData; + +use super::{ + allocator::page_frame::{FrameAllocator, PageFrameCount, PageFrameUsage}, + page::PageFlags, + PageTableKind, VirtAddr, +}; + +/// 伪分配器 +struct PseudoAllocator { + phantom: PhantomData, +} + +impl PseudoAllocator { + pub const fn new() -> Self { + Self { + phantom: PhantomData, + } + } +} + +/// 为NoInitAllocator实现FrameAllocator +impl FrameAllocator for PseudoAllocator { + unsafe fn allocate(&mut self, _count: PageFrameCount) -> Option<(PhysAddr, PageFrameCount)> { + panic!("NoInitAllocator can't allocate page frame"); + } + + unsafe fn free(&mut self, _address: PhysAddr, _count: PageFrameCount) { + panic!("NoInitAllocator can't free page frame"); + } + /// @brief: 获取内存区域页帧的使用情况 + /// @param self + /// @return 页帧的使用情况 + unsafe fn usage(&self) -> PageFrameUsage { + panic!("NoInitAllocator can't get page frame usage"); + } +} + +/// Use pseudo mapper to map physical memory to virtual memory. +/// +/// ## Safety +/// +/// 调用该函数时,必须保证内存管理器尚未初始化。否则将导致未定义的行为 +/// +/// 并且,内核引导文件必须以4K页为粒度,填写了前100M的内存映射关系。(具体以本文件开头的注释为准) +pub unsafe fn pseudo_map_phys(vaddr: VirtAddr, paddr: PhysAddr, count: PageFrameCount) { + assert!(vaddr.check_aligned(MMArch::PAGE_SIZE)); + assert!(paddr.check_aligned(MMArch::PAGE_SIZE)); + + let mut pseudo_allocator = PseudoAllocator::::new(); + + let mut mapper = crate::mm::page::PageMapper::::new( + PageTableKind::Kernel, + MMArch::table(PageTableKind::Kernel), + &mut pseudo_allocator, + ); + + let flags: PageFlags = PageFlags::new().set_write(true).set_execute(true); + + for i in 0..count.data() { + let vaddr = vaddr + i * MMArch::PAGE_SIZE; + let paddr = paddr + i * MMArch::PAGE_SIZE; + let flusher = mapper.map_phys(vaddr, paddr, flags).unwrap(); + flusher.ignore(); + } + + mapper.make_current(); +} diff --git a/kernel/src/mm/page.rs b/kernel/src/mm/page.rs new file mode 100644 index 00000000..13023687 --- /dev/null +++ b/kernel/src/mm/page.rs @@ -0,0 +1,924 @@ +use core::{ + fmt::{self, Debug, Error, Formatter}, + marker::PhantomData, + mem, + ops::Add, + sync::atomic::{compiler_fence, Ordering}, +}; + +use crate::{ + arch::{interrupt::ipi::send_ipi, MMArch}, + exception::ipi::{IpiKind, IpiTarget}, + kerror, kwarn, +}; + +use super::{ + allocator::page_frame::FrameAllocator, syscall::ProtFlags, MemoryManagementArch, PageTableKind, + PhysAddr, VirtAddr, +}; + +#[derive(Debug)] +pub struct PageTable { + /// 当前页表表示的虚拟地址空间的起始地址 + base: VirtAddr, + /// 当前页表所在的物理地址 + phys: PhysAddr, + /// 当前页表的层级(请注意,最顶级页表的level为[Arch::PAGE_LEVELS - 1]) + level: usize, + phantom: PhantomData, +} + +#[allow(dead_code)] +impl PageTable { + pub unsafe fn new(base: VirtAddr, phys: PhysAddr, level: usize) -> Self { + Self { + base, + phys, + level, + phantom: PhantomData, + } + } + + /// 获取顶级页表 + /// + /// ## 参数 + /// + /// - table_kind 页表类型 + /// + /// ## 返回值 + /// + /// 返回顶级页表 + pub unsafe fn top_level_table(table_kind: PageTableKind) -> Self { + return Self::new( + VirtAddr::new(0), + Arch::table(table_kind), + Arch::PAGE_LEVELS - 1, + ); + } + + /// 获取当前页表的物理地址 + #[inline(always)] + pub fn phys(&self) -> PhysAddr { + self.phys + } + + /// 当前页表表示的虚拟地址空间的起始地址 + #[inline(always)] + pub fn base(&self) -> VirtAddr { + self.base + } + + /// 获取当前页表的层级 + #[inline(always)] + pub fn level(&self) -> usize { + self.level + } + + /// 获取当前页表自身所在的虚拟地址 + #[inline(always)] + pub unsafe fn virt(&self) -> VirtAddr { + return Arch::phys_2_virt(self.phys).unwrap(); + } + + /// 获取第i个页表项所表示的虚拟内存空间的起始地址 + pub fn entry_base(&self, i: usize) -> Option { + if i < Arch::PAGE_ENTRY_NUM { + let shift = self.level * Arch::PAGE_ENTRY_SHIFT + Arch::PAGE_SHIFT; + return Some(self.base.add(i << shift)); + } else { + return None; + } + } + + /// 获取当前页表的第i个页表项所在的虚拟地址(注意与entry_base进行区分) + pub unsafe fn entry_virt(&self, i: usize) -> Option { + if i < Arch::PAGE_ENTRY_NUM { + return Some(self.virt().add(i * Arch::PAGE_ENTRY_SIZE)); + } else { + return None; + } + } + + /// 获取当前页表的第i个页表项 + pub unsafe fn entry(&self, i: usize) -> Option> { + let entry_virt = self.entry_virt(i)?; + return Some(PageEntry::new(Arch::read::(entry_virt))); + } + + /// 设置当前页表的第i个页表项 + pub unsafe fn set_entry(&self, i: usize, entry: PageEntry) -> Option<()> { + let entry_virt = self.entry_virt(i)?; + Arch::write::(entry_virt, entry.data()); + return Some(()); + } + + /// 判断当前页表的第i个页表项是否已经填写了值 + /// + /// ## 参数 + /// - Some(true) 如果已经填写了值 + /// - Some(false) 如果未填写值 + /// - None 如果i超出了页表项的范围 + pub fn entry_mapped(&self, i: usize) -> Option { + let etv = unsafe { self.entry_virt(i) }?; + if unsafe { Arch::read::(etv) } != 0 { + return Some(true); + } else { + return Some(false); + } + } + + /// 根据虚拟地址,获取对应的页表项在页表中的下标 + /// + /// ## 参数 + /// + /// - addr: 虚拟地址 + /// + /// ## 返回值 + /// + /// 页表项在页表中的下标。如果addr不在当前页表所表示的虚拟地址空间中,则返回None + pub unsafe fn index_of(&self, addr: VirtAddr) -> Option { + let addr = VirtAddr::new(addr.data() & Arch::PAGE_ADDRESS_MASK); + let shift = self.level * Arch::PAGE_ENTRY_SHIFT + Arch::PAGE_SHIFT; + + let mask = (MMArch::PAGE_ENTRY_NUM << shift) - 1; + if addr < self.base || addr >= self.base.add(mask) { + return None; + } else { + return Some((addr.data() >> shift) & MMArch::PAGE_ENTRY_MASK); + } + } + + /// 获取第i个页表项指向的下一级页表 + pub unsafe fn next_level_table(&self, index: usize) -> Option { + if self.level == 0 { + return None; + } + + // 返回下一级页表 + return Some(PageTable::new( + self.entry_base(index)?, + self.entry(index)?.address().ok()?, + self.level - 1, + )); + } +} + +/// 页表项 +#[derive(Copy, Clone)] +pub struct PageEntry { + data: usize, + phantom: PhantomData, +} + +impl Debug for PageEntry { + fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> { + f.write_fmt(format_args!("PageEntry({:#x})", self.data)) + } +} + +impl PageEntry { + #[inline(always)] + pub fn new(data: usize) -> Self { + Self { + data, + phantom: PhantomData, + } + } + + #[inline(always)] + pub fn data(&self) -> usize { + self.data + } + + /// 获取当前页表项指向的物理地址 + /// + /// ## 返回值 + /// + /// - Ok(PhysAddr) 如果当前页面存在于物理内存中, 返回物理地址 + /// - Err(PhysAddr) 如果当前页表项不存在, 返回物理地址 + #[inline(always)] + pub fn address(&self) -> Result { + let paddr = PhysAddr::new(self.data & Arch::PAGE_ADDRESS_MASK); + + if self.present() { + Ok(paddr) + } else { + Err(paddr) + } + } + + #[inline(always)] + pub fn flags(&self) -> PageFlags { + unsafe { PageFlags::from_data(self.data & Arch::ENTRY_FLAGS_MASK) } + } + + #[inline(always)] + pub fn set_flags(&mut self, flags: PageFlags) { + self.data = (self.data & !Arch::ENTRY_FLAGS_MASK) | flags.data(); + } + + #[inline(always)] + pub fn present(&self) -> bool { + return self.data & Arch::ENTRY_FLAG_PRESENT != 0; + } +} + +/// 页表项的标志位 +#[derive(Copy, Clone, Hash)] +pub struct PageFlags { + data: usize, + phantom: PhantomData, +} + +#[allow(dead_code)] +impl PageFlags { + #[inline(always)] + pub fn new() -> Self { + let mut r = unsafe { + Self::from_data( + Arch::ENTRY_FLAG_DEFAULT_PAGE + | Arch::ENTRY_FLAG_READONLY + | Arch::ENTRY_FLAG_NO_EXEC, + ) + }; + + #[cfg(target_arch = "x86_64")] + { + if crate::arch::mm::X86_64MMArch::is_xd_reserved() { + r = r.set_execute(true); + } + } + + return r; + } + + /// 根据ProtFlags生成PageFlags + /// + /// ## 参数 + /// + /// - prot_flags: 页的保护标志 + /// - user: 用户空间是否可访问 + pub fn from_prot_flags(prot_flags: ProtFlags, user: bool) -> PageFlags { + let flags: PageFlags = PageFlags::new() + .set_user(user) + .set_execute(prot_flags.contains(ProtFlags::PROT_EXEC)) + .set_write(prot_flags.contains(ProtFlags::PROT_WRITE)); + + return flags; + } + + #[inline(always)] + pub fn data(&self) -> usize { + self.data + } + + #[inline(always)] + pub const unsafe fn from_data(data: usize) -> Self { + return Self { + data: data, + phantom: PhantomData, + }; + } + + /// 为新页表的页表项设置默认值 + /// + /// 默认值为: + /// - present + /// - read only + /// - kernel space + /// - no exec + #[inline(always)] + pub fn new_page_table(user: bool) -> Self { + return unsafe { + let r = Self::from_data(Arch::ENTRY_FLAG_DEFAULT_TABLE | Arch::ENTRY_FLAG_READWRITE); + if user { + r.set_user(true) + } else { + r + } + }; + } + + /// 取得当前页表项的所有权,更新当前页表项的标志位,并返回更新后的页表项。 + /// + /// ## 参数 + /// - flag 要更新的标志位的值 + /// - value 如果为true,那么将flag对应的位设置为1,否则设置为0 + /// + /// ## 返回值 + /// + /// 更新后的页表项 + #[inline(always)] + #[must_use] + pub fn update_flags(mut self, flag: usize, value: bool) -> Self { + if value { + self.data |= flag; + } else { + self.data &= !flag; + } + return self; + } + + /// 判断当前页表项是否存在指定的flag(只有全部flag都存在才返回true) + #[inline(always)] + pub fn has_flag(&self, flag: usize) -> bool { + return self.data & flag == flag; + } + + #[inline(always)] + pub fn present(&self) -> bool { + return self.has_flag(Arch::ENTRY_FLAG_PRESENT); + } + + /// 设置当前页表项的权限 + /// + /// @param value 如果为true,那么将当前页表项的权限设置为用户态可访问 + #[must_use] + #[inline(always)] + pub fn set_user(self, value: bool) -> Self { + return self.update_flags(Arch::ENTRY_FLAG_USER, value); + } + + /// 用户态是否可以访问当前页表项 + #[inline(always)] + pub fn has_user(&self) -> bool { + return self.has_flag(Arch::ENTRY_FLAG_USER); + } + + /// 设置当前页表项的可写性, 如果为true,那么将当前页表项的权限设置为可写, 否则设置为只读 + /// + /// ## 返回值 + /// + /// 更新后的页表项. + /// + /// **请注意,**本函数会取得当前页表项的所有权,因此返回的页表项不是原来的页表项 + #[must_use] + #[inline(always)] + pub fn set_write(self, value: bool) -> Self { + // 有的架构同时具有可写和不可写的标志位,因此需要同时更新 + return self + .update_flags(Arch::ENTRY_FLAG_READONLY, !value) + .update_flags(Arch::ENTRY_FLAG_READWRITE, value); + } + + /// 当前页表项是否可写 + #[inline(always)] + pub fn has_write(&self) -> bool { + // 有的架构同时具有可写和不可写的标志位,因此需要同时判断 + return self.data & (Arch::ENTRY_FLAG_READWRITE | Arch::ENTRY_FLAG_READONLY) + == Arch::ENTRY_FLAG_READWRITE; + } + + /// 设置当前页表项的可执行性, 如果为true,那么将当前页表项的权限设置为可执行, 否则设置为不可执行 + #[must_use] + #[inline(always)] + pub fn set_execute(self, mut value: bool) -> Self { + #[cfg(target_arch = "x86_64")] + { + // 如果xd位被保留,那么将可执行性设置为true + if crate::arch::mm::X86_64MMArch::is_xd_reserved() { + value = true; + } + } + + // 有的架构同时具有可执行和不可执行的标志位,因此需要同时更新 + return self + .update_flags(Arch::ENTRY_FLAG_NO_EXEC, !value) + .update_flags(Arch::ENTRY_FLAG_EXEC, value); + } + + /// 当前页表项是否可执行 + #[inline(always)] + pub fn has_execute(&self) -> bool { + // 有的架构同时具有可执行和不可执行的标志位,因此需要同时判断 + return self.data & (Arch::ENTRY_FLAG_EXEC | Arch::ENTRY_FLAG_NO_EXEC) + == Arch::ENTRY_FLAG_EXEC; + } + + /// 设置当前页表项的缓存策略 + /// + /// ## 参数 + /// + /// - value: 如果为true,那么将当前页表项的缓存策略设置为不缓存。 + #[inline(always)] + pub fn set_page_cache_disable(self, value: bool) -> Self { + return self.update_flags(Arch::ENTRY_FLAG_CACHE_DISABLE, value); + } + + /// 获取当前页表项的缓存策略 + /// + /// ## 返回值 + /// + /// 如果当前页表项的缓存策略为不缓存,那么返回true,否则返回false。 + #[inline(always)] + pub fn has_page_cache_disable(&self) -> bool { + return self.has_flag(Arch::ENTRY_FLAG_CACHE_DISABLE); + } + + /// 设置当前页表项的写穿策略 + /// + /// ## 参数 + /// + /// - value: 如果为true,那么将当前页表项的写穿策略设置为写穿。 + #[inline(always)] + pub fn set_page_write_through(self, value: bool) -> Self { + return self.update_flags(Arch::ENTRY_FLAG_WRITE_THROUGH, value); + } + + /// 获取当前页表项的写穿策略 + /// + /// ## 返回值 + /// + /// 如果当前页表项的写穿策略为写穿,那么返回true,否则返回false。 + #[inline(always)] + pub fn has_page_write_through(&self) -> bool { + return self.has_flag(Arch::ENTRY_FLAG_WRITE_THROUGH); + } + + /// MMIO内存的页表项标志 + #[inline(always)] + pub fn mmio_flags() -> Self { + return Self::new() + .set_user(false) + .set_write(true) + .set_execute(true) + .set_page_cache_disable(true) + .set_page_write_through(true); + } +} + +impl fmt::Debug for PageFlags { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PageFlags") + .field("bits", &format_args!("{:#0x}", self.data)) + .field("present", &self.present()) + .field("has_write", &self.has_write()) + .field("has_execute", &self.has_execute()) + .field("has_user", &self.has_user()) + .finish() + } +} + +/// 页表映射器 +#[derive(Hash)] +pub struct PageMapper { + /// 页表类型 + table_kind: PageTableKind, + /// 根页表物理地址 + table_paddr: PhysAddr, + /// 页分配器 + frame_allocator: F, + phantom: PhantomData Arch>, +} + +impl PageMapper { + /// 创建新的页面映射器 + /// + /// ## 参数 + /// - table_kind 页表类型 + /// - table_paddr 根页表物理地址 + /// - allocator 页分配器 + /// + /// ## 返回值 + /// + /// 页面映射器 + pub unsafe fn new(table_kind: PageTableKind, table_paddr: PhysAddr, allocator: F) -> Self { + return Self { + table_kind, + table_paddr, + frame_allocator: allocator, + phantom: PhantomData, + }; + } + + /// 创建页表,并为这个页表创建页面映射器 + pub unsafe fn create(table_kind: PageTableKind, mut allocator: F) -> Option { + let table_paddr = allocator.allocate_one()?; + // 清空页表 + let table_vaddr = Arch::phys_2_virt(table_paddr)?; + Arch::write_bytes(table_vaddr, 0, Arch::PAGE_SIZE); + return Some(Self::new(table_kind, table_paddr, allocator)); + } + + /// 获取当前页表的页面映射器 + #[inline(always)] + pub unsafe fn current(table_kind: PageTableKind, allocator: F) -> Self { + let table_paddr = Arch::table(table_kind); + return Self::new(table_kind, table_paddr, allocator); + } + + /// 判断当前页表分配器所属的页表是否是当前页表 + #[inline(always)] + pub fn is_current(&self) -> bool { + return unsafe { self.table().phys() == Arch::table(self.table_kind) }; + } + + /// 将当前页表分配器所属的页表设置为当前页表 + #[inline(always)] + pub unsafe fn make_current(&self) { + Arch::set_table(self.table_kind, self.table_paddr); + } + + /// 获取当前页表分配器所属的根页表的结构体 + #[inline(always)] + pub fn table(&self) -> PageTable { + // 由于只能通过new方法创建PageMapper,因此这里假定table_paddr是有效的 + return unsafe { + PageTable::new(VirtAddr::new(0), self.table_paddr, Arch::PAGE_LEVELS - 1) + }; + } + + /// 获取当前PageMapper所对应的页分配器实例的引用 + #[inline(always)] + #[allow(dead_code)] + pub fn allocator_ref(&self) -> &F { + return &self.frame_allocator; + } + + /// 获取当前PageMapper所对应的页分配器实例的可变引用 + #[inline(always)] + pub fn allocator_mut(&mut self) -> &mut F { + return &mut self.frame_allocator; + } + + /// 从当前PageMapper的页分配器中分配一个物理页,并将其映射到指定的虚拟地址 + pub unsafe fn map( + &mut self, + virt: VirtAddr, + flags: PageFlags, + ) -> Option> { + compiler_fence(Ordering::SeqCst); + let phys: PhysAddr = self.frame_allocator.allocate_one()?; + compiler_fence(Ordering::SeqCst); + return self.map_phys(virt, phys, flags); + } + + /// 映射一个物理页到指定的虚拟地址 + pub unsafe fn map_phys( + &mut self, + virt: VirtAddr, + phys: PhysAddr, + flags: PageFlags, + ) -> Option> { + // 验证虚拟地址和物理地址是否对齐 + if !(virt.check_aligned(Arch::PAGE_SIZE) && phys.check_aligned(Arch::PAGE_SIZE)) { + kerror!( + "Try to map unaligned page: virt={:?}, phys={:?}", + virt, + phys + ); + return None; + } + let virt = VirtAddr::new(virt.data() & (!Arch::PAGE_NEGATIVE_MASK)); + + // TODO: 验证flags是否合法 + + // 创建页表项 + let entry = PageEntry::new(phys.data() | flags.data()); + let mut table = self.table(); + loop { + let i = table.index_of(virt)?; + assert!(i < Arch::PAGE_ENTRY_NUM); + if table.level() == 0 { + // todo: 检查是否已经映射 + // 现在不检查的原因是,刚刚启动系统时,内核会映射一些页。 + if table.entry_mapped(i)? == true { + kwarn!("Page {:?} already mapped", virt); + } + // kdebug!("Mapping {:?} to {:?}, i = {i}, entry={:?}, flags={:?}", virt, phys, entry, flags); + compiler_fence(Ordering::SeqCst); + table.set_entry(i, entry); + compiler_fence(Ordering::SeqCst); + return Some(PageFlush::new(virt)); + } else { + let next_table = table.next_level_table(i); + if let Some(next_table) = next_table { + table = next_table; + // kdebug!("Mapping {:?} to next level table...", virt); + } else { + // kdebug!("Allocating next level table for {:?}..., i={i}", virt); + // 分配下一级页表 + let frame = self.frame_allocator.allocate_one()?; + // 清空这个页帧 + MMArch::write_bytes(MMArch::phys_2_virt(frame).unwrap(), 0, MMArch::PAGE_SIZE); + + // 设置页表项的flags + // let flags = Arch::ENTRY_FLAG_READWRITE + // | Arch::ENTRY_FLAG_DEFAULT_TABLE + // | if virt.kind() == PageTableKind::User { + // Arch::ENTRY_FLAG_USER + // } else { + // 0 + // }; + let flags: PageFlags = + PageFlags::new_page_table(virt.kind() == PageTableKind::User); + + // kdebug!("Flags: {:?}", flags); + + // 把新分配的页表映射到当前页表 + table.set_entry(i, PageEntry::new(frame.data() | flags.data())); + + // 获取新分配的页表 + table = table.next_level_table(i)?; + } + } + } + } + + /// 将物理地址映射到具有线性偏移量的虚拟地址 + #[allow(dead_code)] + pub unsafe fn map_linearly( + &mut self, + phys: PhysAddr, + flags: PageFlags, + ) -> Option<(VirtAddr, PageFlush)> { + let virt: VirtAddr = Arch::phys_2_virt(phys)?; + return self.map_phys(virt, phys, flags).map(|flush| (virt, flush)); + } + + /// 修改虚拟地址的页表项的flags,并返回页表项刷新器 + /// + /// 请注意,需要在修改完flags后,调用刷新器的flush方法,才能使修改生效 + /// + /// ## 参数 + /// - virt 虚拟地址 + /// - flags 新的页表项的flags + /// + /// ## 返回值 + /// + /// 如果修改成功,返回刷新器,否则返回None + pub unsafe fn remap( + &mut self, + virt: VirtAddr, + flags: PageFlags, + ) -> Option> { + return self + .visit(virt, |p1, i| { + let mut entry = p1.entry(i)?; + entry.set_flags(flags); + p1.set_entry(i, entry); + Some(PageFlush::new(virt)) + }) + .flatten(); + } + + /// 根据虚拟地址,查找页表,获取对应的物理地址和页表项的flags + /// + /// ## 参数 + /// + /// - virt 虚拟地址 + /// + /// ## 返回值 + /// + /// 如果查找成功,返回物理地址和页表项的flags,否则返回None + pub fn translate(&self, virt: VirtAddr) -> Option<(PhysAddr, PageFlags)> { + let entry: PageEntry = self.visit(virt, |p1, i| unsafe { p1.entry(i) })??; + let paddr = entry.address().ok()?; + let flags = entry.flags(); + return Some((paddr, flags)); + } + + /// 取消虚拟地址的映射,释放页面,并返回页表项刷新器 + /// + /// 请注意,需要在取消映射后,调用刷新器的flush方法,才能使修改生效 + /// + /// ## 参数 + /// + /// - virt 虚拟地址 + /// - unmap_parents 是否在父页表内,取消空闲子页表的映射 + /// + /// ## 返回值 + /// 如果取消成功,返回刷新器,否则返回None + pub unsafe fn unmap(&mut self, virt: VirtAddr, unmap_parents: bool) -> Option> { + let (paddr, _, flusher) = self.unmap_phys(virt, unmap_parents)?; + self.frame_allocator.free_one(paddr); + return Some(flusher); + } + + /// 取消虚拟地址的映射,并返回物理地址和页表项的flags + /// + /// ## 参数 + /// + /// - vaddr 虚拟地址 + /// - unmap_parents 是否在父页表内,取消空闲子页表的映射 + /// + /// ## 返回值 + /// + /// 如果取消成功,返回物理地址和页表项的flags,否则返回None + pub unsafe fn unmap_phys( + &mut self, + virt: VirtAddr, + unmap_parents: bool, + ) -> Option<(PhysAddr, PageFlags, PageFlush)> { + if !virt.check_aligned(Arch::PAGE_SIZE) { + kerror!("Try to unmap unaligned page: virt={:?}", virt); + return None; + } + + let mut table = self.table(); + return unmap_phys_inner(virt, &mut table, unmap_parents, self.allocator_mut()) + .map(|(paddr, flags)| (paddr, flags, PageFlush::::new(virt))); + } + + /// 在页表中,访问虚拟地址对应的页表项,并调用传入的函数F + fn visit( + &self, + virt: VirtAddr, + f: impl FnOnce(&mut PageTable, usize) -> T, + ) -> Option { + let mut table = self.table(); + unsafe { + loop { + let i = table.index_of(virt)?; + if table.level() == 0 { + return Some(f(&mut table, i)); + } else { + table = table.next_level_table(i)?; + } + } + } + } +} + +/// 取消页面映射,返回被取消映射的页表项的:【物理地址】和【flags】 +/// +/// ## 参数 +/// +/// - vaddr 虚拟地址 +/// - table 页表 +/// - unmap_parents 是否在父页表内,取消空闲子页表的映射 +/// - allocator 页面分配器(如果页表从这个分配器分配,那么在取消映射时,也需要归还到这个分配器内) +/// +/// ## 返回值 +/// +/// 如果取消成功,返回被取消映射的页表项的:【物理地址】和【flags】,否则返回None +unsafe fn unmap_phys_inner( + vaddr: VirtAddr, + table: &mut PageTable, + unmap_parents: bool, + allocator: &mut impl FrameAllocator, +) -> Option<(PhysAddr, PageFlags)> { + // 获取页表项的索引 + let i = table.index_of(vaddr)?; + + // 如果当前是最后一级页表,直接取消页面映射 + if table.level() == 0 { + let entry = table.entry(i)?; + table.set_entry(i, PageEntry::new(0)); + return Some((entry.address().ok()?, entry.flags())); + } + + let mut subtable = table.next_level_table(i)?; + // 递归地取消映射 + let result = unmap_phys_inner(vaddr, &mut subtable, unmap_parents, allocator)?; + + // TODO: This is a bad idea for architectures where the kernel mappings are done in the process tables, + // as these mappings may become out of sync + if unmap_parents { + // 如果子页表已经没有映射的页面了,就取消子页表的映射 + + // 检查子页表中是否还有映射的页面 + let x = (0..Arch::PAGE_ENTRY_NUM) + .map(|k| subtable.entry(k).expect("invalid page entry")) + .any(|e| e.present()); + if !x { + // 如果没有,就取消子页表的映射 + table.set_entry(i, PageEntry::new(0)); + // 释放子页表 + allocator.free_one(subtable.phys()); + } + } + + return Some(result); +} + +impl Debug for PageMapper { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PageMapper") + .field("table_paddr", &self.table_paddr) + .field("frame_allocator", &self.frame_allocator) + .finish() + } +} + +/// 页表刷新器的trait +pub trait Flusher { + /// 取消对指定的page flusher的刷新 + fn consume(&mut self, flush: PageFlush); +} + +/// 用于刷新某个虚拟地址的刷新器。这个刷新器一经产生,就必须调用flush()方法, +/// 否则会造成对页表的更改被忽略,这是不安全的 +#[must_use = "The flusher must call the 'flush()', or the changes to page table will be unsafely ignored."] +pub struct PageFlush { + virt: VirtAddr, + phantom: PhantomData, +} + +impl PageFlush { + pub fn new(virt: VirtAddr) -> Self { + return Self { + virt, + phantom: PhantomData, + }; + } + + pub fn flush(self) { + unsafe { Arch::invalidate_page(self.virt) }; + } + + /// 忽略掉这个刷新器 + pub unsafe fn ignore(self) { + mem::forget(self); + } +} + +/// 用于刷新整个页表的刷新器。这个刷新器一经产生,就必须调用flush()方法, +/// 否则会造成对页表的更改被忽略,这是不安全的 +#[must_use = "The flusher must call the 'flush()', or the changes to page table will be unsafely ignored."] +pub struct PageFlushAll { + phantom: PhantomData Arch>, +} + +#[allow(dead_code)] +impl PageFlushAll { + pub fn new() -> Self { + return Self { + phantom: PhantomData, + }; + } + + pub fn flush(self) { + unsafe { Arch::invalidate_all() }; + } + + /// 忽略掉这个刷新器 + pub unsafe fn ignore(self) { + mem::forget(self); + } +} + +impl Flusher for PageFlushAll { + /// 为page flush all 实现consume,消除对单个页面的刷新。(刷新整个页表了就不需要刷新单个页面了) + fn consume(&mut self, flush: PageFlush) { + unsafe { flush.ignore() }; + } +} + +impl + ?Sized> Flusher for &mut T { + /// 允许一个flusher consume掉另一个flusher + fn consume(&mut self, flush: PageFlush) { + >::consume(self, flush); + } +} + +impl Flusher for () { + fn consume(&mut self, _flush: PageFlush) {} +} + +impl Drop for PageFlushAll { + fn drop(&mut self) { + unsafe { + Arch::invalidate_all(); + } + } +} + +/// 未在当前CPU上激活的页表的刷新器 +/// +/// 如果页表没有在当前cpu上激活,那么需要发送ipi到其他核心,尝试在其他核心上刷新页表 +/// +/// TODO: 这个方式很暴力,也许把它改成在指定的核心上刷新页表会更好。(可以测试一下开销) +#[derive(Debug)] +pub struct InactiveFlusher; + +impl InactiveFlusher { + pub fn new() -> Self { + return Self {}; + } +} + +impl Flusher for InactiveFlusher { + fn consume(&mut self, flush: PageFlush) { + unsafe { + flush.ignore(); + } + } +} + +impl Drop for InactiveFlusher { + fn drop(&mut self) { + // 发送刷新页表的IPI + send_ipi(IpiKind::FlushTLB, IpiTarget::Other); + } +} + +/// # 把一个地址向下对齐到页大小 +pub fn round_down_to_page_size(addr: usize) -> usize { + addr & !(MMArch::PAGE_SIZE - 1) +} + +/// # 把一个地址向上对齐到页大小 +pub fn round_up_to_page_size(addr: usize) -> usize { + round_down_to_page_size(addr + MMArch::PAGE_SIZE - 1) +} diff --git a/kernel/src/mm/slab.c b/kernel/src/mm/slab.c deleted file mode 100644 index 15d3d894..00000000 --- a/kernel/src/mm/slab.c +++ /dev/null @@ -1,713 +0,0 @@ -#include "slab.h" -#include - -struct slab kmalloc_cache_group[16] = - { - {32, 0, 0, NULL, NULL, NULL, NULL}, - {64, 0, 0, NULL, NULL, NULL, NULL}, - {128, 0, 0, NULL, NULL, NULL, NULL}, - {256, 0, 0, NULL, NULL, NULL, NULL}, - {512, 0, 0, NULL, NULL, NULL, NULL}, - {1024, 0, 0, NULL, NULL, NULL, NULL}, // 1KB - {2048, 0, 0, NULL, NULL, NULL, NULL}, - {4096, 0, 0, NULL, NULL, NULL, NULL}, // 4KB - {8192, 0, 0, NULL, NULL, NULL, NULL}, - {16384, 0, 0, NULL, NULL, NULL, NULL}, - {32768, 0, 0, NULL, NULL, NULL, NULL}, - {65536, 0, 0, NULL, NULL, NULL, NULL}, - {131072, 0, 0, NULL, NULL, NULL, NULL}, // 128KB - {262144, 0, 0, NULL, NULL, NULL, NULL}, - {524288, 0, 0, NULL, NULL, NULL, NULL}, - {1048576, 0, 0, NULL, NULL, NULL, NULL}, // 1MB -}; - -/** - * @brief 创建一个内存池 - * - * @param size 内存池容量大小 - * @param constructor 构造函数 - * @param destructor 析构函数 - * @param arg 参数 - * @return struct slab* 构建好的内存池对象 - */ -struct slab *slab_create(ul size, void *(*constructor)(void *vaddr, ul arg), void *(*destructor)(void *vaddr, ul arg), ul arg) -{ - struct slab *slab_pool = (struct slab *)kmalloc(sizeof(struct slab), 0); - - // BUG - if (slab_pool == NULL) - { - kBUG("slab_create()->kmalloc()->slab == NULL"); - return NULL; - } - - memset(slab_pool, 0, sizeof(struct slab)); - - slab_pool->size = SIZEOF_LONG_ALIGN(size); - slab_pool->count_total_using = 0; - slab_pool->count_total_free = 0; - // 直接分配cache_pool_entry结构体,避免每次访问都要检测是否为NULL,提升效率 - slab_pool->cache_pool_entry = (struct slab_obj *)kmalloc(sizeof(struct slab_obj), 0); - - // BUG - if (slab_pool->cache_pool_entry == NULL) - { - kBUG("slab_create()->kmalloc()->slab->cache_pool_entry == NULL"); - kfree(slab_pool); - return NULL; - } - memset(slab_pool->cache_pool_entry, 0, sizeof(struct slab_obj)); - - // dma内存池设置为空 - slab_pool->cache_dma_pool_entry = NULL; - - // 设置构造及析构函数 - slab_pool->constructor = constructor; - slab_pool->destructor = destructor; - - list_init(&slab_pool->cache_pool_entry->list); - - // 分配属于内存池的内存页 - slab_pool->cache_pool_entry->page = alloc_pages(ZONE_NORMAL, 1, PAGE_KERNEL); - - // BUG - if (slab_pool->cache_pool_entry->page == NULL) - { - kBUG("slab_create()->kmalloc()->slab->cache_pool_entry == NULL"); - kfree(slab_pool->cache_pool_entry); - kfree(slab_pool); - return NULL; - } - - // page_init(slab_pool->cache_pool_entry->page, PAGE_KERNEL); - - slab_pool->cache_pool_entry->count_using = 0; - slab_pool->cache_pool_entry->count_free = PAGE_2M_SIZE / slab_pool->size; - - slab_pool->count_total_free = slab_pool->cache_pool_entry->count_free; - - slab_pool->cache_pool_entry->vaddr = phys_2_virt(slab_pool->cache_pool_entry->page->addr_phys); - - // bitmap有多少有效位 - slab_pool->cache_pool_entry->bmp_count = slab_pool->cache_pool_entry->count_free; - - // 计算位图所占的空间 占用多少byte(按unsigned long大小的上边缘对齐) - slab_pool->cache_pool_entry->bmp_len = ((slab_pool->cache_pool_entry->bmp_count + sizeof(ul) * 8 - 1) >> 6) << 3; - // 初始化位图 - slab_pool->cache_pool_entry->bmp = (ul *)kmalloc(slab_pool->cache_pool_entry->bmp_len, 0); - - // BUG - if (slab_pool->cache_pool_entry->bmp == NULL) - { - kBUG("slab_create()->kmalloc()->slab->cache_pool_entry == NULL"); - free_pages(slab_pool->cache_pool_entry->page, 1); - kfree(slab_pool->cache_pool_entry); - kfree(slab_pool); - return NULL; - } - // 将位图清空 - memset(slab_pool->cache_pool_entry->bmp, 0, slab_pool->cache_pool_entry->bmp_len); - - return slab_pool; -} - -/** - * @brief 销毁内存池 - * 只有当slab是空的时候才能销毁 - * @param slab_pool 要销毁的内存池 - * @return ul - * - */ -ul slab_destroy(struct slab *slab_pool) -{ - struct slab_obj *slab_obj_ptr = slab_pool->cache_pool_entry; - if (slab_pool->count_total_using) - { - kBUG("slab_cache->count_total_using != 0"); - return ESLAB_NOTNULL; - } - - struct slab_obj *tmp_slab_obj = NULL; - while (!list_empty(&slab_obj_ptr->list)) - { - tmp_slab_obj = slab_obj_ptr; - // 获取下一个slab_obj的起始地址 - slab_obj_ptr = container_of(list_next(&slab_obj_ptr->list), struct slab_obj, list); - - list_del(&tmp_slab_obj->list); - - kfree(tmp_slab_obj->bmp); - - page_clean(tmp_slab_obj->page); - - free_pages(tmp_slab_obj->page, 1); - - kfree(tmp_slab_obj); - } - - kfree(slab_obj_ptr->bmp); - page_clean(slab_obj_ptr->page); - free_pages(slab_obj_ptr->page, 1); - kfree(slab_obj_ptr); - kfree(slab_pool); - return 0; -} - -/** - * @brief 分配SLAB内存池中的内存对象 - * - * @param slab_pool slab内存池 - * @param arg 传递给内存对象构造函数的参数 - * @return void* 内存空间的虚拟地址 - */ -void *slab_malloc(struct slab *slab_pool, ul arg) -{ - struct slab_obj *slab_obj_ptr = slab_pool->cache_pool_entry; - struct slab_obj *tmp_slab_obj = NULL; - - // slab内存池中已经没有空闲的内存对象,进行扩容 - if (slab_pool->count_total_free == 0) - { - tmp_slab_obj = (struct slab_obj *)kmalloc(sizeof(struct slab_obj), 0); - - // BUG - if (tmp_slab_obj == NULL) - { - kBUG("slab_malloc()->kmalloc()->slab->tmp_slab_obj == NULL"); - return NULL; - } - - memset(tmp_slab_obj, 0, sizeof(struct slab_obj)); - list_init(&tmp_slab_obj->list); - - tmp_slab_obj->page = alloc_pages(ZONE_NORMAL, 1, PAGE_KERNEL); - - // BUG - if (tmp_slab_obj->page == NULL) - { - kBUG("slab_malloc()->kmalloc()=>tmp_slab_obj->page == NULL"); - kfree(tmp_slab_obj); - return NULL; - } - - tmp_slab_obj->count_using = 0; - tmp_slab_obj->count_free = PAGE_2M_SIZE / slab_pool->size; - tmp_slab_obj->vaddr = phys_2_virt(tmp_slab_obj->page->addr_phys); - tmp_slab_obj->bmp_count = tmp_slab_obj->count_free; - // 计算位图所占的空间 占用多少byte(按unsigned long大小的上边缘对齐) - tmp_slab_obj->bmp_len = ((tmp_slab_obj->bmp_count + sizeof(ul) * 8 - 1) >> 6) << 3; - tmp_slab_obj->bmp = (ul *)kmalloc(tmp_slab_obj->bmp_len, 0); - - // BUG - if (tmp_slab_obj->bmp == NULL) - { - kBUG("slab_malloc()->kmalloc()=>tmp_slab_obj->bmp == NULL"); - free_pages(tmp_slab_obj->page, 1); - kfree(tmp_slab_obj); - return NULL; - } - - memset(tmp_slab_obj->bmp, 0, tmp_slab_obj->bmp_len); - - list_add(&slab_pool->cache_pool_entry->list, &tmp_slab_obj->list); - - slab_pool->count_total_free += tmp_slab_obj->count_free; - - slab_obj_ptr = tmp_slab_obj; - } - - // 扩容完毕或无需扩容,开始分配内存对象 - int tmp_md; - do - { - if (slab_obj_ptr->count_free == 0) - { - slab_obj_ptr = container_of(list_next(&slab_obj_ptr->list), struct slab_obj, list); - continue; - } - - for (int i = 0; i < slab_obj_ptr->bmp_count; ++i) - { - // 当前bmp对应的内存对象都已经被分配 - if (*(slab_obj_ptr->bmp + (i >> 6)) == 0xffffffffffffffffUL) - { - i += 63; - continue; - } - - // 第i个内存对象是空闲的 - tmp_md = i % 64; - if ((*(slab_obj_ptr->bmp + (i >> 6)) & (1UL << tmp_md)) == 0) - { - // 置位bmp - *(slab_obj_ptr->bmp + (i >> 6)) |= (1UL << tmp_md); - - // 更新当前slab对象的计数器 - ++(slab_obj_ptr->count_using); - --(slab_obj_ptr->count_free); - // 更新slab内存池的计数器 - ++(slab_pool->count_total_using); - --(slab_pool->count_total_free); - - if (slab_pool->constructor != NULL) - { - // 返回内存对象指针(要求构造函数返回内存对象指针) - return slab_pool->constructor((char *)slab_obj_ptr->vaddr + slab_pool->size * i, arg); - } - // 返回内存对象指针 - else - return (void *)((char *)slab_obj_ptr->vaddr + slab_pool->size * i); - } - } - - } while (slab_obj_ptr != slab_pool->cache_pool_entry); - - // should not be here - - kBUG("slab_malloc() ERROR: can't malloc"); - - // 释放内存 - if (tmp_slab_obj != NULL) - { - list_del(&tmp_slab_obj->list); - kfree(tmp_slab_obj->bmp); - page_clean(tmp_slab_obj->page); - free_pages(tmp_slab_obj->page, 1); - kfree(tmp_slab_obj); - } - return NULL; -} - -/** - * @brief 回收slab内存池中的对象 - * - * @param slab_pool 对应的内存池 - * @param addr 内存对象的虚拟地址 - * @param arg 传递给虚构函数的参数 - * @return ul - */ -ul slab_free(struct slab *slab_pool, void *addr, ul arg) -{ - struct slab_obj *slab_obj_ptr = slab_pool->cache_pool_entry; - - do - { - // 虚拟地址不在当前内存池对象的管理范围内 - if (!(slab_obj_ptr->vaddr <= addr && addr <= (slab_obj_ptr->vaddr + PAGE_2M_SIZE))) - { - slab_obj_ptr = container_of(list_next(&slab_obj_ptr->list), struct slab_obj, list); - } - else - { - - // 计算出给定内存对象是第几个 - int index = (addr - slab_obj_ptr->vaddr) / slab_pool->size; - - // 复位位图中对应的位 - *(slab_obj_ptr->bmp + (index >> 6)) ^= (1UL << index % 64); - - ++(slab_obj_ptr->count_free); - --(slab_obj_ptr->count_using); - - ++(slab_pool->count_total_free); - --(slab_pool->count_total_using); - - // 有对应的析构函数,调用析构函数 - if (slab_pool->destructor != NULL) - slab_pool->destructor((char *)slab_obj_ptr->vaddr + slab_pool->size * index, arg); - - // 当前内存对象池的正在使用的内存对象为0,且内存池的空闲对象大于当前对象池的2倍,则销毁当前对象池,以减轻系统内存压力 - if ((slab_obj_ptr->count_using == 0) && ((slab_pool->count_total_free >> 1) >= slab_obj_ptr->count_free) && (slab_obj_ptr != slab_pool->cache_pool_entry)) - { - - list_del(&slab_obj_ptr->list); - slab_pool->count_total_free -= slab_obj_ptr->count_free; - - kfree(slab_obj_ptr->bmp); - page_clean(slab_obj_ptr->page); - free_pages(slab_obj_ptr->page, 1); - - kfree(slab_obj_ptr); - } - } - - return 0; - } while (slab_obj_ptr != slab_pool->cache_pool_entry); - - kwarn("slab_free(): address not in current slab"); - return ENOT_IN_SLAB; -} - -/** - * @brief 初始化内存池组 - * 在初始化通用内存管理单元期间,尚无内存空间分配函数,需要我们手动为SLAB内存池指定存储空间 - * @return ul - */ -ul slab_init() -{ - kinfo("Initializing SLAB..."); - // 将slab的内存池空间放置在mms的后方 - ul tmp_addr = memory_management_struct.end_of_struct; - for (int i = 0; i < 16; ++i) - { - io_mfence(); - spin_init(&kmalloc_cache_group[i].lock); - // 将slab内存池对象的空间放置在mms的后面,并且预留4个unsigned long 的空间以防止内存越界 - kmalloc_cache_group[i].cache_pool_entry = (struct slab_obj *)memory_management_struct.end_of_struct; - - memory_management_struct.end_of_struct += sizeof(struct slab_obj) + (sizeof(ul) << 2); - - list_init(&kmalloc_cache_group[i].cache_pool_entry->list); - - // 初始化内存池对象 - kmalloc_cache_group[i].cache_pool_entry->count_using = 0; - kmalloc_cache_group[i].cache_pool_entry->count_free = PAGE_2M_SIZE / kmalloc_cache_group[i].size; - kmalloc_cache_group[i].cache_pool_entry->bmp_len = (((kmalloc_cache_group[i].cache_pool_entry->count_free + sizeof(ul) * 8 - 1) >> 6) << 3); - kmalloc_cache_group[i].cache_pool_entry->bmp_count = kmalloc_cache_group[i].cache_pool_entry->count_free; - - // 在slab对象后方放置bmp - kmalloc_cache_group[i].cache_pool_entry->bmp = (ul *)memory_management_struct.end_of_struct; - - // bmp后方预留4个unsigned long的空间防止内存越界,且按照8byte进行对齐 - memory_management_struct.end_of_struct = (ul)(memory_management_struct.end_of_struct + kmalloc_cache_group[i].cache_pool_entry->bmp_len + (sizeof(ul) << 2)) & (~(sizeof(ul) - 1)); - io_mfence(); - // @todo:此处可优化,直接把所有位设置为0,然后再对部分不存在对应的内存对象的位设置为1 - memset(kmalloc_cache_group[i].cache_pool_entry->bmp, 0xff, kmalloc_cache_group[i].cache_pool_entry->bmp_len); - for (int j = 0; j < kmalloc_cache_group[i].cache_pool_entry->bmp_count; ++j) - *(kmalloc_cache_group[i].cache_pool_entry->bmp + (j >> 6)) ^= 1UL << (j % 64); - - kmalloc_cache_group[i].count_total_using = 0; - kmalloc_cache_group[i].count_total_free = kmalloc_cache_group[i].cache_pool_entry->count_free; - io_mfence(); - } - - struct Page *page = NULL; - - // 将上面初始化内存池组时,所占用的内存页进行初始化 - ul tmp_page_mms_end = virt_2_phys(memory_management_struct.end_of_struct) >> PAGE_2M_SHIFT; - - ul page_num = 0; - for (int i = PAGE_2M_ALIGN(virt_2_phys(tmp_addr)) >> PAGE_2M_SHIFT; i <= tmp_page_mms_end; ++i) - { - - page = memory_management_struct.pages_struct + i; - page_num = page->addr_phys >> PAGE_2M_SHIFT; - *(memory_management_struct.bmp + (page_num >> 6)) |= (1UL << (page_num % 64)); - ++page->zone->count_pages_using; - io_mfence(); - --page->zone->count_pages_free; - page_init(page, PAGE_KERNEL_INIT | PAGE_KERNEL | PAGE_PGT_MAPPED); - } - io_mfence(); - - // 为slab内存池对象分配内存空间 - ul *virt = NULL; - for (int i = 0; i < 16; ++i) - { - // 获取一个新的空页并添加到空页表,然后返回其虚拟地址 - virt = (ul *)((memory_management_struct.end_of_struct + PAGE_2M_SIZE * i + PAGE_2M_SIZE - 1) & PAGE_2M_MASK); - - page = Virt_To_2M_Page(virt); - - page_num = page->addr_phys >> PAGE_2M_SHIFT; - - *(memory_management_struct.bmp + (page_num >> 6)) |= (1UL << (page_num % 64)); - - ++page->zone->count_pages_using; - io_mfence(); // 该位置必须加一个mfence,否则O3优化运行时会报错 - --page->zone->count_pages_free; - page_init(page, PAGE_PGT_MAPPED | PAGE_KERNEL | PAGE_KERNEL_INIT); - - kmalloc_cache_group[i].cache_pool_entry->page = page; - - kmalloc_cache_group[i].cache_pool_entry->vaddr = virt; - } - - kinfo("SLAB initialized successfully!"); - - return 0; -} - -/** - * @brief 在kmalloc中创建slab_obj的函数(与slab_malloc()中的类似) - * - * @param size - * @return struct slab_obj* 创建好的slab_obj - */ - -struct slab_obj *kmalloc_create_slab_obj(ul size) -{ - struct Page *page = alloc_pages(ZONE_NORMAL, 1, 0); - - // BUG - if (page == NULL) - { - kBUG("kmalloc_create()->alloc_pages()=>page == NULL"); - return NULL; - } - - page_init(page, PAGE_KERNEL); - - ul *vaddr = NULL; - ul struct_size = 0; - struct slab_obj *slab_obj_ptr; - - // 根据size大小,选择不同的分支来处理 - // 之所以选择512byte为分界点,是因为,此时bmp大小刚好为512byte。显而易见,选择过小的话会导致kmalloc函数与当前函数反复互相调用,最终导致栈溢出 - switch (size) - { - // ============ 对于size<=512byte的内存池对象,将slab_obj结构体和bmp放置在物理页的内部 ======== - // 由于这些对象的特征是,bmp占的空间大,而内存块的空间小,这样做的目的是避免再去申请一块内存来存储bmp,减少浪费。 - case 32: - case 64: - case 128: - case 256: - case 512: - vaddr = phys_2_virt(page->addr_phys); - // slab_obj结构体的大小 (本身的大小+bmp的大小) - struct_size = sizeof(struct slab_obj) + PAGE_2M_SIZE / size / 8; - // 将slab_obj放置到物理页的末尾 - slab_obj_ptr = (struct slab_obj *)((unsigned char *)vaddr + PAGE_2M_SIZE - struct_size); - slab_obj_ptr->bmp = (void *)slab_obj_ptr + sizeof(struct slab_obj); - - slab_obj_ptr->count_free = (PAGE_2M_SIZE - struct_size) / size; - slab_obj_ptr->count_using = 0; - slab_obj_ptr->bmp_count = slab_obj_ptr->count_free; - slab_obj_ptr->vaddr = vaddr; - slab_obj_ptr->page = page; - - list_init(&slab_obj_ptr->list); - - slab_obj_ptr->bmp_len = ((slab_obj_ptr->bmp_count + sizeof(ul) * 8 - 1) >> 6) << 3; - - // @todo:此处可优化,直接把所有位设置为0,然后再对部分不存在对应的内存对象的位设置为1 - memset(slab_obj_ptr->bmp, 0xff, slab_obj_ptr->bmp_len); - - for (int i = 0; i < slab_obj_ptr->bmp_count; ++i) - *(slab_obj_ptr->bmp + (i >> 6)) ^= 1UL << (i % 64); - - break; - // ================= 较大的size时,slab_obj和bmp不再放置于当前物理页内部 ============ - // 因为在这种情况下,bmp很短,继续放置在当前物理页内部则会造成可分配的对象少,加剧了内存空间的浪费 - case 1024: // 1KB - case 2048: - case 4096: // 4KB - case 8192: - case 16384: - case 32768: - case 65536: - case 131072: // 128KB - case 262144: - case 524288: - case 1048576: // 1MB - slab_obj_ptr = (struct slab_obj *)kmalloc(sizeof(struct slab_obj), 0); - - slab_obj_ptr->count_free = PAGE_2M_SIZE / size; - slab_obj_ptr->count_using = 0; - slab_obj_ptr->bmp_count = slab_obj_ptr->count_free; - - slab_obj_ptr->bmp_len = ((slab_obj_ptr->bmp_count + sizeof(ul) * 8 - 1) >> 6) << 3; - - slab_obj_ptr->bmp = (ul *)kmalloc(slab_obj_ptr->bmp_len, 0); - - // @todo:此处可优化,直接把所有位设置为0,然后再对部分不存在对应的内存对象的位设置为1 - memset(slab_obj_ptr->bmp, 0xff, slab_obj_ptr->bmp_len); - for (int i = 0; i < slab_obj_ptr->bmp_count; ++i) - *(slab_obj_ptr->bmp + (i >> 6)) ^= 1UL << (i % 64); - - slab_obj_ptr->vaddr = phys_2_virt(page->addr_phys); - slab_obj_ptr->page = page; - list_init(&slab_obj_ptr->list); - break; - // size 错误 - default: - kerror("kamlloc_create(): Wrong size%d", size); - free_pages(page, 1); - return NULL; - break; - } - - return slab_obj_ptr; -} - -/** - * @brief 通用内存分配函数 - * - * @param size 要分配的内存大小 - * @param gfp 内存的flag - * @return void* 内核内存虚拟地址 - */ -void *kmalloc(unsigned long size, gfp_t gfp) -{ - void *result = NULL; - if (size > 1048576) - { - kwarn("kmalloc(): Can't alloc such memory: %ld bytes, because it is too large.", size); - return NULL; - } - int index; - for (int i = 0; i < 16; ++i) - { - if (kmalloc_cache_group[i].size >= size) - { - index = i; - break; - } - } - // 对当前内存池加锁 - spin_lock(&kmalloc_cache_group[index].lock); - - struct slab_obj *slab_obj_ptr = kmalloc_cache_group[index].cache_pool_entry; - - // 内存池没有可用的内存对象,需要进行扩容 - if (unlikely(kmalloc_cache_group[index].count_total_free == 0)) - { - // 创建slab_obj - slab_obj_ptr = kmalloc_create_slab_obj(kmalloc_cache_group[index].size); - - // BUG - if (unlikely(slab_obj_ptr == NULL)) - { - kBUG("kmalloc()->kmalloc_create_slab_obj()=>slab == NULL"); - goto failed; - } - - kmalloc_cache_group[index].count_total_free += slab_obj_ptr->count_free; - list_add(&kmalloc_cache_group[index].cache_pool_entry->list, &slab_obj_ptr->list); - } - else // 内存对象充足 - { - do - { - // 跳转到下一个内存池对象 - if (slab_obj_ptr->count_free == 0) - slab_obj_ptr = container_of(list_next(&slab_obj_ptr->list), struct slab_obj, list); - else - break; - } while (slab_obj_ptr != kmalloc_cache_group[index].cache_pool_entry); - } - // 寻找一块可用的内存对象 - int md; - for (int i = 0; i < slab_obj_ptr->bmp_count; ++i) - { - - // 当前bmp全部被使用 - if (*(slab_obj_ptr->bmp + (i >> 6)) == 0xffffffffffffffffUL) - { - i += 63; - continue; - } - md = i % 64; - // 找到相应的内存对象 - if ((*(slab_obj_ptr->bmp + (i >> 6)) & (1UL << md)) == 0) - { - *(slab_obj_ptr->bmp + (i >> 6)) |= (1UL << md); - ++(slab_obj_ptr->count_using); - --(slab_obj_ptr->count_free); - - --kmalloc_cache_group[index].count_total_free; - ++kmalloc_cache_group[index].count_total_using; - // 放锁 - spin_unlock(&kmalloc_cache_group[index].lock); - // 返回内存对象 - result = (void *)((char *)slab_obj_ptr->vaddr + kmalloc_cache_group[index].size * i); - goto done; - } - } - goto failed; -done:; - if (gfp & __GFP_ZERO) - memset(result, 0, size); - return result; -failed:; - spin_unlock(&kmalloc_cache_group[index].lock); - kerror("kmalloc(): Cannot alloc more memory: %d bytes", size); - return NULL; -} - -/** - * @brief 通用内存释放函数 - * - * @param address 要释放的内存线性地址 - * @return unsigned long - */ -unsigned long kfree(void *address) -{ - if (unlikely(address == NULL)) - return 0; - struct slab_obj *slab_obj_ptr = NULL; - - // 将线性地址按照2M物理页对齐, 获得所在物理页的起始线性地址 - void *page_base_addr = (void *)((ul)address & PAGE_2M_MASK); - - int index; - - for (int i = 0; i < 16; ++i) - { - slab_obj_ptr = kmalloc_cache_group[i].cache_pool_entry; - - do - { - // 不属于当前slab_obj的管理范围 - if (likely(slab_obj_ptr->vaddr != page_base_addr)) - { - slab_obj_ptr = container_of(list_next(&slab_obj_ptr->list), struct slab_obj, list); - } - else - { - // 对当前内存池加锁 - spin_lock(&kmalloc_cache_group[i].lock); - // 计算地址属于哪一个内存对象 - index = (address - slab_obj_ptr->vaddr) / kmalloc_cache_group[i].size; - - // 复位bmp - *(slab_obj_ptr->bmp + (index >> 6)) ^= 1UL << (index % 64); - - ++(slab_obj_ptr->count_free); - --(slab_obj_ptr->count_using); - ++kmalloc_cache_group[i].count_total_free; - --kmalloc_cache_group[i].count_total_using; - - // 回收空闲的slab_obj - // 条件:当前slab_obj_ptr的使用为0、总空闲内存对象>=当前slab_obj的总对象的2倍 且当前slab_pool不为起始slab_obj - if ((slab_obj_ptr->count_using == 0) && (kmalloc_cache_group[i].count_total_free >= ((slab_obj_ptr->bmp_count) << 1)) && (kmalloc_cache_group[i].cache_pool_entry != slab_obj_ptr)) - { - switch (kmalloc_cache_group[i].size) - { - case 32: - case 64: - case 128: - case 256: - case 512: - // 在这种情况下,slab_obj是被安放在page内部的 - list_del(&slab_obj_ptr->list); - - kmalloc_cache_group[i].count_total_free -= slab_obj_ptr->bmp_count; - page_clean(slab_obj_ptr->page); - free_pages(slab_obj_ptr->page, 1); - break; - - default: - // 在这种情况下,slab_obj是被安放在额外获取的内存对象中的 - list_del(&slab_obj_ptr->list); - kmalloc_cache_group[i].count_total_free -= slab_obj_ptr->bmp_count; - - kfree(slab_obj_ptr->bmp); - - page_clean(slab_obj_ptr->page); - free_pages(slab_obj_ptr->page, 1); - - kfree(slab_obj_ptr); - break; - } - } - // 放锁 - spin_unlock(&kmalloc_cache_group[i].lock); - return 0; - } - - } while (slab_obj_ptr != kmalloc_cache_group[i].cache_pool_entry); - } - kBUG("kfree(): Can't free memory. address=%#018lx", address); - return ECANNOT_FREE_MEM; -} \ No newline at end of file diff --git a/kernel/src/mm/slab.h b/kernel/src/mm/slab.h index 218b576d..19fb8b69 100644 --- a/kernel/src/mm/slab.h +++ b/kernel/src/mm/slab.h @@ -1,54 +1,6 @@ #pragma once #include "mm.h" -#include -#include -#include -#include - -#define SIZEOF_LONG_ALIGN(size) ((size + sizeof(long) - 1) & ~(sizeof(long) - 1)) -#define SIZEOF_INT_ALIGN(size) ((size + sizeof(int) - 1) & ~(sizeof(int) - 1)) - -// SLAB存储池count_using不为空 -#define ESLAB_NOTNULL 101 -#define ENOT_IN_SLAB 102 // 地址不在当前slab内存池中 -#define ECANNOT_FREE_MEM 103 // 无法释放内存 - -struct slab_obj -{ - struct List list; - // 当前slab对象所使用的内存页 - struct Page *page; - - ul count_using; - ul count_free; - - // 当前页面所在的线性地址 - void *vaddr; - - // 位图 - ul bmp_len; // 位图的长度(字节) - ul bmp_count; // 位图的有效位数 - ul *bmp; -}; - -// slab内存池 -struct slab -{ - ul size; // 单位:byte - ul count_total_using; - ul count_total_free; - // 内存池对象 - struct slab_obj *cache_pool_entry; - // dma内存池对象 - struct slab_obj *cache_dma_pool_entry; - - spinlock_t lock; // 当前内存池的操作锁 - - // 内存池的构造函数和析构函数 - void *(*constructor)(void *vaddr, ul arg); - void *(*destructor)(void *vaddr, ul arg); -}; /** * @brief 通用内存分配函数 @@ -57,19 +9,16 @@ struct slab * @param gfp 内存的flag * @return void* 分配得到的内存的指针 */ -void *kmalloc(unsigned long size, gfp_t gfp); +extern void *kmalloc(unsigned long size, gfp_t gfp); /** * @brief 从kmalloc申请一块内存,并将这块内存清空 - * + * * @param size 要分配的内存大小 * @param gfp 内存的flag * @return void* 分配得到的内存的指针 */ -static __always_inline void *kzalloc(size_t size, gfp_t gfp) -{ - return kmalloc(size, gfp | __GFP_ZERO); -} +extern void *kzalloc(size_t size, gfp_t gfp); /** * @brief 通用内存释放函数 @@ -77,58 +26,4 @@ static __always_inline void *kzalloc(size_t size, gfp_t gfp) * @param address 要释放的内存地址 * @return unsigned long */ -unsigned long kfree(void *address); - -/** - * @brief 创建一个内存池 - * - * @param size 内存池容量大小 - * @param constructor 构造函数 - * @param destructor 析构函数 - * @param arg 参数 - * @return struct slab* 构建好的内存池对象 - */ -struct slab *slab_create(ul size, void *(*constructor)(void *vaddr, ul arg), void *(*destructor)(void *vaddr, ul arg), ul arg); - -/** - * @brief 销毁内存池对象 - * 只有当slab对象是空的时候才能销毁 - * @param slab_pool 要销毁的内存池对象 - * @return ul - * - */ -ul slab_destroy(struct slab *slab_pool); - -/** - * @brief 分配SLAB内存池中的内存对象 - * - * @param slab_pool slab内存池 - * @param arg 传递给内存对象构造函数的参数 - * @return void* 内存空间的虚拟地址 - */ -void *slab_malloc(struct slab *slab_pool, ul arg); - -/** - * @brief 回收slab内存池中的对象 - * - * @param slab_pool 对应的内存池 - * @param addr 内存对象的虚拟地址 - * @param arg 传递给虚构函数的参数 - * @return ul - */ -ul slab_free(struct slab *slab_pool, void *addr, ul arg); - -/** - * @brief 在kmalloc中创建slab_obj的函数(与slab_malloc()类似) - * - * @param size - * @return struct slab_obj* 创建好的slab_obj - */ -struct slab_obj *kmalloc_create_slab_obj(ul size); - -/** - * @brief 初始化内存池组 - * 在初始化通用内存管理单元期间,尚无内存空间分配函数,需要我们手动为SLAB内存池指定存储空间 - * @return ul - */ -ul slab_init(); +extern unsigned long kfree(void *address); diff --git a/kernel/src/mm/syscall.rs b/kernel/src/mm/syscall.rs index 2adb2f7e..ef60a006 100644 --- a/kernel/src/mm/syscall.rs +++ b/kernel/src/mm/syscall.rs @@ -1,43 +1,219 @@ +use core::intrinsics::unlikely; + +use alloc::sync::Arc; + use crate::{ - include::bindings::bindings::mm_stat_t, + arch::MMArch, + kerror, + libs::align::{check_aligned, page_align_up}, + mm::MemoryManagementArch, syscall::{Syscall, SystemError}, }; -extern "C" { - fn sys_do_brk(new_addr: usize) -> usize; - fn sys_do_sbrk(incr: isize) -> usize; - fn sys_do_mstat(dst: *mut mm_stat_t, from_user: bool) -> usize; +use super::{ + allocator::page_frame::{PageFrameCount, VirtPageFrame}, + ucontext::{AddressSpace, DEFAULT_MMAP_MIN_ADDR}, + verify_area, VirtAddr, +}; + +bitflags! { + /// Memory protection flags + pub struct ProtFlags: u64 { + const PROT_NONE = 0x0; + const PROT_READ = 0x1; + const PROT_WRITE = 0x2; + const PROT_EXEC = 0x4; + } + + /// Memory mapping flags + pub struct MapFlags: u64 { + const MAP_NONE = 0x0; + /// share changes + const MAP_SHARED = 0x1; + /// changes are private + const MAP_PRIVATE = 0x2; + /// Interpret addr exactly + const MAP_FIXED = 0x10; + /// don't use a file + const MAP_ANONYMOUS = 0x20; + // linux-6.1-rc5/include/uapi/asm-generic/mman.h#7 + /// stack-like segment + const MAP_GROWSDOWN = 0x100; + /// ETXTBSY + const MAP_DENYWRITE = 0x800; + /// Mark it as an executable + const MAP_EXECUTABLE = 0x1000; + /// Pages are locked + const MAP_LOCKED = 0x2000; + /// don't check for reservations + const MAP_NORESERVE = 0x4000; + /// populate (prefault) pagetables + const MAP_POPULATE = 0x8000; + /// do not block on IO + const MAP_NONBLOCK = 0x10000; + /// give out an address that is best suited for process/thread stacks + const MAP_STACK = 0x20000; + /// create a huge page mapping + const MAP_HUGETLB = 0x40000; + /// perform synchronous page faults for the mapping + const MAP_SYNC = 0x80000; + /// MAP_FIXED which doesn't unmap underlying mapping + const MAP_FIXED_NOREPLACE = 0x100000; + + /// For anonymous mmap, memory could be uninitialized + const MAP_UNINITIALIZED = 0x4000000; + + } } + impl Syscall { - pub fn brk(new_addr: usize) -> Result { - let ret = unsafe { sys_do_brk(new_addr) }; - if (ret as isize) < 0 { - return Err( - SystemError::from_posix_errno(-(ret as isize) as i32).expect("brk: Invalid errno") - ); + pub fn brk(new_addr: VirtAddr) -> Result { + // kdebug!("brk: new_addr={:?}", new_addr); + let address_space = AddressSpace::current()?; + let mut address_space = address_space.write(); + + unsafe { + address_space + .set_brk(VirtAddr::new(page_align_up(new_addr.data()))) + .ok(); + + return Ok(address_space.sbrk(0).unwrap()); } - return Ok(ret); } - pub fn sbrk(incr: isize) -> Result { - let ret = unsafe { sys_do_sbrk(incr) }; - if (ret as isize) < 0 { - return Err( - SystemError::from_posix_errno(-(ret as isize) as i32).expect("sbrk: Invalid errno") - ); - } - return Ok(ret); + pub fn sbrk(incr: isize) -> Result { + // kdebug!("pid:{}, sbrk: incr={}", current_pcb().pid, incr); + + let address_space = AddressSpace::current()?; + let mut address_space = address_space.write(); + let r = unsafe { address_space.sbrk(incr) }; + + // kdebug!("pid:{}, sbrk: r={:?}", current_pcb().pid, r); + return r; } - /// 获取内存统计信息 + /// ## mmap系统调用 /// - /// TODO: 该函数不是符合POSIX标准的,在将来需要删除! - pub fn mstat(dst: *mut mm_stat_t, from_user: bool) -> Result { - let ret = unsafe { sys_do_mstat(dst, from_user) }; - if (ret as isize) < 0 { - return Err(SystemError::from_posix_errno(-(ret as isize) as i32) - .expect("mstat: Invalid errno")); + /// 该函数的实现参考了Linux内核的实现,但是并不完全相同。因为有些功能咱们还没实现 + /// + /// ## 参数 + /// + /// - `start_vaddr`:映射的起始地址 + /// - `len`:映射的长度 + /// - `prot`:保护标志 + /// - `flags`:映射标志 + /// - `fd`:文件描述符(暂时不支持) + /// - `offset`:文件偏移量 (暂时不支持) + /// + /// ## 返回值 + /// + /// 成功时返回映射的起始地址,失败时返回错误码 + pub fn mmap( + start_vaddr: VirtAddr, + len: usize, + prot_flags: usize, + map_flags: usize, + _fd: i32, + _offset: usize, + ) -> Result { + let map_flags = MapFlags::from_bits_truncate(map_flags as u64); + let prot_flags = ProtFlags::from_bits_truncate(prot_flags as u64); + + if start_vaddr < VirtAddr::new(DEFAULT_MMAP_MIN_ADDR) + && map_flags.contains(MapFlags::MAP_FIXED) + { + kerror!( + "mmap: MAP_FIXED is not supported for address below {}", + DEFAULT_MMAP_MIN_ADDR + ); + return Err(SystemError::EINVAL); } - return Ok(ret); + // 暂时不支持除匿名页以外的映射 + if !map_flags.contains(MapFlags::MAP_ANONYMOUS) { + kerror!("mmap: not support file mapping"); + return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + } + + // 暂时不支持巨页映射 + if map_flags.contains(MapFlags::MAP_HUGETLB) { + kerror!("mmap: not support huge page mapping"); + return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + } + let current_address_space = AddressSpace::current()?; + let start_page = current_address_space.write().map_anonymous( + start_vaddr, + len, + prot_flags, + map_flags, + true, + )?; + return Ok(start_page.virt_address().data()); + } + + /// ## munmap系统调用 + /// + /// ## 参数 + /// + /// - `start_vaddr`:取消映射的起始地址(已经对齐到页) + /// - `len`:取消映射的字节数(已经对齐到页) + /// + /// ## 返回值 + /// + /// 成功时返回0,失败时返回错误码 + pub fn munmap(start_vaddr: VirtAddr, len: usize) -> Result { + assert!(start_vaddr.check_aligned(MMArch::PAGE_SIZE)); + assert!(check_aligned(len, MMArch::PAGE_SIZE)); + + if unlikely(verify_area(start_vaddr, len).is_err()) { + return Err(SystemError::EINVAL); + } + if unlikely(len == 0) { + return Err(SystemError::EINVAL); + } + + let current_address_space: Arc = AddressSpace::current()?; + let start_frame = VirtPageFrame::new(start_vaddr); + let page_count = PageFrameCount::new(len / MMArch::PAGE_SIZE); + + current_address_space + .write() + .munmap(start_frame, page_count) + .map_err(|_| SystemError::EINVAL)?; + return Ok(0); + } + + /// ## mprotect系统调用 + /// + /// ## 参数 + /// + /// - `start_vaddr`:起始地址(已经对齐到页) + /// - `len`:长度(已经对齐到页) + /// - `prot_flags`:保护标志 + pub fn mprotect( + start_vaddr: VirtAddr, + len: usize, + prot_flags: usize, + ) -> Result { + assert!(start_vaddr.check_aligned(MMArch::PAGE_SIZE)); + assert!(check_aligned(len, MMArch::PAGE_SIZE)); + + if unlikely(verify_area(start_vaddr, len).is_err()) { + return Err(SystemError::EINVAL); + } + if unlikely(len == 0) { + return Err(SystemError::EINVAL); + } + + let prot_flags = ProtFlags::from_bits(prot_flags as u64).ok_or(SystemError::EINVAL)?; + + let current_address_space: Arc = AddressSpace::current()?; + let start_frame = VirtPageFrame::new(start_vaddr); + let page_count = PageFrameCount::new(len / MMArch::PAGE_SIZE); + + current_address_space + .write() + .mprotect(start_frame, page_count, prot_flags) + .map_err(|_| SystemError::EINVAL)?; + return Ok(0); } } diff --git a/kernel/src/mm/ucontext.rs b/kernel/src/mm/ucontext.rs new file mode 100644 index 00000000..6fc7d89b --- /dev/null +++ b/kernel/src/mm/ucontext.rs @@ -0,0 +1,1319 @@ +// 进程的用户空间内存管理 + +use core::{ + cmp, + hash::Hasher, + intrinsics::unlikely, + ops::Add, + sync::atomic::{compiler_fence, Ordering}, +}; + +use alloc::{ + collections::BTreeMap, + sync::{Arc, Weak}, + vec::Vec, +}; +use hashbrown::HashSet; + +use crate::{ + arch::{asm::current::current_pcb, mm::PageMapper, CurrentIrqArch, MMArch}, + exception::InterruptArch, + libs::{ + align::page_align_up, + rwlock::{RwLock, RwLockWriteGuard}, + spinlock::{SpinLock, SpinLockGuard}, + }, + syscall::SystemError, +}; + +use super::{ + allocator::page_frame::{ + deallocate_page_frames, PageFrameCount, PhysPageFrame, VirtPageFrame, VirtPageFrameIter, + }, + page::{Flusher, InactiveFlusher, PageFlags, PageFlushAll}, + syscall::{MapFlags, ProtFlags}, + MemoryManagementArch, PageTableKind, VirtAddr, VirtRegion, +}; + +/// MMAP_MIN_ADDR的默认值 +/// 以下内容来自linux-5.19: +/// This is the portion of low virtual memory which should be protected +// from userspace allocation. Keeping a user from writing to low pages +// can help reduce the impact of kernel NULL pointer bugs. +// For most ia64, ppc64 and x86 users with lots of address space +// a value of 65536 is reasonable and should cause no problems. +// On arm and other archs it should not be higher than 32768. +// Programs which use vm86 functionality or have some need to map +// this low address space will need CAP_SYS_RAWIO or disable this +// protection by setting the value to 0. +pub const DEFAULT_MMAP_MIN_ADDR: usize = 65536; + +#[derive(Debug)] +pub struct AddressSpace { + inner: RwLock, +} + +impl AddressSpace { + pub fn new(create_stack: bool) -> Result, SystemError> { + let inner = InnerAddressSpace::new(create_stack)?; + let result = Self { + inner: RwLock::new(inner), + }; + return Ok(Arc::new(result)); + } + + /// 从pcb中获取当前进程的地址空间结构体的Arc指针 + pub fn current() -> Result, SystemError> { + let result = current_pcb() + .address_space() + .expect("Current process has no address space"); + return Ok(result); + } + + /// 判断某个地址空间是否为当前进程的地址空间 + pub fn is_current(self: &Arc) -> bool { + let current = Self::current(); + if let Ok(current) = current { + return Arc::ptr_eq(¤t, self); + } + return false; + } +} + +impl core::ops::Deref for AddressSpace { + type Target = RwLock; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl core::ops::DerefMut for AddressSpace { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.inner + } +} + +/// @brief 用户地址空间结构体(每个进程都有一个) +#[derive(Debug)] +pub struct InnerAddressSpace { + pub user_mapper: UserMapper, + pub mappings: UserMappings, + pub mmap_min: VirtAddr, + /// 用户栈信息结构体 + pub user_stack: Option, + + pub elf_brk_start: VirtAddr, + pub elf_brk: VirtAddr, + + /// 当前进程的堆空间的起始地址 + pub brk_start: VirtAddr, + /// 当前进程的堆空间的结束地址(不包含) + pub brk: VirtAddr, + + pub start_code: VirtAddr, + pub end_code: VirtAddr, + pub start_data: VirtAddr, + pub end_data: VirtAddr, +} + +impl InnerAddressSpace { + pub fn new(create_stack: bool) -> Result { + let mut result = Self { + user_mapper: MMArch::setup_new_usermapper()?, + mappings: UserMappings::new(), + mmap_min: VirtAddr(DEFAULT_MMAP_MIN_ADDR), + elf_brk_start: VirtAddr::new(0), + elf_brk: VirtAddr::new(0), + brk_start: MMArch::USER_BRK_START, + brk: MMArch::USER_BRK_START, + user_stack: None, + start_code: VirtAddr(0), + end_code: VirtAddr(0), + start_data: VirtAddr(0), + end_data: VirtAddr(0), + }; + if create_stack { + // kdebug!("to create user stack."); + result.new_user_stack(UserStack::DEFAULT_USER_STACK_SIZE)?; + } + + return Ok(result); + } + + /// 尝试克隆当前进程的地址空间,包括这些映射都会被克隆 + /// + /// # Returns + /// + /// 返回克隆后的,新的地址空间的Arc指针 + pub fn try_clone(&mut self) -> Result, SystemError> { + let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() }; + let new_addr_space = AddressSpace::new(false)?; + let mut new_guard = new_addr_space.write(); + + // 拷贝用户栈的结构体信息,但是不拷贝用户栈的内容(因为后面VMA的拷贝会拷贝用户栈的内容) + unsafe { + new_guard.user_stack = Some(self.user_stack.as_ref().unwrap().clone_info_only()); + } + let _current_stack_size = self.user_stack.as_ref().unwrap().stack_size(); + + let current_mapper = &mut self.user_mapper.utable; + + for vma in self.mappings.vmas.iter() { + // TODO: 增加对VMA是否为文件映射的判断,如果是的话,就跳过 + + let vma_guard: SpinLockGuard<'_, VMA> = vma.lock(); + let old_flags = vma_guard.flags(); + let tmp_flags: PageFlags = PageFlags::new().set_write(true); + + // 分配内存页并创建新的VMA + let new_vma = VMA::zeroed( + VirtPageFrame::new(vma_guard.region.start()), + PageFrameCount::new(vma_guard.region.size() / MMArch::PAGE_SIZE), + tmp_flags, + &mut new_guard.user_mapper.utable, + (), + )?; + new_guard.mappings.vmas.insert(new_vma.clone()); + // kdebug!("new vma: {:x?}", new_vma); + let mut new_vma_guard = new_vma.lock(); + for page in new_vma_guard.pages().map(|p| p.virt_address()) { + // kdebug!("page: {:x?}", page); + let current_frame = unsafe { + MMArch::phys_2_virt( + current_mapper + .translate(page) + .expect("VMA page not mapped") + .0, + ) + } + .expect("Phys2Virt: vaddr overflow.") + .data() as *mut u8; + + let new_frame = unsafe { + MMArch::phys_2_virt( + new_guard + .user_mapper + .utable + .translate(page) + .expect("VMA page not mapped") + .0, + ) + } + .expect("Phys2Virt: vaddr overflow.") + .data() as *mut u8; + + unsafe { + // 拷贝数据 + new_frame.copy_from_nonoverlapping(current_frame, MMArch::PAGE_SIZE); + } + } + drop(vma_guard); + + new_vma_guard.remap(old_flags, &mut new_guard.user_mapper.utable, ())?; + drop(new_vma_guard); + } + drop(new_guard); + drop(irq_guard); + return Ok(new_addr_space); + } + + /// 判断当前的地址空间是否是当前进程的地址空间 + #[inline] + pub fn is_current(&self) -> bool { + return self.user_mapper.utable.is_current(); + } + + /// 进行匿名页映射 + /// + /// ## 参数 + /// + /// - `start_vaddr`:映射的起始地址 + /// - `len`:映射的长度 + /// - `prot_flags`:保护标志 + /// - `map_flags`:映射标志 + /// - `round_to_min`:是否将`start_vaddr`对齐到`mmap_min`,如果为`true`,则当`start_vaddr`不为0时,会对齐到`mmap_min`,否则仅向下对齐到页边界 + pub fn map_anonymous( + &mut self, + start_vaddr: VirtAddr, + len: usize, + prot_flags: ProtFlags, + map_flags: MapFlags, + round_to_min: bool, + ) -> Result { + // 用于对齐hint的函数 + let round_hint_to_min = |hint: VirtAddr| { + // 先把hint向下对齐到页边界 + let addr = hint.data() & (!MMArch::PAGE_OFFSET_MASK); + // kdebug!("map_anonymous: hint = {:?}, addr = {addr:#x}", hint); + // 如果hint不是0,且hint小于DEFAULT_MMAP_MIN_ADDR,则对齐到DEFAULT_MMAP_MIN_ADDR + if (addr != 0) && round_to_min && (addr < DEFAULT_MMAP_MIN_ADDR) { + Some(VirtAddr::new(page_align_up(DEFAULT_MMAP_MIN_ADDR))) + } else if addr == 0 { + None + } else { + Some(VirtAddr::new(addr)) + } + }; + // kdebug!("map_anonymous: start_vaddr = {:?}", start_vaddr); + // kdebug!("map_anonymous: len(no align) = {}", len); + + let len = page_align_up(len); + + // kdebug!("map_anonymous: len = {}", len); + + let start_page: VirtPageFrame = self.mmap( + round_hint_to_min(start_vaddr), + PageFrameCount::from_bytes(len).unwrap(), + prot_flags, + map_flags, + move |page, count, flags, mapper, flusher| { + Ok(VMA::zeroed(page, count, flags, mapper, flusher)?) + }, + )?; + + return Ok(start_page); + } + + /// 向进程的地址空间映射页面 + /// + /// # 参数 + /// + /// - `addr`:映射的起始地址,如果为`None`,则由内核自动分配 + /// - `page_count`:映射的页面数量 + /// - `prot_flags`:保护标志 + /// - `map_flags`:映射标志 + /// - `map_func`:映射函数,用于创建VMA + /// + /// # Returns + /// + /// 返回映射的起始虚拟页帧 + /// + /// # Errors + /// + /// - `EINVAL`:参数错误 + pub fn mmap< + F: FnOnce( + VirtPageFrame, + PageFrameCount, + PageFlags, + &mut PageMapper, + &mut dyn Flusher, + ) -> Result, SystemError>, + >( + &mut self, + addr: Option, + page_count: PageFrameCount, + prot_flags: ProtFlags, + map_flags: MapFlags, + map_func: F, + ) -> Result { + if page_count == PageFrameCount::new(0) { + return Err(SystemError::EINVAL); + } + // kdebug!("mmap: addr: {addr:?}, page_count: {page_count:?}, prot_flags: {prot_flags:?}, map_flags: {map_flags:?}"); + + // 找到未使用的区域 + let region = match addr { + Some(vaddr) => { + self.mappings + .find_free_at(self.mmap_min, vaddr, page_count.bytes(), map_flags)? + } + None => self + .mappings + .find_free(self.mmap_min, page_count.bytes()) + .ok_or(SystemError::ENOMEM)?, + }; + + let page = VirtPageFrame::new(region.start()); + + // kdebug!("mmap: page: {:?}, region={region:?}", page.virt_address()); + + compiler_fence(Ordering::SeqCst); + let (mut active, mut inactive); + let flusher = if self.is_current() { + // kdebug!("mmap: current ucontext"); + active = PageFlushAll::new(); + &mut active as &mut dyn Flusher + } else { + // kdebug!("mmap: not current ucontext"); + inactive = InactiveFlusher::new(); + &mut inactive as &mut dyn Flusher + }; + compiler_fence(Ordering::SeqCst); + // 映射页面,并将VMA插入到地址空间的VMA列表中 + self.mappings.insert_vma(map_func( + page, + page_count, + PageFlags::from_prot_flags(prot_flags, true), + &mut self.user_mapper.utable, + flusher, + )?); + + return Ok(page); + } + + /// 取消进程的地址空间中的映射 + /// + /// # 参数 + /// + /// - `start_page`:起始页帧 + /// - `page_count`:取消映射的页帧数量 + /// + /// # Errors + /// + /// - `EINVAL`:参数错误 + /// - `ENOMEM`:内存不足 + pub fn munmap( + &mut self, + start_page: VirtPageFrame, + page_count: PageFrameCount, + ) -> Result<(), SystemError> { + let to_unmap = VirtRegion::new(start_page.virt_address(), page_count.bytes()); + let mut flusher: PageFlushAll = PageFlushAll::new(); + + let regions: Vec> = self.mappings.conflicts(to_unmap).collect::>(); + + for r in regions { + let r = r.lock().region; + let r = self.mappings.remove_vma(&r).unwrap(); + let intersection = r.lock().region().intersect(&to_unmap).unwrap(); + let (before, r, after) = r.extract(intersection).unwrap(); + + // TODO: 当引入后备页映射后,这里需要增加通知文件的逻辑 + + if let Some(before) = before { + // 如果前面有VMA,则需要将前面的VMA重新插入到地址空间的VMA列表中 + self.mappings.insert_vma(before); + } + + if let Some(after) = after { + // 如果后面有VMA,则需要将后面的VMA重新插入到地址空间的VMA列表中 + self.mappings.insert_vma(after); + } + + r.unmap(&mut self.user_mapper.utable, &mut flusher); + } + + // TODO: 当引入后备页映射后,这里需要增加通知文件的逻辑 + + return Ok(()); + } + + pub fn mprotect( + &mut self, + start_page: VirtPageFrame, + page_count: PageFrameCount, + prot_flags: ProtFlags, + ) -> Result<(), SystemError> { + // kdebug!( + // "mprotect: start_page: {:?}, page_count: {:?}, prot_flags:{prot_flags:?}", + // start_page, + // page_count + // ); + let (mut active, mut inactive); + let mut flusher = if self.is_current() { + active = PageFlushAll::new(); + &mut active as &mut dyn Flusher + } else { + inactive = InactiveFlusher::new(); + &mut inactive as &mut dyn Flusher + }; + + let mapper = &mut self.user_mapper.utable; + let region = VirtRegion::new(start_page.virt_address(), page_count.bytes()); + // kdebug!("mprotect: region: {:?}", region); + + let regions = self.mappings.conflicts(region).collect::>(); + // kdebug!("mprotect: regions: {:?}", regions); + + for r in regions { + // kdebug!("mprotect: r: {:?}", r); + let r = r.lock().region().clone(); + let r = self.mappings.remove_vma(&r).unwrap(); + + let intersection = r.lock().region().intersect(®ion).unwrap(); + let (before, r, after) = r.extract(intersection).expect("Failed to extract VMA"); + + if let Some(before) = before { + self.mappings.insert_vma(before); + } + if let Some(after) = after { + self.mappings.insert_vma(after); + } + + let mut r_guard = r.lock(); + // 如果VMA的保护标志不允许指定的修改,则返回错误 + if !r_guard.can_have_flags(prot_flags) { + drop(r_guard); + self.mappings.insert_vma(r.clone()); + return Err(SystemError::EACCES); + } + + let new_flags: PageFlags = r_guard + .flags() + .set_execute(prot_flags.contains(ProtFlags::PROT_EXEC)) + .set_write(prot_flags.contains(ProtFlags::PROT_WRITE)); + + r_guard.remap(new_flags, mapper, &mut flusher)?; + drop(r_guard); + self.mappings.insert_vma(r); + } + + return Ok(()); + } + + /// 创建新的用户栈 + /// + /// ## 参数 + /// + /// - `size`:栈的大小 + pub fn new_user_stack(&mut self, size: usize) -> Result<(), SystemError> { + assert!(self.user_stack.is_none(), "User stack already exists"); + let stack = UserStack::new(self, None, size)?; + self.user_stack = Some(stack); + return Ok(()); + } + + #[inline(always)] + pub fn user_stack_mut(&mut self) -> Option<&mut UserStack> { + return self.user_stack.as_mut(); + } + + /// 取消用户空间内的所有映射 + pub unsafe fn unmap_all(&mut self) { + let mut flusher: PageFlushAll = PageFlushAll::new(); + for vma in self.mappings.iter_vmas() { + vma.unmap(&mut self.user_mapper.utable, &mut flusher); + } + } + + /// 设置进程的堆的内存空间 + /// + /// ## 参数 + /// + /// - `new_brk`:新的堆的结束地址。需要满足页对齐要求,并且是用户空间地址,且大于等于当前的堆的起始地址 + /// + /// ## 返回值 + /// + /// 返回旧的堆的结束地址 + pub unsafe fn set_brk(&mut self, new_brk: VirtAddr) -> Result { + assert!(new_brk.check_aligned(MMArch::PAGE_SIZE)); + + if !new_brk.check_user() || new_brk < self.brk_start { + return Err(SystemError::EFAULT); + } + + let old_brk = self.brk; + // kdebug!("set_brk: old_brk: {:?}, new_brk: {:?}", old_brk, new_brk); + if new_brk > self.brk { + let len = new_brk - self.brk; + let prot_flags = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC; + let map_flags = MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS | MapFlags::MAP_FIXED; + self.map_anonymous(old_brk, len, prot_flags, map_flags, true)? + .virt_address(); + self.brk = new_brk; + return Ok(old_brk); + } else { + let unmap_len = self.brk - new_brk; + let unmap_start = new_brk; + if unmap_len == 0 { + return Ok(old_brk); + } + self.munmap( + VirtPageFrame::new(unmap_start), + PageFrameCount::from_bytes(unmap_len).unwrap(), + )?; + self.brk = new_brk; + return Ok(old_brk); + } + } + + pub unsafe fn sbrk(&mut self, incr: isize) -> Result { + if incr == 0 { + return Ok(self.brk); + } + + let new_brk = if incr > 0 { + self.brk + incr as usize + } else { + self.brk - (incr.abs() as usize) + }; + + let new_brk = VirtAddr::new(page_align_up(new_brk.data())); + + return self.set_brk(new_brk); + } +} + +impl Drop for InnerAddressSpace { + fn drop(&mut self) { + unsafe { + self.unmap_all(); + } + } +} + +#[derive(Debug, Hash)] +pub struct UserMapper { + pub utable: PageMapper, +} + +impl UserMapper { + pub fn new(utable: PageMapper) -> Self { + return Self { utable }; + } +} + +impl Drop for UserMapper { + fn drop(&mut self) { + if self.utable.is_current() { + // 如果当前要被销毁的用户空间的页表是当前进程的页表,那么就切换回初始内核页表 + unsafe { MMArch::set_table(PageTableKind::User, MMArch::initial_page_table()) } + } + // 释放用户空间顶层页表占用的页帧 + // 请注意,在释放这个页帧之前,用户页表应该已经被完全释放,否则会产生内存泄露 + unsafe { + deallocate_page_frames( + PhysPageFrame::new(self.utable.table().phys()), + PageFrameCount::new(1), + ) + }; + } +} + +/// 用户空间映射信息 +#[derive(Debug)] +pub struct UserMappings { + /// 当前用户空间的虚拟内存区域 + vmas: HashSet>, + /// 当前用户空间的VMA空洞 + vm_holes: BTreeMap, +} + +impl UserMappings { + pub fn new() -> Self { + return Self { + vmas: HashSet::new(), + vm_holes: core::iter::once((VirtAddr::new(0), MMArch::USER_END_VADDR.data())) + .collect::>(), + }; + } + + /// 判断当前进程的VMA内,是否有包含指定的虚拟地址的VMA。 + /// + /// 如果有,返回包含指定虚拟地址的VMA的Arc指针,否则返回None。 + #[allow(dead_code)] + pub fn contains(&self, vaddr: VirtAddr) -> Option> { + for v in self.vmas.iter() { + let guard = v.lock(); + if guard.region.contains(vaddr) { + return Some(v.clone()); + } + } + return None; + } + + /// 获取当前进程的地址空间中,与给定虚拟地址范围有重叠的VMA的迭代器。 + pub fn conflicts(&self, request: VirtRegion) -> impl Iterator> + '_ { + let r = self + .vmas + .iter() + .filter(move |v| !v.lock().region.intersect(&request).is_none()) + .cloned(); + return r; + } + + /// 在当前进程的地址空间中,寻找第一个符合条件的空闲的虚拟内存范围。 + /// + /// @param min_vaddr 最小的起始地址 + /// @param size 请求的大小 + /// + /// @return 如果找到了,返回虚拟内存范围,否则返回None + pub fn find_free(&self, min_vaddr: VirtAddr, size: usize) -> Option { + let _vaddr = min_vaddr; + let mut iter = self + .vm_holes + .iter() + .skip_while(|(hole_vaddr, hole_size)| hole_vaddr.add(**hole_size) <= min_vaddr); + + let (hole_vaddr, size) = iter.find(|(hole_vaddr, hole_size)| { + // 计算当前空洞的可用大小 + let available_size: usize = + if hole_vaddr <= &&min_vaddr && min_vaddr <= hole_vaddr.add(**hole_size) { + **hole_size - (min_vaddr - **hole_vaddr) + } else { + **hole_size + }; + + size <= available_size + })?; + + // 创建一个新的虚拟内存范围。 + let region = VirtRegion::new(cmp::max(*hole_vaddr, min_vaddr), *size); + return Some(region); + } + + pub fn find_free_at( + &self, + min_vaddr: VirtAddr, + vaddr: VirtAddr, + size: usize, + flags: MapFlags, + ) -> Result { + // 如果没有指定地址,那么就在当前进程的地址空间中寻找一个空闲的虚拟内存范围。 + if vaddr == VirtAddr::new(0) { + return self.find_free(min_vaddr, size).ok_or(SystemError::ENOMEM); + } + + // 如果指定了地址,那么就检查指定的地址是否可用。 + + let requested = VirtRegion::new(vaddr, size); + + if requested.end() >= MMArch::USER_END_VADDR || !vaddr.check_aligned(MMArch::PAGE_SIZE) { + return Err(SystemError::EINVAL); + } + + if let Some(_x) = self.conflicts(requested).next() { + if flags.contains(MapFlags::MAP_FIXED_NOREPLACE) { + // 如果指定了 MAP_FIXED_NOREPLACE 标志,由于所指定的地址无法成功建立映射,则放弃映射,不对地址做修正 + return Err(SystemError::EEXIST); + } + + if flags.contains(MapFlags::MAP_FIXED) { + // todo: 支持MAP_FIXED标志对已有的VMA进行覆盖 + return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP); + } + + // 如果没有指定MAP_FIXED标志,那么就对地址做修正 + let requested = self.find_free(min_vaddr, size).ok_or(SystemError::ENOMEM)?; + return Ok(requested); + } + + return Ok(requested); + } + + /// 在当前进程的地址空间中,保留一个指定大小的区域,使得该区域不在空洞中。 + /// 该函数会修改vm_holes中的空洞信息。 + /// + /// @param region 要保留的区域 + /// + /// 请注意,在调用本函数之前,必须先确定region所在范围内没有VMA。 + fn reserve_hole(&mut self, region: &VirtRegion) { + let prev_hole: Option<(&VirtAddr, &mut usize)> = + self.vm_holes.range_mut(..region.start()).next_back(); + + if let Some((prev_hole_vaddr, prev_hole_size)) = prev_hole { + let prev_hole_end = prev_hole_vaddr.add(*prev_hole_size); + + if prev_hole_end > region.start() { + // 如果前一个空洞的结束地址大于当前空洞的起始地址,那么就需要调整前一个空洞的大小。 + *prev_hole_size = region.start().data() - prev_hole_vaddr.data(); + } + + if prev_hole_end > region.end() { + // 如果前一个空洞的结束地址大于当前空洞的结束地址,那么就需要增加一个新的空洞。 + self.vm_holes + .insert(region.end(), prev_hole_end - region.end()); + } + } + } + + /// 在当前进程的地址空间中,释放一个指定大小的区域,使得该区域成为一个空洞。 + /// 该函数会修改vm_holes中的空洞信息。 + fn unreserve_hole(&mut self, region: &VirtRegion) { + // 如果将要插入的空洞与后一个空洞相邻,那么就需要合并。 + let next_hole_size: Option = self.vm_holes.remove(®ion.end()); + + if let Some((_prev_hole_vaddr, prev_hole_size)) = self + .vm_holes + .range_mut(..region.start()) + .next_back() + .filter(|(offset, size)| offset.data() + **size == region.start().data()) + { + *prev_hole_size += region.size() + next_hole_size.unwrap_or(0); + } else { + self.vm_holes + .insert(region.start(), region.size() + next_hole_size.unwrap_or(0)); + } + } + + /// 在当前进程的映射关系中,插入一个新的VMA。 + pub fn insert_vma(&mut self, vma: Arc) { + let region = vma.lock().region.clone(); + // 要求插入的地址范围必须是空闲的,也就是说,当前进程的地址空间中,不能有任何与之重叠的VMA。 + assert!(self.conflicts(region).next().is_none()); + self.reserve_hole(®ion); + + self.vmas.insert(vma); + } + + /// @brief 删除一个VMA,并把对应的地址空间加入空洞中。 + /// + /// 这里不会取消VMA对应的地址的映射 + /// + /// @param region 要删除的VMA所在的地址范围 + /// + /// @return 如果成功删除了VMA,则返回被删除的VMA,否则返回None + /// 如果没有可以删除的VMA,则不会执行删除操作,并报告失败。 + pub fn remove_vma(&mut self, region: &VirtRegion) -> Option> { + // 请注意,由于这里会对每个VMA加锁,因此性能很低 + let vma: Arc = self + .vmas + .drain_filter(|vma| vma.lock().region == *region) + .next()?; + self.unreserve_hole(region); + + return Some(vma); + } + + /// @brief Get the iterator of all VMAs in this process. + pub fn iter_vmas(&self) -> hashbrown::hash_set::Iter> { + return self.vmas.iter(); + } +} + +impl Default for UserMappings { + fn default() -> Self { + return Self::new(); + } +} + +/// 加了锁的VMA +/// +/// 备注:进行性能测试,看看SpinLock和RwLock哪个更快。 +#[derive(Debug)] +pub struct LockedVMA(SpinLock); + +impl core::hash::Hash for LockedVMA { + fn hash(&self, state: &mut H) { + self.0.lock().hash(state); + } +} + +impl PartialEq for LockedVMA { + fn eq(&self, other: &Self) -> bool { + self.0.lock().eq(&other.0.lock()) + } +} + +impl Eq for LockedVMA {} + +#[allow(dead_code)] +impl LockedVMA { + pub fn new(vma: VMA) -> Arc { + let r = Arc::new(Self(SpinLock::new(vma))); + r.0.lock().self_ref = Arc::downgrade(&r); + return r; + } + + pub fn lock(&self) -> SpinLockGuard { + return self.0.lock(); + } + + /// 调整当前VMA的页面的标志位 + /// + /// TODO:增加调整虚拟页映射的物理地址的功能 + /// + /// @param flags 新的标志位 + /// @param mapper 页表映射器 + /// @param flusher 页表项刷新器 + /// + pub fn remap( + &self, + flags: PageFlags, + mapper: &mut PageMapper, + mut flusher: impl Flusher, + ) -> Result<(), SystemError> { + let mut guard = self.lock(); + assert!(guard.mapped); + for page in guard.region.pages() { + // 暂时要求所有的页帧都已经映射到页表 + // TODO: 引入Lazy Mapping, 通过缺页中断来映射页帧,这里就不必要求所有的页帧都已经映射到页表了 + let r = unsafe { + mapper + .remap(page.virt_address(), flags) + .expect("Failed to remap, beacuse of some page is not mapped") + }; + flusher.consume(r); + } + guard.flags = flags; + return Ok(()); + } + + pub fn unmap(&self, mapper: &mut PageMapper, mut flusher: impl Flusher) { + let mut guard = self.lock(); + assert!(guard.mapped); + for page in guard.region.pages() { + let (paddr, _, flush) = unsafe { mapper.unmap_phys(page.virt_address(), true) } + .expect("Failed to unmap, beacuse of some page is not mapped"); + + // todo: 获取物理页的anon_vma的守卫 + + // todo: 从anon_vma中删除当前VMA + + // todo: 如果物理页的anon_vma链表长度为0,则释放物理页. + + // 目前由于还没有实现共享页,所以直接释放物理页也没问题。 + // 但是在实现共享页之后,就不能直接释放物理页了,需要在anon_vma链表长度为0的时候才能释放物理页 + unsafe { deallocate_page_frames(PhysPageFrame::new(paddr), PageFrameCount::new(1)) }; + + flusher.consume(flush); + } + guard.mapped = false; + } + + pub fn mapped(&self) -> bool { + return self.0.lock().mapped; + } + + /// 将当前VMA进行切分,切分成3个VMA,分别是: + /// + /// 1. 前面的VMA,如果没有则为None + /// 2. 中间的VMA,也就是传入的Region + /// 3. 后面的VMA,如果没有则为None + pub fn extract( + &self, + region: VirtRegion, + ) -> Option<( + Option>, + Arc, + Option>, + )> { + assert!(region.start().check_aligned(MMArch::PAGE_SIZE)); + assert!(region.end().check_aligned(MMArch::PAGE_SIZE)); + + let mut guard = self.lock(); + { + // 如果传入的region不在当前VMA的范围内,则直接返回None + if unlikely(region.start() < guard.region.start() || region.end() > guard.region.end()) + { + return None; + } + + let intersect: Option = guard.region.intersect(®ion); + // 如果当前VMA不包含region,则直接返回None + if unlikely(intersect.is_none()) { + return None; + } + let intersect: VirtRegion = intersect.unwrap(); + if unlikely(intersect == guard.region) { + // 如果当前VMA完全包含region,则直接返回当前VMA + return Some((None, guard.self_ref.upgrade().unwrap(), None)); + } + } + + let before: Option> = guard.region.before(®ion).map(|virt_region| { + let mut vma: VMA = unsafe { guard.clone() }; + vma.region = virt_region; + + let vma: Arc = LockedVMA::new(vma); + vma + }); + + let after: Option> = guard.region.after(®ion).map(|virt_region| { + let mut vma: VMA = unsafe { guard.clone() }; + vma.region = virt_region; + + let vma: Arc = LockedVMA::new(vma); + vma + }); + + guard.region = region; + + // TODO: 重新设置before、after这两个VMA里面的物理页的anon_vma + + return Some((before, guard.self_ref.upgrade().unwrap(), after)); + } +} + +/// @brief 虚拟内存区域 +#[derive(Debug)] +pub struct VMA { + /// 虚拟内存区域对应的虚拟地址范围 + region: VirtRegion, + /// VMA内的页帧的标志 + flags: PageFlags, + /// VMA内的页帧是否已经映射到页表 + mapped: bool, + /// VMA所属的用户地址空间 + user_address_space: Option>, + self_ref: Weak, +} + +impl core::hash::Hash for VMA { + fn hash(&self, state: &mut H) { + self.region.hash(state); + self.flags.hash(state); + self.mapped.hash(state); + } +} + +#[allow(dead_code)] +impl VMA { + pub fn region(&self) -> &VirtRegion { + return &self.region; + } + + /// # 拷贝当前VMA的内容 + /// + /// ### 安全性 + /// + /// 由于这样操作可能由于错误的拷贝,导致内存泄露、内存重复释放等问题,所以需要小心使用。 + pub unsafe fn clone(&self) -> Self { + return Self { + region: self.region, + flags: self.flags, + mapped: self.mapped, + user_address_space: self.user_address_space.clone(), + self_ref: self.self_ref.clone(), + }; + } + + #[inline(always)] + pub fn flags(&self) -> PageFlags { + return self.flags; + } + + pub fn pages(&self) -> VirtPageFrameIter { + return VirtPageFrameIter::new( + VirtPageFrame::new(self.region.start()), + VirtPageFrame::new(self.region.end()), + ); + } + + pub fn remap( + &mut self, + flags: PageFlags, + mapper: &mut PageMapper, + mut flusher: impl Flusher, + ) -> Result<(), SystemError> { + assert!(self.mapped); + for page in self.region.pages() { + // kdebug!("remap page {:?}", page.virt_address()); + // 暂时要求所有的页帧都已经映射到页表 + // TODO: 引入Lazy Mapping, 通过缺页中断来映射页帧,这里就不必要求所有的页帧都已经映射到页表了 + let r = unsafe { + mapper + .remap(page.virt_address(), flags) + .expect("Failed to remap, beacuse of some page is not mapped") + }; + // kdebug!("consume page {:?}", page.virt_address()); + flusher.consume(r); + // kdebug!("remap page {:?} done", page.virt_address()); + } + self.flags = flags; + return Ok(()); + } + + /// 检查当前VMA是否可以拥有指定的标志位 + /// + /// ## 参数 + /// + /// - `prot_flags` 要检查的标志位 + pub fn can_have_flags(&self, prot_flags: ProtFlags) -> bool { + return (self.flags.has_write() || !prot_flags.contains(ProtFlags::PROT_WRITE)) + && (self.flags.has_execute() || !prot_flags.contains(ProtFlags::PROT_EXEC)); + } + + /// 把物理地址映射到虚拟地址 + /// + /// @param phys 要映射的物理地址 + /// @param destination 要映射到的虚拟地址 + /// @param count 要映射的页帧数量 + /// @param flags 页面标志位 + /// @param mapper 页表映射器 + /// @param flusher 页表项刷新器 + /// + /// @return 返回映射后的虚拟内存区域 + pub fn physmap( + phys: PhysPageFrame, + destination: VirtPageFrame, + count: PageFrameCount, + flags: PageFlags, + mapper: &mut PageMapper, + mut flusher: impl Flusher, + ) -> Result, SystemError> { + { + let mut cur_phy = phys; + let mut cur_dest = destination; + + for _ in 0..count.data() { + // 将物理页帧映射到虚拟页帧 + let r = unsafe { + mapper.map_phys(cur_dest.virt_address(), cur_phy.phys_address(), flags) + } + .expect("Failed to map phys, may be OOM error"); + + // todo: 增加OOM处理 + + // todo: 将VMA加入到anon_vma中 + + // 刷新TLB + flusher.consume(r); + + cur_phy = cur_phy.next(); + cur_dest = cur_dest.next(); + } + } + + let r: Arc = LockedVMA::new(VMA { + region: VirtRegion::new(destination.virt_address(), count.data() * MMArch::PAGE_SIZE), + flags, + mapped: true, + user_address_space: None, + self_ref: Weak::default(), + }); + return Ok(r); + } + + /// 从页分配器中分配一些物理页,并把它们映射到指定的虚拟地址,然后创建VMA + /// + /// @param destination 要映射到的虚拟地址 + /// @param count 要映射的页帧数量 + /// @param flags 页面标志位 + /// @param mapper 页表映射器 + /// @param flusher 页表项刷新器 + /// + /// @return 返回映射后的虚拟内存区域 + pub fn zeroed( + destination: VirtPageFrame, + page_count: PageFrameCount, + flags: PageFlags, + mapper: &mut PageMapper, + mut flusher: impl Flusher, + ) -> Result, SystemError> { + let mut cur_dest: VirtPageFrame = destination; + // kdebug!( + // "VMA::zeroed: page_count = {:?}, destination={destination:?}", + // page_count + // ); + for _ in 0..page_count.data() { + // kdebug!( + // "VMA::zeroed: cur_dest={cur_dest:?}, vaddr = {:?}", + // cur_dest.virt_address() + // ); + let r = unsafe { mapper.map(cur_dest.virt_address(), flags) } + .expect("Failed to map zero, may be OOM error"); + // todo: 将VMA加入到anon_vma中 + // todo: 增加OOM处理 + + // 稍后再刷新TLB,这里取消刷新 + flusher.consume(r); + cur_dest = cur_dest.next(); + } + let r = LockedVMA::new(VMA { + region: VirtRegion::new( + destination.virt_address(), + page_count.data() * MMArch::PAGE_SIZE, + ), + flags, + mapped: true, + user_address_space: None, + self_ref: Weak::default(), + }); + drop(flusher); + // kdebug!("VMA::zeroed: flusher dropped"); + + // 清空这些内存 + let virt_iter = VirtPageFrameIter::new(destination, destination.add(page_count)); + for frame in virt_iter { + let paddr = mapper.translate(frame.virt_address()).unwrap().0; + + unsafe { + let vaddr = MMArch::phys_2_virt(paddr).unwrap(); + MMArch::write_bytes(vaddr, 0, MMArch::PAGE_SIZE); + } + } + // kdebug!("VMA::zeroed: done"); + return Ok(r); + } +} + +impl Drop for VMA { + fn drop(&mut self) { + // 当VMA被释放时,需要确保它已经被从页表中解除映射 + assert!(!self.mapped, "VMA is still mapped"); + } +} + +impl PartialEq for VMA { + fn eq(&self, other: &Self) -> bool { + return self.region == other.region; + } +} + +impl Eq for VMA {} + +impl PartialOrd for VMA { + fn partial_cmp(&self, other: &Self) -> Option { + return self.region.partial_cmp(&other.region); + } +} + +impl Ord for VMA { + fn cmp(&self, other: &Self) -> cmp::Ordering { + return self.region.cmp(&other.region); + } +} + +#[derive(Debug)] +pub struct UserStack { + // 栈底地址 + stack_bottom: VirtAddr, + // 当前已映射的大小 + mapped_size: usize, + /// 栈顶地址(这个值需要仔细确定!因为它可能不会实时与用户栈的真实栈顶保持一致!要小心!) + current_sp: VirtAddr, +} + +impl UserStack { + /// 默认的用户栈底地址 + pub const DEFAULT_USER_STACK_BOTTOM: VirtAddr = MMArch::USER_STACK_START; + /// 默认的用户栈大小为8MB + pub const DEFAULT_USER_STACK_SIZE: usize = 8 * 1024 * 1024; + /// 用户栈的保护页数量 + pub const GUARD_PAGES_NUM: usize = 4; + + /// 创建一个用户栈 + pub fn new( + vm: &mut InnerAddressSpace, + stack_bottom: Option, + stack_size: usize, + ) -> Result { + let stack_bottom = stack_bottom.unwrap_or(Self::DEFAULT_USER_STACK_BOTTOM); + assert!(stack_bottom.check_aligned(MMArch::PAGE_SIZE)); + + // 分配用户栈的保护页 + let guard_size = Self::GUARD_PAGES_NUM * MMArch::PAGE_SIZE; + let actual_stack_bottom = stack_bottom - guard_size; + + let mut prot_flags = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE; + let map_flags = + MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS | MapFlags::MAP_FIXED_NOREPLACE; + // kdebug!( + // "map anonymous stack: {:?} {}", + // actual_stack_bottom, + // guard_size + // ); + vm.map_anonymous( + actual_stack_bottom, + guard_size, + prot_flags, + map_flags, + false, + )?; + // test_buddy(); + // 设置保护页只读 + prot_flags.remove(ProtFlags::PROT_WRITE); + // kdebug!( + // "to mprotect stack guard pages: {:?} {}", + // actual_stack_bottom, + // guard_size + // ); + vm.mprotect( + VirtPageFrame::new(actual_stack_bottom), + PageFrameCount::new(Self::GUARD_PAGES_NUM), + prot_flags, + )?; + + // kdebug!( + // "mprotect stack guard pages done: {:?} {}", + // actual_stack_bottom, + // guard_size + // ); + + let mut user_stack = UserStack { + stack_bottom: actual_stack_bottom, + mapped_size: guard_size, + current_sp: actual_stack_bottom - guard_size, + }; + + // kdebug!("extend user stack: {:?} {}", stack_bottom, stack_size); + // 分配用户栈 + user_stack.initial_extend(vm, stack_size)?; + // kdebug!("user stack created: {:?} {}", stack_bottom, stack_size); + return Ok(user_stack); + } + + fn initial_extend( + &mut self, + vm: &mut InnerAddressSpace, + mut bytes: usize, + ) -> Result<(), SystemError> { + let prot_flags = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC; + let map_flags = MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS; + + bytes = page_align_up(bytes); + self.mapped_size += bytes; + + vm.map_anonymous( + self.stack_bottom - self.mapped_size, + bytes, + prot_flags, + map_flags, + false, + )?; + + return Ok(()); + } + + /// 扩展用户栈 + /// + /// ## 参数 + /// + /// - `vm` 用户地址空间结构体 + /// - `bytes` 要扩展的字节数 + /// + /// ## 返回值 + /// + /// - **Ok(())** 扩展成功 + /// - **Err(SystemError)** 扩展失败 + #[allow(dead_code)] + pub fn extend( + &mut self, + vm: &mut RwLockWriteGuard, + mut bytes: usize, + ) -> Result<(), SystemError> { + let prot_flags = ProtFlags::PROT_READ | ProtFlags::PROT_WRITE | ProtFlags::PROT_EXEC; + let map_flags = MapFlags::MAP_PRIVATE | MapFlags::MAP_ANONYMOUS; + + bytes = page_align_up(bytes); + self.mapped_size += bytes; + + vm.map_anonymous( + self.stack_bottom - self.mapped_size, + bytes, + prot_flags, + map_flags, + false, + )?; + + return Ok(()); + } + + /// 获取栈顶地址 + /// + /// 请注意,如果用户栈的栈顶地址发生变化,这个值可能不会实时更新! + pub fn sp(&self) -> VirtAddr { + return self.current_sp; + } + + pub unsafe fn set_sp(&mut self, sp: VirtAddr) { + self.current_sp = sp; + } + + /// 仅仅克隆用户栈的信息,不会克隆用户栈的内容/映射 + pub unsafe fn clone_info_only(&self) -> Self { + return Self { + stack_bottom: self.stack_bottom, + mapped_size: self.mapped_size, + current_sp: self.current_sp, + }; + } + + /// 获取当前用户栈的大小(不包括保护页) + pub fn stack_size(&self) -> usize { + return self.mapped_size - Self::GUARD_PAGES_NUM * MMArch::PAGE_SIZE; + } +} diff --git a/kernel/src/mm/utils.c b/kernel/src/mm/utils.c deleted file mode 100644 index 9f9cd048..00000000 --- a/kernel/src/mm/utils.c +++ /dev/null @@ -1,109 +0,0 @@ -#include "internal.h" - -extern uint64_t mm_total_2M_pages; - -/** - * @brief 获取指定虚拟地址处映射的物理地址 - * - * @param mm 内存空间分布结构体 - * @param vaddr 虚拟地址 - * @return uint64_t 已映射的物理地址 - */ -uint64_t __mm_get_paddr(struct mm_struct *mm, uint64_t vaddr) -{ - ul *tmp; - - tmp = phys_2_virt((ul *)(((ul)mm->pgd) & (~0xfffUL)) + ((vaddr >> PAGE_GDT_SHIFT) & 0x1ff)); - - // pml4页表项为0 - if (*tmp == 0) - return 0; - - tmp = phys_2_virt((ul *)(*tmp & (~0xfffUL)) + ((vaddr >> PAGE_1G_SHIFT) & 0x1ff)); - - // pdpt页表项为0 - if (*tmp == 0) - return 0; - - // 读取pdt页表项 - tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(vaddr) >> PAGE_2M_SHIFT) & 0x1ff))); - - // pde页表项为0 - if (*tmp == 0) - return 0; - - if (*tmp & (1 << 7)) - { - // 当前为2M物理页 - return (*tmp) & (~0x1fffUL); - } - else - { - // 存在4级页表 - tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(vaddr) >> PAGE_4K_SHIFT) & 0x1ff))); - - return (*tmp) & (~0x1ffUL); - } -} - -/** - * @brief 检测指定地址是否已经被映射 - * - * @param page_table_phys_addr 页表的物理地址 - * @param virt_addr 要检测的地址 - * @return true 已经被映射 - * @return false - */ -bool mm_check_mapped(ul page_table_phys_addr, uint64_t virt_addr) -{ - ul *tmp; - - tmp = phys_2_virt((ul *)((ul)page_table_phys_addr & (~0xfffUL)) + ((virt_addr >> PAGE_GDT_SHIFT) & 0x1ff)); - - // pml4页表项为0 - if (*tmp == 0) - return 0; - - tmp = phys_2_virt((ul *)(*tmp & (~0xfffUL)) + ((virt_addr >> PAGE_1G_SHIFT) & 0x1ff)); - - // pdpt页表项为0 - if (*tmp == 0) - return 0; - - // 读取pdt页表项 - tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(virt_addr) >> PAGE_2M_SHIFT) & 0x1ff))); - - // pde页表项为0 - if (*tmp == 0) - return 0; - - if (*tmp & (1 << 7)) - { - // 当前为2M物理页 - return true; - } - else - { - // 存在4级页表 - tmp = phys_2_virt(((ul *)(*tmp & (~0xfffUL)) + (((ul)(virt_addr) >> PAGE_4K_SHIFT) & 0x1ff))); - if (*tmp != 0) - return true; - else - return false; - } -} - -/** - * @brief 检测是否为有效的2M页(物理内存页) - * - * @param paddr 物理地址 - * @return int8_t 是 -> 1 - * 否 -> 0 - */ -int8_t mm_is_2M_page(uint64_t paddr) -{ - if (likely((paddr >> PAGE_2M_SHIFT) < mm_total_2M_pages)) - return 1; - else - return 0; -} diff --git a/kernel/src/mm/vma.c b/kernel/src/mm/vma.c deleted file mode 100644 index 54ed527c..00000000 --- a/kernel/src/mm/vma.c +++ /dev/null @@ -1,275 +0,0 @@ -#include "mm.h" -#include "slab.h" -#include "internal.h" - -/** - * @brief 获取一块新的vma结构体,并将其与指定的mm进行绑定 - * - * @param mm 与VMA绑定的内存空间分布结构体 - * @return struct vm_area_struct* 新的VMA - */ -struct vm_area_struct *vm_area_alloc(struct mm_struct *mm) -{ - struct vm_area_struct *vma = (struct vm_area_struct *)kmalloc(sizeof(struct vm_area_struct), 0); - if (vma) - vma_init(vma, mm); - return vma; -} - -/** - * @brief 从链表中删除指定的vma结构体 - * - * @param vma - */ -void vm_area_del(struct vm_area_struct *vma) -{ - if (vma->vm_mm == NULL) - return; - __vma_unlink_list(vma->vm_mm, vma); -} - -/** - * @brief 释放vma结构体 - * - * @param vma 待释放的vma结构体 - */ -void vm_area_free(struct vm_area_struct *vma) -{ - if (vma->vm_prev == NULL && vma->vm_next == NULL) // 如果当前是剩余的最后一个vma - vma->vm_mm->vmas = NULL; - kfree(vma); -} - -/** - * @brief 将vma结构体插入mm_struct的链表之中 - * - * @param mm 内存空间分布结构体 - * @param vma 待插入的VMA结构体 - * @param prev 链表的前一个结点 - */ -void __vma_link_list(struct mm_struct *mm, struct vm_area_struct *vma, struct vm_area_struct *prev) -{ - struct vm_area_struct *next = NULL; - vma->vm_prev = prev; - if (prev) // 若指定了前一个结点,则直接连接 - { - next = prev->vm_next; - prev->vm_next = vma; - } - else // 否则将vma直接插入到给定的mm的vma链表之中 - { - next = mm->vmas; - mm->vmas = vma; - } - - vma->vm_next = next; - - if (next != NULL) - next->vm_prev = vma; -} - -/** - * @brief 将vma给定结构体从vma链表的结点之中删除 - * - * @param mm 内存空间分布结构体 - * @param vma 待插入的VMA结构体 - */ -void __vma_unlink_list(struct mm_struct *mm, struct vm_area_struct *vma) -{ - struct vm_area_struct *prev, *next; - next = vma->vm_next; - prev = vma->vm_prev; - if (prev) - prev->vm_next = next; - else // 当前vma是链表中的第一个vma - mm->vmas = next; - - if (next) - next->vm_prev = prev; -} - -/** - * @brief 查找第一个符合“addr < vm_end”条件的vma - * - * @param mm 内存空间分布结构体 - * @param addr 虚拟地址 - * @return struct vm_area_struct* 符合条件的vma - */ -struct vm_area_struct *vma_find(struct mm_struct *mm, uint64_t addr) -{ - struct vm_area_struct *vma = mm->vmas; - struct vm_area_struct *result = NULL; - while (vma != NULL) - { - if (vma->vm_end > addr) - { - result = vma; - break; - } - vma = vma->vm_next; - } - return result; -} - -/** - * @brief 插入vma - * - * @param mm - * @param vma - * @return int - */ -int vma_insert(struct mm_struct *mm, struct vm_area_struct *vma) -{ - - struct vm_area_struct *prev; - - prev = vma_find(mm, vma->vm_start); - - if (prev && prev->vm_start <= vma->vm_start && prev->vm_end >= vma->vm_end) - { - // 已经存在了相同的vma - return -EEXIST; - } - // todo: bugfix: 这里的第二种情况貌似从来不会满足 - else if (prev && ((vma->vm_start >= prev->vm_start && vma->vm_start <= prev->vm_end) || (prev->vm_start <= vma->vm_end && prev->vm_start >= vma->vm_start))) - { - //部分重叠 - if ((!CROSS_2M_BOUND(vma->vm_start, prev->vm_start)) && (!CROSS_2M_BOUND(vma->vm_end, prev->vm_end))&& vma->vm_end) - { - //合并vma 并改变链表vma的范围 - kdebug("before combining vma:vm_start = %#018lx, vm_end = %#018lx\n", vma->vm_start, vma->vm_end); - - prev->vm_start = (vma->vm_start < prev->vm_start )? vma->vm_start : prev->vm_start; - prev->vm_end = (vma->vm_end > prev->vm_end) ? vma->vm_end : prev->vm_end; - // 计算page_offset - prev->page_offset = prev->vm_start - (prev->vm_start & PAGE_2M_MASK); - kdebug("combined vma:vm_start = %#018lx, vm_end = %#018lx\nprev:vm_start = %018lx, vm_end = %018lx\n", vma->vm_start, vma->vm_end, prev->vm_start, prev->vm_end); - kinfo("vma has same part\n"); - return __VMA_MERGED; - } - } - - // prev = vma_find(mm, vma->vm_start); - - if (prev == NULL) // 要将当前vma插入到链表的尾部 - { - struct vm_area_struct *ptr = mm->vmas; - while (ptr) - { - if (ptr->vm_next) - ptr = ptr->vm_next; - else - { - prev = ptr; - break; - } - } - } - else - prev = prev->vm_prev; - __vma_link_list(mm, vma, prev); - return 0; -} - -/** - * @brief 创建anon_vma,并将其与页面结构体进行绑定 - * 若提供的页面结构体指针为NULL,则只创建,不绑定 - * - * @param page 页面结构体的指针 - * @param lock_page 是否将页面结构体加锁 - * @return struct anon_vma_t* 创建好的anon_vma - */ -struct anon_vma_t *__anon_vma_create_alloc(struct Page *page, bool lock_page) -{ - struct anon_vma_t *anon_vma = (struct anon_vma_t *)kmalloc(sizeof(struct anon_vma_t), 0); - if (unlikely(anon_vma == NULL)) - return NULL; - memset(anon_vma, 0, sizeof(struct anon_vma_t)); - - list_init(&anon_vma->vma_list); - semaphore_init(&anon_vma->sem, 1); - - // 需要和page进行绑定 - if (page != NULL) - { - if (lock_page == true) // 需要加锁 - { - uint64_t rflags; - spin_lock(&page->op_lock); - page->anon_vma = anon_vma; - spin_unlock(&page->op_lock); - } - else - page->anon_vma = anon_vma; - - anon_vma->page = page; - } - return anon_vma; -} - -/** - * @brief 将指定的vma加入到anon_vma的管理范围之中 - * - * @param anon_vma 页面的anon_vma - * @param vma 待加入的vma - * @return int 返回码 - */ -int __anon_vma_add(struct anon_vma_t *anon_vma, struct vm_area_struct *vma) -{ - semaphore_down(&anon_vma->sem); - list_add(&anon_vma->vma_list, &vma->anon_vma_list); - vma->anon_vma = anon_vma; - atomic_inc(&anon_vma->ref_count); - semaphore_up(&anon_vma->sem); - return 0; -} - -/** - * @brief 释放anon vma结构体 - * - * @param anon_vma 待释放的anon_vma结构体 - * @return int 返回码 - */ -int __anon_vma_free(struct anon_vma_t *anon_vma) -{ - if (anon_vma->page != NULL) - { - spin_lock(&anon_vma->page->op_lock); - anon_vma->page->anon_vma = NULL; - spin_unlock(&anon_vma->page->op_lock); - } - kfree(anon_vma); - - return 0; -} - -/** - * @brief 从anon_vma的管理范围中删除指定的vma - * (在进入这个函数之前,应该要对anon_vma加锁) - * @param vma 将要取消对应的anon_vma管理的vma结构体 - * @return int 返回码 - */ -int __anon_vma_del(struct vm_area_struct *vma) -{ - // 当前vma没有绑定anon_vma - if (vma->anon_vma == NULL) - return -EINVAL; - - list_del(&vma->anon_vma_list); - atomic_dec(&vma->anon_vma->ref_count); - - // 若当前anon_vma的引用计数归零,则意味着可以释放内存页 - if (unlikely(atomic_read(&vma->anon_vma->ref_count) == 0)) // 应当释放该anon_vma - { - // 若页面结构体是mmio创建的,则释放页面结构体 - if (vma->anon_vma->page->attr & PAGE_DEVICE) - kfree(vma->anon_vma->page); - else - free_pages(vma->anon_vma->page, 1); - __anon_vma_free(vma->anon_vma); - } - - // 清理当前vma的关联数据 - vma->anon_vma = NULL; - list_init(&vma->anon_vma_list); -} diff --git a/kernel/src/process/abi.rs b/kernel/src/process/abi.rs new file mode 100644 index 00000000..58a7d8ab --- /dev/null +++ b/kernel/src/process/abi.rs @@ -0,0 +1,86 @@ +/// An enumeration of the possible values for the `AT_*` constants. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum AtType { + /// End of vector. + Null, + /// Entry should be ignored. + Ignore, + /// File descriptor of program. + ExecFd, + /// Program headers for program. + Phdr, + /// Size of program header entry. + PhEnt, + /// Number of program headers. + PhNum, + /// System page size. + PageSize, + /// Base address of interpreter. + Base, + /// Flags. + Flags, + /// Entry point of program. + Entry, + /// Program is not ELF. + NotElf, + /// Real uid. + Uid, + /// Effective uid. + EUid, + /// Real gid. + Gid, + /// Effective gid. + EGid, + /// String identifying CPU for optimizations. + Platform, + /// Arch dependent hints at CPU capabilities. + HwCap, + /// Frequency at which times() increments. + ClkTck, + /// Secure mode boolean. + Secure, + /// String identifying real platform, may differ from AT_PLATFORM. + BasePlatform, + /// Address of 16 random bytes. + Random, + /// Extension of AT_HWCAP. + HwCap2, + /// Filename of program. + ExecFn, + /// Minimal stack size for signal delivery. + MinSigStackSize, +} + +impl TryFrom for AtType { + type Error = &'static str; + + fn try_from(value: u32) -> Result { + match value { + 0 => Ok(AtType::Null), + 1 => Ok(AtType::Ignore), + 2 => Ok(AtType::ExecFd), + 3 => Ok(AtType::Phdr), + 4 => Ok(AtType::PhEnt), + 5 => Ok(AtType::PhNum), + 6 => Ok(AtType::PageSize), + 7 => Ok(AtType::Base), + 8 => Ok(AtType::Flags), + 9 => Ok(AtType::Entry), + 10 => Ok(AtType::NotElf), + 11 => Ok(AtType::Uid), + 12 => Ok(AtType::EUid), + 13 => Ok(AtType::Gid), + 14 => Ok(AtType::EGid), + 15 => Ok(AtType::Platform), + 16 => Ok(AtType::HwCap), + 17 => Ok(AtType::ClkTck), + 23 => Ok(AtType::Secure), + 24 => Ok(AtType::BasePlatform), + 25 => Ok(AtType::Random), + 26 => Ok(AtType::HwCap2), + 31 => Ok(AtType::ExecFn), + 51 => Ok(AtType::MinSigStackSize), + _ => Err("Invalid value for AtType"), + } + } +} diff --git a/kernel/src/process/c_adapter.rs b/kernel/src/process/c_adapter.rs new file mode 100644 index 00000000..f7a5a03c --- /dev/null +++ b/kernel/src/process/c_adapter.rs @@ -0,0 +1,115 @@ +use core::{ffi::c_void, ptr::null_mut}; + +use alloc::boxed::Box; + +use crate::{ + arch::{asm::current::current_pcb, fpu::FpState}, + include::bindings::bindings::process_control_block, + syscall::SystemError, +}; + +use super::{fork::copy_mm, process::init_stdio, process_init}; + +#[no_mangle] +pub extern "C" fn rs_process_init() { + process_init(); +} + +#[no_mangle] +pub extern "C" fn rs_process_copy_mm(clone_vm: bool, new_pcb: &mut process_control_block) -> usize { + return copy_mm(clone_vm, new_pcb) + .map(|_| 0) + .unwrap_or_else(|err| err.to_posix_errno() as usize); +} + +/// @brief 初始化当前进程的文件描述符数组 +/// 请注意,如果当前进程已经有文件描述符数组,那么本操作将被禁止 +#[no_mangle] +pub extern "C" fn process_init_files() -> i32 { + let r = current_pcb().init_files(); + if r.is_ok() { + return 0; + } else { + return r.unwrap_err().to_posix_errno(); + } +} + +#[no_mangle] +pub extern "C" fn rs_drop_address_space(pcb: &'static mut process_control_block) -> i32 { + unsafe { + pcb.drop_address_space(); + } + return 0; +} + +/// @brief 拷贝当前进程的文件描述符信息 +/// +/// @param clone_flags 克隆标志位 +/// @param pcb 新的进程的pcb +#[no_mangle] +pub extern "C" fn process_copy_files( + clone_flags: u64, + from: &'static process_control_block, +) -> i32 { + let r = current_pcb().copy_files(clone_flags, from); + if r.is_ok() { + return 0; + } else { + return r.unwrap_err().to_posix_errno(); + } +} + +/// @brief 回收进程的文件描述符数组 +/// +/// @param pcb 要被回收的进程的pcb +/// +/// @return i32 +#[no_mangle] +pub extern "C" fn process_exit_files(pcb: &'static mut process_control_block) -> i32 { + let r: Result<(), SystemError> = pcb.exit_files(); + if r.is_ok() { + return 0; + } else { + return r.unwrap_err().to_posix_errno(); + } +} + +/// @brief 复制当前进程的浮点状态 +#[allow(dead_code)] +#[no_mangle] +pub extern "C" fn rs_dup_fpstate() -> *mut c_void { + // 如果当前进程没有浮点状态,那么就返回一个默认的浮点状态 + if current_pcb().fp_state == null_mut() { + return Box::leak(Box::new(FpState::default())) as *mut FpState as usize as *mut c_void; + } else { + // 如果当前进程有浮点状态,那么就复制一个新的浮点状态 + let state = current_pcb().fp_state as usize as *mut FpState; + unsafe { + let s = state.as_ref().unwrap(); + let state: &mut FpState = Box::leak(Box::new(s.clone())); + + return state as *mut FpState as usize as *mut c_void; + } + } +} + +/// @brief 释放进程的浮点状态所占用的内存 +#[no_mangle] +pub extern "C" fn rs_process_exit_fpstate(pcb: &'static mut process_control_block) { + if pcb.fp_state != null_mut() { + let state = pcb.fp_state as usize as *mut FpState; + unsafe { + drop(Box::from_raw(state)); + } + } +} + +#[no_mangle] +pub extern "C" fn rs_init_stdio() -> i32 { + let r = init_stdio(); + if r.is_ok() { + return 0; + } else { + return r.unwrap_err().to_posix_errno(); + } +} diff --git a/kernel/src/process/exec.rs b/kernel/src/process/exec.rs new file mode 100644 index 00000000..3e27a0d2 --- /dev/null +++ b/kernel/src/process/exec.rs @@ -0,0 +1,288 @@ +use core::{fmt::Debug, ptr::null}; + +use alloc::{collections::BTreeMap, string::String, sync::Arc, vec::Vec}; + +use crate::{ + filesystem::vfs::{ + file::{File, FileMode}, + ROOT_INODE, + }, + io::SeekFrom, + libs::elf::ELF_LOADER, + mm::{ + ucontext::{AddressSpace, UserStack}, + VirtAddr, + }, + syscall::SystemError, +}; + +/// 系统支持的所有二进制文件加载器的列表 +const BINARY_LOADERS: [&'static dyn BinaryLoader; 1] = [&ELF_LOADER]; + +pub trait BinaryLoader: 'static + Debug { + /// 检查二进制文件是否为当前加载器支持的格式 + fn probe(self: &'static Self, param: &ExecParam, buf: &[u8]) -> Result<(), ExecError>; + + fn load( + self: &'static Self, + param: &mut ExecParam, + head_buf: &[u8], + ) -> Result; +} + +/// 二进制文件加载结果 +#[derive(Debug)] +pub struct BinaryLoaderResult { + /// 程序入口地址 + entry_point: VirtAddr, +} + +impl BinaryLoaderResult { + pub fn new(entry_point: VirtAddr) -> Self { + Self { entry_point } + } + + pub fn entry_point(&self) -> VirtAddr { + self.entry_point + } +} + +#[allow(dead_code)] +#[derive(Debug)] +pub enum ExecError { + /// 二进制文件不可执行 + NotExecutable, + /// 二进制文件不是当前架构的 + WrongArchitecture, + /// 访问权限不足 + PermissionDenied, + /// 不支持的操作 + NotSupported, + /// 解析文件本身的时候出现错误(比如一些字段本身不合法) + ParseError, + /// 内存不足 + OutOfMemory, + /// 参数错误 + InvalidParemeter, + /// 无效的地址 + BadAddress(Option), + Other(String), +} + +impl Into for ExecError { + fn into(self) -> SystemError { + match self { + ExecError::NotExecutable => SystemError::ENOEXEC, + ExecError::WrongArchitecture => SystemError::EOPNOTSUPP_OR_ENOTSUP, + ExecError::PermissionDenied => SystemError::EACCES, + ExecError::NotSupported => SystemError::EOPNOTSUPP_OR_ENOTSUP, + ExecError::ParseError => SystemError::ENOEXEC, + ExecError::OutOfMemory => SystemError::ENOMEM, + ExecError::InvalidParemeter => SystemError::EINVAL, + ExecError::BadAddress(_addr) => SystemError::EFAULT, + ExecError::Other(_msg) => SystemError::ENOEXEC, + } + } +} + +bitflags! { + pub struct ExecParamFlags: u32 { + // 是否以可执行文件的形式加载 + const EXEC = 1 << 0; + } +} + +#[derive(Debug)] +pub struct ExecParam<'a> { + file_path: &'a str, + file: Option, + vm: Arc, + /// 一些标志位 + flags: ExecParamFlags, + /// 用来初始化进程的一些信息。这些信息由二进制加载器和exec机制来共同填充 + init_info: ProcInitInfo, +} + +#[derive(Debug, Eq, PartialEq)] +pub enum ExecLoadMode { + /// 以可执行文件的形式加载 + Exec, + /// 以动态链接库的形式加载 + DSO, +} + +#[allow(dead_code)] +impl<'a> ExecParam<'a> { + pub fn new(file_path: &'a str, vm: Arc, flags: ExecParamFlags) -> Self { + Self { + file_path, + file: None, + vm, + flags, + init_info: ProcInitInfo::new(), + } + } + + pub fn file_path(&self) -> &'a str { + self.file_path + } + + pub fn vm(&self) -> &Arc { + &self.vm + } + + pub fn flags(&self) -> &ExecParamFlags { + &self.flags + } + + pub fn init_info(&self) -> &ProcInitInfo { + &self.init_info + } + + pub fn init_info_mut(&mut self) -> &mut ProcInitInfo { + &mut self.init_info + } + + /// 获取加载模式 + pub fn load_mode(&self) -> ExecLoadMode { + if self.flags.contains(ExecParamFlags::EXEC) { + ExecLoadMode::Exec + } else { + ExecLoadMode::DSO + } + } + + pub fn file_mut(&mut self) -> &mut File { + self.file.as_mut().unwrap() + } +} + +/// ## 加载二进制文件 +pub fn load_binary_file(param: &mut ExecParam) -> Result { + let inode = ROOT_INODE().lookup(param.file_path)?; + + // 读取文件头部,用于判断文件类型 + let file = File::new(inode, FileMode::O_RDONLY)?; + param.file = Some(file); + let mut head_buf = [0u8; 512]; + param.file_mut().lseek(SeekFrom::SeekSet(0))?; + let _bytes = param.file_mut().read(512, &mut head_buf)?; + // kdebug!("load_binary_file: read {} bytes", _bytes); + + let mut loader = None; + for bl in BINARY_LOADERS.iter() { + let probe_result = bl.probe(param, &head_buf); + if probe_result.is_ok() { + loader = Some(bl); + break; + } + } + // kdebug!("load_binary_file: loader: {:?}", loader); + if loader.is_none() { + return Err(SystemError::ENOEXEC); + } + + let loader: &&dyn BinaryLoader = loader.unwrap(); + assert!(param.vm().is_current()); + // kdebug!("load_binary_file: to load with param: {:?}", param); + + let result: BinaryLoaderResult = loader + .load(param, &head_buf) + .unwrap_or_else(|e| panic!("load_binary_file failed: error: {e:?}, param: {param:?}")); + + // kdebug!("load_binary_file: load success"); + return Ok(result); +} + +/// 程序初始化信息,这些信息会被压入用户栈中 +#[derive(Debug)] +pub struct ProcInitInfo { + pub args: Vec, + pub envs: Vec, + pub auxv: BTreeMap, +} + +impl ProcInitInfo { + pub fn new() -> Self { + Self { + args: Vec::new(), + envs: Vec::new(), + auxv: BTreeMap::new(), + } + } + + /// 把程序初始化信息压入用户栈中 + /// 这个函数会把参数、环境变量、auxv等信息压入用户栈中 + /// + /// ## 返回值 + /// + /// 返回值是一个元组,第一个元素是最终的用户栈顶地址,第二个元素是环境变量pointer数组的起始地址 + pub unsafe fn push_at( + &self, + ustack: &mut UserStack, + ) -> Result<(VirtAddr, VirtAddr), SystemError> { + // 先把程序的名称压入栈中 + self.push_str(ustack, self.args[0].as_str())?; + + // 然后把环境变量压入栈中 + let envps = self + .envs + .iter() + .map(|s| { + self.push_str(ustack, s.as_str()).expect("push_str failed"); + ustack.sp() + }) + .collect::>(); + + // 然后把参数压入栈中 + let argps = self + .args + .iter() + .map(|s| { + self.push_str(ustack, s.as_str()).expect("push_str failed"); + ustack.sp() + }) + .collect::>(); + + // 压入auxv + self.push_slice(ustack, &[null::(), null::()])?; + for (&k, &v) in self.auxv.iter() { + self.push_slice(ustack, &[k as usize, v])?; + } + + // 把环境变量指针压入栈中 + self.push_slice(ustack, &[null::()])?; + self.push_slice(ustack, envps.as_slice())?; + + // 把参数指针压入栈中 + self.push_slice(ustack, &[null::()])?; + self.push_slice(ustack, argps.as_slice())?; + + let argv_ptr = ustack.sp(); + + // 把argc压入栈中 + self.push_slice(ustack, &[self.args.len()])?; + + return Ok((ustack.sp(), argv_ptr)); + } + + fn push_slice(&self, ustack: &mut UserStack, slice: &[T]) -> Result<(), SystemError> { + let mut sp = ustack.sp(); + sp -= slice.len() * core::mem::size_of::(); + sp -= sp.data() % core::mem::align_of::(); + + unsafe { core::slice::from_raw_parts_mut(sp.data() as *mut T, slice.len()) } + .copy_from_slice(slice); + unsafe { + ustack.set_sp(sp); + } + + return Ok(()); + } + + fn push_str(&self, ustack: &mut UserStack, s: &str) -> Result<(), SystemError> { + self.push_slice(ustack, &[b'\0'])?; + self.push_slice(ustack, s.as_bytes())?; + return Ok(()); + } +} diff --git a/kernel/src/process/fork.c b/kernel/src/process/fork.c index 01147d37..2fcf8b4d 100644 --- a/kernel/src/process/fork.c +++ b/kernel/src/process/fork.c @@ -10,6 +10,7 @@ extern void kernel_thread_func(void); extern uint64_t rs_procfs_register_pid(uint64_t); extern uint64_t rs_procfs_unregister_pid(uint64_t); extern void *rs_dup_fpstate(); +extern uint64_t rs_process_copy_mm(bool clone_vm, struct process_control_block *new_pcb); extern int process_copy_files(uint64_t clone_flags, struct process_control_block *pcb); int process_copy_flags(uint64_t clone_flags, struct process_control_block *pcb); @@ -137,7 +138,7 @@ unsigned long do_fork(struct pt_regs *regs, unsigned long clone_flags, unsigned // 创建对应procfs文件 rs_procfs_register_pid(tsk->pid); - + // kdebug("Fork ok. pid: %d\n", tsk->pid); // 唤醒进程 process_wakeup(tsk); @@ -185,88 +186,9 @@ int process_copy_flags(uint64_t clone_flags, struct process_control_block *pcb) */ int process_copy_mm(uint64_t clone_flags, struct process_control_block *pcb) { - int retval = 0; - // 与父进程共享内存空间 - if (clone_flags & CLONE_VM) - { - pcb->mm = current_pcb->mm; - - return retval; - } - - // 分配新的内存空间分布结构体 - struct mm_struct *new_mms = (struct mm_struct *)kmalloc(sizeof(struct mm_struct), 0); - memset(new_mms, 0, sizeof(struct mm_struct)); - - memcpy(new_mms, current_pcb->mm, sizeof(struct mm_struct)); - new_mms->vmas = NULL; - pcb->mm = new_mms; - - // 分配顶层页表, 并设置顶层页表的物理地址 - new_mms->pgd = (pml4t_t *)virt_2_phys(kmalloc(PAGE_4K_SIZE, 0)); - // 由于高2K部分为内核空间,在接下来需要覆盖其数据,因此不用清零 - memset(phys_2_virt(new_mms->pgd), 0, PAGE_4K_SIZE / 2); - - // 拷贝内核空间的页表指针 - memcpy(phys_2_virt(new_mms->pgd) + 256, phys_2_virt(initial_proc[proc_current_cpu_id]->mm->pgd) + 256, - PAGE_4K_SIZE / 2); - - uint64_t *current_pgd = (uint64_t *)phys_2_virt(current_pcb->mm->pgd); - - uint64_t *new_pml4t = (uint64_t *)phys_2_virt(new_mms->pgd); - - // 拷贝用户空间的vma - struct vm_area_struct *vma = current_pcb->mm->vmas; - while (vma != NULL) - { - if (vma->vm_end > USER_MAX_LINEAR_ADDR || vma->vm_flags & VM_DONTCOPY) - { - vma = vma->vm_next; - continue; - } - - int64_t vma_size = vma->vm_end - vma->vm_start; - // kdebug("vma_size=%ld, vm_start=%#018lx", vma_size, vma->vm_start); - if (vma_size > PAGE_2M_SIZE / 2) - { - int page_to_alloc = (PAGE_2M_ALIGN(vma_size)) >> PAGE_2M_SHIFT; - for (int i = 0; i < page_to_alloc; ++i) - { - uint64_t pa = alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys; - - struct vm_area_struct *new_vma = NULL; - int ret = mm_create_vma(new_mms, vma->vm_start + i * PAGE_2M_SIZE, PAGE_2M_SIZE, vma->vm_flags, - vma->vm_ops, &new_vma); - // 防止内存泄露 - if (unlikely(ret == -EEXIST)) - free_pages(Phy_to_2M_Page(pa), 1); - else - mm_map_vma(new_vma, pa, 0, PAGE_2M_SIZE); - - memcpy((void *)phys_2_virt(pa), (void *)(vma->vm_start + i * PAGE_2M_SIZE), - (vma_size >= PAGE_2M_SIZE) ? PAGE_2M_SIZE : vma_size); - vma_size -= PAGE_2M_SIZE; - } - } - else - { - uint64_t map_size = PAGE_4K_ALIGN(vma_size); - uint64_t va = (uint64_t)kmalloc(map_size, 0); - - struct vm_area_struct *new_vma = NULL; - int ret = mm_create_vma(new_mms, vma->vm_start, map_size, vma->vm_flags, vma->vm_ops, &new_vma); - // 防止内存泄露 - if (unlikely(ret == -EEXIST)) - kfree((void *)va); - else - mm_map_vma(new_vma, virt_2_phys(va), 0, map_size); - - memcpy((void *)va, (void *)vma->vm_start, vma_size); - } - vma = vma->vm_next; - } - - return retval; + pcb->address_space = NULL; + bool clone_vm = (clone_flags & CLONE_VM); + return (int)rs_process_copy_mm(clone_vm, pcb); } /** diff --git a/kernel/src/process/fork.rs b/kernel/src/process/fork.rs index 80b40d9a..09e5f14f 100644 --- a/kernel/src/process/fork.rs +++ b/kernel/src/process/fork.rs @@ -44,7 +44,7 @@ pub extern "C" fn process_copy_sighand(clone_flags: u64, pcb: *mut process_contr // kdebug!("DEFAULT_SIGACTION.sa_flags={}", DEFAULT_SIGACTION.sa_flags); // 拷贝sigaction - let mut flags: u64 = 0; + let mut flags: usize = 0; spin_lock_irqsave(unsafe { &mut (*current_pcb().sighand).siglock }, &mut flags); compiler_fence(core::sync::atomic::Ordering::SeqCst); @@ -64,7 +64,7 @@ pub extern "C" fn process_copy_sighand(clone_flags: u64, pcb: *mut process_contr } compiler_fence(core::sync::atomic::Ordering::SeqCst); - spin_unlock_irqrestore(unsafe { &mut (*current_pcb().sighand).siglock }, &flags); + spin_unlock_irqrestore(unsafe { &mut (*current_pcb().sighand).siglock }, flags); compiler_fence(core::sync::atomic::Ordering::SeqCst); // 将信号的处理函数设置为default(除了那些被手动屏蔽的) @@ -131,3 +131,39 @@ pub extern "C" fn process_exit_sighand(pcb: *mut process_control_block) { (*pcb).signal = 0 as *mut crate::include::bindings::bindings::signal_struct; } } + +/// 拷贝进程的地址空间 +/// +/// ## 参数 +/// +/// - `clone_vm`: 是否与父进程共享地址空间。true表示共享 +/// - `new_pcb`: 新进程的pcb +/// +/// ## 返回值 +/// +/// - 成功:返回Ok(()) +/// - 失败:返回Err(SystemError) +/// +/// ## Panic +/// +/// - 如果当前进程没有用户地址空间,则panic +pub fn copy_mm(clone_vm: bool, new_pcb: &mut process_control_block) -> Result<(), SystemError> { + // kdebug!("copy_mm, clone_vm: {}", clone_vm); + let old_address_space = current_pcb() + .address_space() + .expect("copy_mm: Failed to get address space of current process."); + + if clone_vm { + unsafe { new_pcb.set_address_space(old_address_space) }; + return Ok(()); + } + + let new_address_space = old_address_space.write().try_clone().unwrap_or_else(|e| { + panic!( + "copy_mm: Failed to clone address space of current process, current pid: [{}], new pid: [{}]. Error: {:?}", + current_pcb().pid, new_pcb.pid, e + ) + }); + unsafe { new_pcb.set_address_space(new_address_space) }; + return Ok(()); +} diff --git a/kernel/src/process/mod.rs b/kernel/src/process/mod.rs index 6a5a9707..342dc471 100644 --- a/kernel/src/process/mod.rs +++ b/kernel/src/process/mod.rs @@ -1,6 +1,38 @@ +use core::{ + ptr::null_mut, + sync::atomic::{compiler_fence, Ordering}, +}; + +use crate::{ + arch::asm::current::current_pcb, + kdebug, + mm::{ + set_INITIAL_PROCESS_ADDRESS_SPACE, ucontext::AddressSpace, INITIAL_PROCESS_ADDRESS_SPACE, + }, +}; + +pub mod abi; +pub mod c_adapter; +pub mod exec; pub mod fork; pub mod initial_proc; pub mod pid; pub mod preempt; pub mod process; pub mod syscall; + +pub fn process_init() { + unsafe { + compiler_fence(Ordering::SeqCst); + current_pcb().address_space = null_mut(); + kdebug!("To create address space for INIT process."); + // test_buddy(); + set_INITIAL_PROCESS_ADDRESS_SPACE( + AddressSpace::new(true).expect("Failed to create address space for INIT process."), + ); + kdebug!("INIT process address space created."); + compiler_fence(Ordering::SeqCst); + current_pcb().set_address_space(INITIAL_PROCESS_ADDRESS_SPACE()); + compiler_fence(Ordering::SeqCst); + }; +} diff --git a/kernel/src/process/proc-types.h b/kernel/src/process/proc-types.h index 712806ca..9bb97f5d 100644 --- a/kernel/src/process/proc-types.h +++ b/kernel/src/process/proc-types.h @@ -61,14 +61,14 @@ struct thread_struct // ========= pcb->flags ========= // 进程标志位 -#define PF_KTHREAD (1UL << 0) // 内核线程 -#define PF_NEED_SCHED (1UL << 1) // 进程需要被调度 -#define PF_VFORK (1UL << 2) // 标志进程是否由于vfork而存在资源共享 -#define PF_KFORK (1UL << 3) // 标志在内核态下调用fork(临时标记,do_fork()结束后会将其复位) -#define PF_NOFREEZE (1UL << 4) // 当前进程不能被冻结 -#define PF_EXITING (1UL << 5) // 进程正在退出 -#define PF_WAKEKILL (1UL << 6) // 进程由于接收到终止信号唤醒 -#define PF_SIGNALED (1UL << 7) // 进程由于接收到信号而退出 +#define PF_KTHREAD (1UL << 0) // 内核线程 +#define PF_NEED_SCHED (1UL << 1) // 进程需要被调度 +#define PF_VFORK (1UL << 2) // 标志进程是否由于vfork而存在资源共享 +#define PF_KFORK (1UL << 3) // 标志在内核态下调用fork(临时标记,do_fork()结束后会将其复位) +#define PF_NOFREEZE (1UL << 4) // 当前进程不能被冻结 +#define PF_EXITING (1UL << 5) // 进程正在退出 +#define PF_WAKEKILL (1UL << 6) // 进程由于接收到终止信号唤醒 +#define PF_SIGNALED (1UL << 7) // 进程由于接收到信号而退出 #define PF_NEED_MIGRATE (1UL << 8) // 进程需要迁移到其他的核心 /** @@ -87,9 +87,6 @@ struct process_control_block // pcb的名字 char name[PCB_NAME_LEN]; - // 内存空间分布结构体, 记录内存页表和程序段信息 - struct mm_struct *mm; - // 进程切换时保存的状态信息 struct thread_struct *thread; @@ -111,7 +108,7 @@ struct process_control_block int64_t rt_time_slice; // 由实时调度器管理的时间片 // 进程拥有的文件描述符的指针数组(由Rust进行管理) - void * fds; + void *fds; // 链表中的下一个pcb struct process_control_block *prev_pcb, *next_pcb; @@ -136,11 +133,15 @@ struct process_control_block // 如果当前进程等待被迁移到另一个cpu核心上(也就是flags中的PF_NEED_MIGRATE被置位), // 该字段存储要被迁移到的目标处理器核心号 uint32_t migrate_to; - void* fp_state;//Fpstate 用于用户态切换到内核态时保存浮点寄存器里面的值 + // Fpstate 用于用户态切换到内核态时保存浮点寄存器里面的值 + void *fp_state; + // 指向进程的地址空间的arc指针. + void *address_space; }; // 将进程的pcb和内核栈融合到一起,8字节对齐 -union proc_union { +union proc_union +{ struct process_control_block pcb; ul stack[STACK_SIZE / sizeof(ul)]; } __attribute__((aligned(8))); diff --git a/kernel/src/process/process.c b/kernel/src/process/process.c index 9d4b2b90..6dd9840e 100644 --- a/kernel/src/process/process.c +++ b/kernel/src/process/process.c @@ -38,7 +38,6 @@ extern void kernel_thread_func(void); extern void rs_procfs_unregister_pid(uint64_t); ul _stack_start; // initial proc的栈基地址(虚拟地址) -extern struct mm_struct initial_mm; extern struct signal_struct INITIAL_SIGNALS; extern struct sighand_struct INITIAL_SIGHAND; @@ -46,17 +45,20 @@ extern void process_exit_sighand(struct process_control_block *pcb); extern void process_exit_signal(struct process_control_block *pcb); extern void initial_proc_init_signal(struct process_control_block *pcb); extern void rs_process_exit_fpstate(struct process_control_block *pcb); +extern void rs_drop_address_space(struct process_control_block *pcb); extern int process_init_files(); extern int rs_init_stdio(); +extern uint64_t rs_do_execve(const char *filename, const char *const argv[], const char *const envp[], struct pt_regs *regs); +extern uint64_t rs_exec_init_process(struct pt_regs *regs); // 设置初始进程的PCB -#define INITIAL_PROC(proc) \ - { \ - .state = PROC_UNINTERRUPTIBLE, .flags = PF_KTHREAD, .preempt_count = 0, .signal = 0, .cpu_id = 0, \ - .mm = &initial_mm, .thread = &initial_thread, .addr_limit = 0xffffffffffffffff, .pid = 0, .priority = 2, \ - .virtual_runtime = 0, .fds = {0}, .next_pcb = &proc, .prev_pcb = &proc, .parent_pcb = &proc, .exit_code = 0, \ - .wait_child_proc_exit = 0, .worker_private = NULL, .policy = SCHED_NORMAL, .sig_blocked = 0, \ - .signal = &INITIAL_SIGNALS, .sighand = &INITIAL_SIGHAND, \ +#define INITIAL_PROC(proc) \ + { \ + .state = PROC_UNINTERRUPTIBLE, .flags = PF_KTHREAD, .preempt_count = 0, .signal = 0, .cpu_id = 0, \ + .thread = &initial_thread, .addr_limit = 0xffffffffffffffff, .pid = 0, .priority = 2, \ + .virtual_runtime = 0, .fds = {0}, .next_pcb = &proc, .prev_pcb = &proc, .parent_pcb = &proc, .exit_code = 0, \ + .wait_child_proc_exit = 0, .worker_private = NULL, .policy = SCHED_NORMAL, .sig_blocked = 0, \ + .signal = &INITIAL_SIGNALS, .sighand = &INITIAL_SIGHAND, .address_space = NULL \ } struct thread_struct initial_thread = { @@ -113,8 +115,10 @@ void __switch_to(struct process_control_block *prev, struct process_control_bloc // initial_tss[0].ist2, initial_tss[0].ist3, initial_tss[0].ist4, initial_tss[0].ist5, // initial_tss[0].ist6, initial_tss[0].ist7); - __asm__ __volatile__("movq %%fs, %0 \n\t" : "=a"(prev->thread->fs)); - __asm__ __volatile__("movq %%gs, %0 \n\t" : "=a"(prev->thread->gs)); + __asm__ __volatile__("movq %%fs, %0 \n\t" + : "=a"(prev->thread->fs)); + __asm__ __volatile__("movq %%gs, %0 \n\t" + : "=a"(prev->thread->gs)); __asm__ __volatile__("movq %0, %%fs \n\t" ::"a"(next->thread->fs)); __asm__ __volatile__("movq %0, %%gs \n\t" ::"a"(next->thread->gs)); @@ -145,359 +149,6 @@ int process_open_exec_file(char *path) return fd; } -/** - * @brief 加载elf格式的程序文件到内存中,并设置regs - * - * @param regs 寄存器 - * @param path 文件路径 - * @return int - */ -static int process_load_elf_file(struct pt_regs *regs, char *path) -{ - int retval = 0; - int fd = process_open_exec_file(path); - - if ((long)fd < 0) - { - kdebug("(long)fd=%ld", (long)fd); - return (unsigned long)fd; - } - - void *buf = kzalloc(PAGE_4K_SIZE, 0); - uint64_t pos = 0; - - retval = enter_syscall_int(SYS_LSEEK, fd, 0, SEEK_SET, 0, 0, 0, 0, 0); - - // 读取 Elf64_Ehdr - retval = enter_syscall_int(SYS_READ, fd, (uint64_t)buf, sizeof(Elf64_Ehdr), 0, 0, 0, 0, 0); - - pos = enter_syscall_int(SYS_LSEEK, fd, 0, SEEK_CUR, 0, 0, 0, 0, 0); - - if (retval != sizeof(Elf64_Ehdr)) - { - kerror("retval=%d, not equal to sizeof(Elf64_Ehdr):%d", retval, sizeof(Elf64_Ehdr)); - } - retval = 0; - if (!elf_check(buf)) - { - kerror("Not an ELF file: %s", path); - retval = -ENOTSUP; - goto load_elf_failed; - } - -#if ARCH(X86_64) - // 暂时只支持64位的文件 - if (((Elf32_Ehdr *)buf)->e_ident[EI_CLASS] != ELFCLASS64) - { - kdebug("((Elf32_Ehdr *)buf)->e_ident[EI_CLASS]=%d", ((Elf32_Ehdr *)buf)->e_ident[EI_CLASS]); - retval = -EUNSUPPORTED; - goto load_elf_failed; - } - Elf64_Ehdr ehdr = *(Elf64_Ehdr *)buf; - // 暂时只支持AMD64架构 - if (ehdr.e_machine != EM_AMD64) - { - kerror("e_machine=%d", ehdr.e_machine); - retval = -EUNSUPPORTED; - goto load_elf_failed; - } -#else -#error Unsupported architecture! -#endif - if (ehdr.e_type != ET_EXEC) - { - kerror("Not executable file! filename=%s\tehdr->e_type=%d", path, ehdr.e_type); - retval = -EUNSUPPORTED; - goto load_elf_failed; - } - // kdebug("filename=%s:\te_entry=%#018lx", path, ehdr.e_entry); - regs->rip = ehdr.e_entry; - current_pcb->mm->code_addr_start = ehdr.e_entry; - - // kdebug("ehdr.e_phoff=%#018lx\t ehdr.e_phentsize=%d, ehdr.e_phnum=%d", ehdr.e_phoff, ehdr.e_phentsize, - // ehdr.e_phnum); 将指针移动到program header处 - - // 读取所有的phdr - pos = ehdr.e_phoff; - - pos = enter_syscall_int(SYS_LSEEK, fd, pos, SEEK_SET, 0, 0, 0, 0, 0); - - memset(buf, 0, PAGE_4K_SIZE); - - enter_syscall_int(SYS_READ, fd, (uint64_t)buf, (uint64_t)ehdr.e_phentsize * (uint64_t)ehdr.e_phnum, 0, 0, 0, 0, 0); - - pos = enter_syscall_int(SYS_LSEEK, fd, 0, SEEK_CUR, 0, 0, 0, 0, 0); - - if ((long)retval < 0) - { - kdebug("(unsigned long)filp=%d", (long)retval); - retval = -ENOEXEC; - goto load_elf_failed; - } - - Elf64_Phdr *phdr = buf; - // 将程序加载到内存中 - for (int i = 0; i < ehdr.e_phnum; ++i, ++phdr) - { - // kdebug("phdr[%d] phdr->p_offset=%#018lx phdr->p_vaddr=%#018lx phdr->p_memsz=%ld phdr->p_filesz=%ld - // phdr->p_type=%d", i, phdr->p_offset, phdr->p_vaddr, phdr->p_memsz, phdr->p_filesz, phdr->p_type); - - // 不是可加载的段 - if (phdr->p_type != PT_LOAD) - continue; - - int64_t remain_mem_size = phdr->p_memsz; - int64_t remain_file_size = phdr->p_filesz; - pos = phdr->p_offset; - - uint64_t virt_base = 0; - uint64_t beginning_offset = 0; // 由于页表映射导致的virtbase与实际的p_vaddr之间的偏移量 - - if (remain_mem_size >= PAGE_2M_SIZE) // 接下来存在映射2M页的情况,因此将vaddr按2M向下对齐 - virt_base = phdr->p_vaddr & PAGE_2M_MASK; - else // 接下来只有4K页的映射 - virt_base = phdr->p_vaddr & PAGE_4K_MASK; - - beginning_offset = phdr->p_vaddr - virt_base; - remain_mem_size += beginning_offset; - - while (remain_mem_size > 0) - { - // kdebug("loading..."); - int64_t map_size = 0; - if (remain_mem_size >= PAGE_2M_SIZE) - { - uint64_t pa = alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys; - struct vm_area_struct *vma = NULL; - int ret = - mm_create_vma(current_pcb->mm, virt_base, PAGE_2M_SIZE, VM_USER | VM_ACCESS_FLAGS, NULL, &vma); - - // 防止内存泄露 - if (ret == -EEXIST) - free_pages(Phy_to_2M_Page(pa), 1); - else - mm_map(current_pcb->mm, virt_base, PAGE_2M_SIZE, pa); - // mm_map_vma(vma, pa, 0, PAGE_2M_SIZE); - io_mfence(); - memset((void *)virt_base, 0, PAGE_2M_SIZE); - map_size = PAGE_2M_SIZE; - } - else - { - // todo: 使用4K、8K、32K大小内存块混合进行分配,提高空间利用率(减少了bmp的大小) - map_size = ALIGN(remain_mem_size, PAGE_4K_SIZE); - // 循环分配4K大小内存块 - for (uint64_t off = 0; off < map_size; off += PAGE_4K_SIZE) - { - uint64_t paddr = virt_2_phys((uint64_t)kmalloc(PAGE_4K_SIZE, 0)); - - struct vm_area_struct *vma = NULL; - int val = mm_create_vma(current_pcb->mm, virt_base + off, PAGE_4K_SIZE, VM_USER | VM_ACCESS_FLAGS, - NULL, &vma); - // kdebug("virt_base=%#018lx", virt_base + off); - if (val == -EEXIST) - kfree(phys_2_virt(paddr)); - else - mm_map(current_pcb->mm, virt_base + off, PAGE_4K_SIZE, paddr); - // mm_map_vma(vma, paddr, 0, PAGE_4K_SIZE); - io_mfence(); - memset((void *)(virt_base + off), 0, PAGE_4K_SIZE); - } - } - - pos = enter_syscall_int(SYS_LSEEK, fd, pos, SEEK_SET, 0, 0, 0, 0, 0); - - int64_t val = 0; - if (remain_file_size > 0) - { - int64_t to_trans = (remain_file_size > PAGE_2M_SIZE) ? PAGE_2M_SIZE : remain_file_size; - - void *buf3 = kzalloc(PAGE_4K_SIZE, 0); - while (to_trans > 0) - { - int64_t x = 0; - - x = enter_syscall_int(SYS_READ, fd, (uint64_t)buf3, to_trans, 0, 0, 0, 0, 0); - memcpy(virt_base + beginning_offset + val, buf3, x); - val += x; - to_trans -= x; - - pos = enter_syscall_int(SYS_LSEEK, fd, 0, SEEK_CUR, 0, 0, 0, 0, 0); - } - kfree(buf3); - - // kdebug("virt_base + beginning_offset=%#018lx, val=%d, to_trans=%d", virt_base + beginning_offset, - // val, - // to_trans); - // kdebug("to_trans=%d", to_trans); - } - - if (val < 0) - goto load_elf_failed; - - remain_mem_size -= map_size; - remain_file_size -= val; - virt_base += map_size; - } - } - - // 分配2MB的栈内存空间 - regs->rsp = current_pcb->mm->stack_start; - regs->rbp = current_pcb->mm->stack_start; - - { - struct vm_area_struct *vma = NULL; - uint64_t pa = alloc_pages(ZONE_NORMAL, 1, PAGE_PGT_MAPPED)->addr_phys; - int val = mm_create_vma(current_pcb->mm, current_pcb->mm->stack_start - PAGE_2M_SIZE, PAGE_2M_SIZE, - VM_USER | VM_ACCESS_FLAGS, NULL, &vma); - if (val == -EEXIST) - free_pages(Phy_to_2M_Page(pa), 1); - else - mm_map_vma(vma, pa, 0, PAGE_2M_SIZE); - } - - // 清空栈空间 - memset((void *)(current_pcb->mm->stack_start - PAGE_2M_SIZE), 0, PAGE_2M_SIZE); - -load_elf_failed:; - { - enter_syscall_int(SYS_CLOSE, fd, 0, 0, 0, 0, 0, 0, 0); - } - - if (buf != NULL) - kfree(buf); - return retval; -} -/** - * @brief 使当前进程去执行新的代码 - * - * @param regs 当前进程的寄存器 - * @param path 可执行程序的路径 - * @param argv 参数列表 - * @param envp 环境变量 - * @return ul 错误码 - */ -#pragma GCC push_options -#pragma GCC optimize("O0") -ul do_execve(struct pt_regs *regs, char *path, char *argv[], char *envp[]) -{ - - // 当前进程正在与父进程共享地址空间,需要创建 - // 独立的地址空间才能使新程序正常运行 - if (current_pcb->flags & PF_VFORK) - { - // kdebug("proc:%d creating new mem space", current_pcb->pid); - // 分配新的内存空间分布结构体 - struct mm_struct *new_mms = (struct mm_struct *)kmalloc(sizeof(struct mm_struct), 0); - memset(new_mms, 0, sizeof(struct mm_struct)); - current_pcb->mm = new_mms; - - // 分配顶层页表, 并设置顶层页表的物理地址 - new_mms->pgd = (pml4t_t *)virt_2_phys(kmalloc(PAGE_4K_SIZE, 0)); - - // 由于高2K部分为内核空间,在接下来需要覆盖其数据,因此不用清零 - memset(phys_2_virt(new_mms->pgd), 0, PAGE_4K_SIZE / 2); - - // 拷贝内核空间的页表指针 - memcpy(phys_2_virt(new_mms->pgd) + 256, phys_2_virt(initial_proc[proc_current_cpu_id]) + 256, PAGE_4K_SIZE / 2); - } - - // 设置用户栈和用户堆的基地址 - unsigned long stack_start_addr = 0x6ffff0a00000UL; - const uint64_t brk_start_addr = 0x700000000000UL; - - process_switch_mm(current_pcb); - - // 为用户态程序设置地址边界 - if (!(current_pcb->flags & PF_KTHREAD)) - current_pcb->addr_limit = USER_MAX_LINEAR_ADDR; - - current_pcb->mm->code_addr_end = 0; - current_pcb->mm->data_addr_start = 0; - current_pcb->mm->data_addr_end = 0; - current_pcb->mm->rodata_addr_start = 0; - current_pcb->mm->rodata_addr_end = 0; - current_pcb->mm->bss_start = 0; - current_pcb->mm->bss_end = 0; - current_pcb->mm->brk_start = brk_start_addr; - current_pcb->mm->brk_end = brk_start_addr; - current_pcb->mm->stack_start = stack_start_addr; - - // 清除进程的vfork标志位 - current_pcb->flags &= ~PF_VFORK; - - // 加载elf格式的可执行文件 - int tmp = process_load_elf_file(regs, path); - - if (tmp < 0) - goto exec_failed; - - int argc = 0; - char **dst_argv = NULL; - // kdebug("stack_start_addr=%#018lx", stack_start_addr); - // 拷贝参数列表 - if (argv != NULL) - { - - // 目标程序的argv基地址指针,最大8个参数 - dst_argv = (char **)(stack_start_addr - (sizeof(char **) << 3)); - uint64_t str_addr = (uint64_t)dst_argv; - - for (argc = 0; argc < 8 && argv[argc] != NULL; ++argc) - { - - if (*argv[argc] == NULL) - break; - - // 测量参数的长度(最大1023) - int argv_len = strnlen_user(argv[argc], 1023) + 1; - strncpy((char *)(str_addr - argv_len), argv[argc], argv_len - 1); - str_addr -= argv_len; - dst_argv[argc] = (char *)str_addr; - // 字符串加上结尾字符 - ((char *)str_addr)[argv_len] = '\0'; - } - - // 重新设定栈基址,并预留空间防止越界 - - stack_start_addr = str_addr - 8; - } - - // kdebug("stack_start_addr=%#018lx", stack_start_addr); - // ==== 生成relibc所需的Stack结构体 - { - uint64_t *ptr_stack = (uint64_t *)(stack_start_addr - 8); - if (argc == 0) - *ptr_stack = 0; - else - *ptr_stack = (uint64_t)dst_argv; - ptr_stack--; - *ptr_stack = argc; - stack_start_addr -= 16; - } - - // 传递参数(旧版libc) - regs->rdi = argc; - regs->rsi = (uint64_t)dst_argv; - // 设置用户栈基地址 - current_pcb->mm->stack_start = stack_start_addr; - regs->rsp = regs->rbp = stack_start_addr; - // kdebug("execve ok"); - // 设置进程的段选择子为用户态可访问 - regs->cs = USER_CS | 3; - regs->ds = USER_DS | 3; - regs->ss = USER_DS | 0x3; - regs->rflags = 0x200246; - regs->rax = 1; - regs->es = 0; - - return 0; - -exec_failed:; - process_do_exit(tmp); -} -#pragma GCC pop_options - /** * @brief 初始化实时进程rt_pcb * @@ -527,43 +178,24 @@ ul initial_kernel_thread(ul arg) kinfo("initial proc running...\targ:%#018lx, vruntime=%d", arg, current_pcb->virtual_runtime); int val = 0; val = scm_enable_double_buffer(); - + io_mfence(); rs_init_stdio(); + io_mfence(); // block_io_scheduler_init(); ahci_init(); mount_root_fs(); + io_mfence(); rs_virtio_probe(); + io_mfence(); + // 使用单独的内核线程来初始化usb驱动程序 // 注释:由于目前usb驱动程序不完善,因此先将其注释掉 // int usb_pid = kernel_thread(usb_init, 0, 0); kinfo("LZ4 lib Version=%s", LZ4_versionString()); + io_mfence(); __rust_demo_func(); - // while (1) - // { - // /* code */ - // } - - // 对completion完成量进行测试 - // __test_completion(); - - // // 对一些组件进行单元测试 - uint64_t tpid[] = { - // ktest_start(ktest_test_bitree, 0), ktest_start(ktest_test_kfifo, 0), ktest_start(ktest_test_mutex, 0), - // ktest_start(ktest_test_idr, 0), - // usb_pid, - }; - - // kinfo("Waiting test thread exit..."); - // // 等待测试进程退出 - // for (int i = 0; i < sizeof(tpid) / sizeof(uint64_t); ++i) - // waitpid(tpid[i], NULL, NULL); - // kinfo("All test done."); - - // 测试实时进程 - - // struct process_control_block *test_rt1 = kthread_run_rt(&test, NULL, "test rt"); - // kdebug("process:rt test kthread is created!!!!"); + io_mfence(); // 准备切换到用户态 struct pt_regs *regs; @@ -588,8 +220,8 @@ ul initial_kernel_thread(ul arg) // 这里的设计思路和switch_to类似 加载用户态程序:shell.elf __asm__ __volatile__("movq %1, %%rsp \n\t" "pushq %2 \n\t" - "jmp do_execve \n\t" ::"D"(current_pcb->thread->rsp), - "m"(current_pcb->thread->rsp), "m"(current_pcb->thread->rip), "S"("/bin/shell.elf"), "c"(NULL), + "jmp rs_exec_init_process \n\t" ::"D"(current_pcb->thread->rsp), + "m"(current_pcb->thread->rsp), "m"(current_pcb->thread->rip), "c"(NULL), "d"(NULL) : "memory"); @@ -678,9 +310,15 @@ pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) * @brief 初始化进程模块 * ☆前置条件:已完成系统调用模块的初始化 */ +#pragma GCC push_options +#pragma GCC optimize("O0") void process_init() { kinfo("Initializing process..."); + // rs_test_buddy(); + io_mfence(); + rs_process_init(); + io_mfence(); initial_tss[proc_current_cpu_id].rsp0 = initial_thread.rbp; @@ -692,18 +330,23 @@ void process_init() list_init(&initial_proc_union.pcb.list); wait_queue_init(&initial_proc_union.pcb.wait_child_proc_exit, NULL); + io_mfence(); // 初始化init进程的signal相关的信息 initial_proc_init_signal(current_pcb); kdebug("Initial process to init files"); + io_mfence(); process_init_files(); kdebug("Initial process init files ok"); + io_mfence(); // 临时设置IDLE进程的的虚拟运行时间为0,防止下面的这些内核线程的虚拟运行时间出错 current_pcb->virtual_runtime = 0; + barrier(); kernel_thread(initial_kernel_thread, 10, CLONE_FS | CLONE_SIGNAL); // 初始化内核线程 barrier(); kthread_mechanism_init(); // 初始化kthread机制 + barrier(); initial_proc_union.pcb.state = PROC_RUNNING; initial_proc_union.pcb.preempt_count = 0; @@ -712,6 +355,7 @@ void process_init() // 将IDLE进程的虚拟运行时间设置为一个很大的数值 current_pcb->virtual_runtime = (1UL << 60); } +#pragma GCC pop_options /** * @brief 根据pid获取进程的pcb。存在对应的pcb时,返回对应的pcb的指针,否则返回NULL @@ -786,57 +430,7 @@ int process_wakeup_immediately(struct process_control_block *pcb) */ uint64_t process_exit_mm(struct process_control_block *pcb) { - if (pcb->flags & CLONE_VM) - return 0; - if (pcb->mm == NULL) - { - kdebug("pcb->mm==NULL"); - return 0; - } - if (pcb->mm->pgd == NULL) - { - kdebug("pcb->mm->pgd==NULL"); - return 0; - } - - // // 获取顶层页表 - pml4t_t *current_pgd = (pml4t_t *)phys_2_virt(pcb->mm->pgd); - - // 循环释放VMA中的内存 - struct vm_area_struct *vma = pcb->mm->vmas; - while (vma != NULL) - { - - struct vm_area_struct *cur_vma = vma; - vma = cur_vma->vm_next; - - uint64_t pa; - mm_unmap_vma(pcb->mm, cur_vma, &pa); - - uint64_t size = (cur_vma->vm_end - cur_vma->vm_start); - - // 释放内存 - switch (size) - { - case PAGE_4K_SIZE: - kfree(phys_2_virt(pa)); - break; - default: - break; - } - vm_area_del(cur_vma); - vm_area_free(cur_vma); - } - - // 释放顶层页表 - kfree(current_pgd); - if (unlikely(pcb->mm->vmas != NULL)) - { - kwarn("pcb.mm.vmas!=NULL"); - } - // 释放内存空间分布结构体 - kfree(pcb->mm); - + rs_drop_address_space(pcb); return 0; } @@ -857,9 +451,6 @@ void process_exit_thread(struct process_control_block *pcb) */ int process_release_pcb(struct process_control_block *pcb) { - // 释放子进程的页表 - // BUG 暂时注释process_exit_mm - // process_exit_mm(pcb); if ((pcb->flags & PF_KTHREAD)) // 释放内核线程的worker private结构体 free_kthread_struct(pcb); @@ -871,6 +462,8 @@ int process_release_pcb(struct process_control_block *pcb) process_exit_signal(pcb); rs_process_exit_fpstate(pcb); rs_procfs_unregister_pid(pcb->pid); + // 释放进程的地址空间 + process_exit_mm(pcb); // 释放当前pcb kfree(pcb); return 0; diff --git a/kernel/src/process/process.h b/kernel/src/process/process.h index 99a0652a..d09cc670 100644 --- a/kernel/src/process/process.h +++ b/kernel/src/process/process.h @@ -31,18 +31,18 @@ extern int process_exit_files(struct process_control_block *pcb); */ // 设置初始进程的tss -#define INITIAL_TSS \ - { \ - .reserved0 = 0, .rsp0 = (ul)(initial_proc_union.stack + STACK_SIZE / sizeof(ul)), \ - .rsp1 = (ul)(initial_proc_union.stack + STACK_SIZE / sizeof(ul)), \ - .rsp2 = (ul)(initial_proc_union.stack + STACK_SIZE / sizeof(ul)), .reserved1 = 0, .ist1 = 0xffff800000007c00, \ - .ist2 = 0xffff800000007c00, .ist3 = 0xffff800000007c00, .ist4 = 0xffff800000007c00, \ - .ist5 = 0xffff800000007c00, .ist6 = 0xffff800000007c00, .ist7 = 0xffff800000007c00, .reserved2 = 0, \ - .reserved3 = 0, .io_map_base_addr = 0 \ +#define INITIAL_TSS \ + { \ + .reserved0 = 0, .rsp0 = (ul)(initial_proc_union.stack + STACK_SIZE / sizeof(ul)), \ + .rsp1 = (ul)(initial_proc_union.stack + STACK_SIZE / sizeof(ul)), \ + .rsp2 = (ul)(initial_proc_union.stack + STACK_SIZE / sizeof(ul)), .reserved1 = 0, .ist1 = 0xffff800000007c00, \ + .ist2 = 0xffff800000007c00, .ist3 = 0xffff800000007c00, .ist4 = 0xffff800000007c00, \ + .ist5 = 0xffff800000007c00, .ist6 = 0xffff800000007c00, .ist7 = 0xffff800000007c00, .reserved2 = 0, \ + .reserved3 = 0, .io_map_base_addr = 0 \ } -#define GET_CURRENT_PCB \ - "movq %rsp, %rbx \n\t" \ +#define GET_CURRENT_PCB \ + "movq %rsp, %rbx \n\t" \ "andq $-32768, %rbx\n\t" /** @@ -51,23 +51,23 @@ extern int process_exit_files(struct process_control_block *pcb); * 然后调用__switch_to切换栈,配置其他信息,最后恢复下一个进程的rax rbp。 */ -#define switch_to(prev, next) \ - do \ - { \ - __asm__ __volatile__("pushq %%rbp \n\t" \ - "pushq %%rax \n\t" \ - "movq %%rsp, %0 \n\t" \ - "movq %2, %%rsp \n\t" \ - "leaq 2f(%%rip), %%rax \n\t" \ - "movq %%rax, %1 \n\t" \ - "pushq %3 \n\t" \ - "jmp __switch_to \n\t" \ - "2: \n\t" \ - "popq %%rax \n\t" \ - "popq %%rbp \n\t" \ - : "=m"(prev->thread->rsp), "=m"(prev->thread->rip) \ - : "m"(next->thread->rsp), "m"(next->thread->rip), "D"(prev), "S"(next) \ - : "memory", "rax"); \ +#define switch_to(prev, next) \ + do \ + { \ + __asm__ __volatile__("pushq %%rbp \n\t" \ + "pushq %%rax \n\t" \ + "movq %%rsp, %0 \n\t" \ + "movq %2, %%rsp \n\t" \ + "leaq 2f(%%rip), %%rax \n\t" \ + "movq %%rax, %1 \n\t" \ + "pushq %3 \n\t" \ + "jmp __switch_to \n\t" \ + "2: \n\t" \ + "popq %%rax \n\t" \ + "popq %%rbp \n\t" \ + : "=m"(prev->thread->rsp), "=m"(prev->thread->rip) \ + : "m"(next->thread->rsp), "m"(next->thread->rip), "D"(prev), "S"(next) \ + : "memory", "rax"); \ } while (0) /** @@ -112,17 +112,6 @@ int process_wakeup(struct process_control_block *pcb); */ int process_wakeup_immediately(struct process_control_block *pcb); -/** - * @brief 使当前进程去执行新的代码 - * - * @param regs 当前进程的寄存器 - * @param path 可执行程序的路径 - * @param argv 参数列表 - * @param envp 环境变量 - * @return ul 错误码 - */ -ul do_execve(struct pt_regs *regs, char *path, char *argv[], char *envp[]); - /** * @brief 释放进程的页表 * @@ -166,10 +155,11 @@ int process_release_pcb(struct process_control_block *pcb); * @param next 下一个进程的pcb * */ -#define process_switch_mm(next_pcb) \ - do \ - { \ - asm volatile("movq %0, %%cr3 \n\t" ::"r"(next_pcb->mm->pgd) : "memory"); \ +#define process_switch_mm(next_pcb) \ + do \ + { \ + asm volatile("movq %0, %%cr3 \n\t" ::"r"(next_pcb->mm->pgd) \ + : "memory"); \ } while (0) // flush_tlb(); diff --git a/kernel/src/process/process.rs b/kernel/src/process/process.rs index 4f718645..084ee52d 100644 --- a/kernel/src/process/process.rs +++ b/kernel/src/process/process.rs @@ -1,12 +1,13 @@ use core::{ ffi::c_void, + mem::ManuallyDrop, ptr::{null_mut, read_volatile, write_volatile}, }; use alloc::{boxed::Box, sync::Arc}; use crate::{ - arch::{asm::current::current_pcb, fpu::FpState}, + arch::asm::current::current_pcb, filesystem::vfs::{ file::{File, FileDescriptorVec, FileMode}, FileType, ROOT_INODE, @@ -16,6 +17,7 @@ use crate::{ PROC_UNINTERRUPTIBLE, }, libs::casting::DowncastArc, + mm::ucontext::AddressSpace, net::socket::SocketInode, sched::core::{cpu_executing, sched_enqueue}, smp::core::{smp_get_processor_id, smp_send_reschedule}, @@ -311,94 +313,41 @@ impl process_control_block { .expect("Not a socket inode"); return Some(socket); } -} -// =========== 导出到C的函数,在将来,进程管理模块被完全重构之后,需要删掉他们 BEGIN ============ - -/// @brief 初始化当前进程的文件描述符数组 -/// 请注意,如果当前进程已经有文件描述符数组,那么本操作将被禁止 -#[no_mangle] -pub extern "C" fn process_init_files() -> i32 { - let r = current_pcb().init_files(); - if r.is_ok() { - return 0; - } else { - return r.unwrap_err().to_posix_errno(); - } -} - -/// @brief 拷贝当前进程的文件描述符信息 -/// -/// @param clone_flags 克隆标志位 -/// @param pcb 新的进程的pcb -#[no_mangle] -pub extern "C" fn process_copy_files( - clone_flags: u64, - from: &'static process_control_block, -) -> i32 { - let r = current_pcb().copy_files(clone_flags, from); - if r.is_ok() { - return 0; - } else { - return r.unwrap_err().to_posix_errno(); - } -} - -/// @brief 回收进程的文件描述符数组 -/// -/// @param pcb 要被回收的进程的pcb -/// -/// @return i32 -#[no_mangle] -pub extern "C" fn process_exit_files(pcb: &'static mut process_control_block) -> i32 { - let r: Result<(), SystemError> = pcb.exit_files(); - if r.is_ok() { - return 0; - } else { - return r.unwrap_err().to_posix_errno(); - } -} - -/// @brief 复制当前进程的浮点状态 -#[allow(dead_code)] -#[no_mangle] -pub extern "C" fn rs_dup_fpstate() -> *mut c_void { - // 如果当前进程没有浮点状态,那么就返回一个默认的浮点状态 - if current_pcb().fp_state == null_mut() { - return Box::leak(Box::new(FpState::default())) as *mut FpState as usize as *mut c_void; - } else { - // 如果当前进程有浮点状态,那么就复制一个新的浮点状态 - let state = current_pcb().fp_state as usize as *mut FpState; - unsafe { - let s = state.as_ref().unwrap(); - let state: &mut FpState = Box::leak(Box::new(s.clone())); - - return state as *mut FpState as usize as *mut c_void; + /// 释放pcb中存储的地址空间的指针 + pub unsafe fn drop_address_space(&mut self) { + let p = self.address_space as *const AddressSpace; + if p.is_null() { + return; } + let p: Arc = Arc::from_raw(p); + drop(p); + self.address_space = null_mut(); } -} -/// @brief 释放进程的浮点状态所占用的内存 -#[no_mangle] -pub extern "C" fn rs_process_exit_fpstate(pcb: &'static mut process_control_block) { - if pcb.fp_state != null_mut() { - let state = pcb.fp_state as usize as *mut FpState; - unsafe { - drop(Box::from_raw(state)); + /// 设置pcb中存储的地址空间的指针 + /// + /// ## panic + /// 如果当前pcb已经有地址空间,那么panic + pub unsafe fn set_address_space(&mut self, address_space: Arc) { + assert!(self.address_space.is_null(), "Address space already set"); + self.address_space = Arc::into_raw(address_space) as *mut c_void; + } + + /// 获取当前进程的地址空间的指针 + pub fn address_space(&self) -> Option> { + let ptr = self.address_space as *const AddressSpace; + if ptr.is_null() { + return None; } + // 为了防止pcb中的指针被释放,这里需要将其包装一下,使得Arc的drop不会被调用 + let arc_wrapper = ManuallyDrop::new(unsafe { Arc::from_raw(ptr) }); + + let result = Arc::clone(&arc_wrapper); + return Some(result); } } -#[no_mangle] -pub extern "C" fn rs_init_stdio() -> i32 { - let r = init_stdio(); - if r.is_ok() { - return 0; - } else { - return r.unwrap_err().to_posix_errno(); - } -} -// =========== 以上为导出到C的函数,在将来,进程管理模块被完全重构之后,需要删掉他们 END ============ /// @brief 初始化pid=1的进程的stdio pub fn init_stdio() -> Result<(), SystemError> { diff --git a/kernel/src/sched/cfs.rs b/kernel/src/sched/cfs.rs index 9731e983..78d51ea0 100644 --- a/kernel/src/sched/cfs.rs +++ b/kernel/src/sched/cfs.rs @@ -58,24 +58,24 @@ impl CFSQueue { /// @brief 将pcb加入队列 pub fn enqueue(&mut self, pcb: &'static mut process_control_block) { - let mut rflags = 0u64; + let mut rflags = 0usize; self.lock.lock_irqsave(&mut rflags); // 如果进程是IDLE进程,那么就不加入队列 if pcb.pid == 0 { - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); return; } self.queue.insert(pcb.virtual_runtime, pcb); - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); } /// @brief 将pcb从调度队列中弹出,若队列为空,则返回IDLE进程的pcb pub fn dequeue(&mut self) -> &'static mut process_control_block { let res: &'static mut process_control_block; - let mut rflags = 0u64; + let mut rflags = 0usize; self.lock.lock_irqsave(&mut rflags); if !self.queue.is_empty() { // 队列不为空,返回下一个要执行的pcb @@ -84,7 +84,7 @@ impl CFSQueue { // 如果队列为空,则返回IDLE进程的pcb res = unsafe { self.idle_pcb.as_mut().unwrap() }; } - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); return res; } diff --git a/kernel/src/sched/core.c b/kernel/src/sched/core.c index 8f7a86f5..9d90fa71 100644 --- a/kernel/src/sched/core.c +++ b/kernel/src/sched/core.c @@ -8,7 +8,7 @@ */ void switch_proc(struct process_control_block *prev, struct process_control_block *proc) { - process_switch_mm(proc); + // process_switch_mm(proc); io_mfence(); switch_to(prev, proc); } \ No newline at end of file diff --git a/kernel/src/sched/core.rs b/kernel/src/sched/core.rs index 4aec5f0b..aa1fb17d 100644 --- a/kernel/src/sched/core.rs +++ b/kernel/src/sched/core.rs @@ -7,6 +7,7 @@ use crate::{ process_control_block, MAX_CPU_NUM, PF_NEED_MIGRATE, PROC_RUNNING, SCHED_FIFO, SCHED_NORMAL, SCHED_RR, }, + kinfo, process::process::process_cpu, syscall::SystemError, }; @@ -140,10 +141,12 @@ pub extern "C" fn sched_enqueue(pcb: &'static mut process_control_block, mut res #[allow(dead_code)] #[no_mangle] pub extern "C" fn sched_init() { + kinfo!("Initializing schedulers..."); unsafe { sched_cfs_init(); sched_rt_init(); } + kinfo!("Schedulers initialized"); } /// @brief 当时钟中断到达时,更新时间片 diff --git a/kernel/src/sched/rt.rs b/kernel/src/sched/rt.rs index 47e6801d..ea3ccad1 100644 --- a/kernel/src/sched/rt.rs +++ b/kernel/src/sched/rt.rs @@ -50,22 +50,22 @@ impl RTQueue { } /// @brief 将pcb加入队列 pub fn enqueue(&mut self, pcb: &'static mut process_control_block) { - let mut rflags = 0u64; + let mut rflags = 0usize; self.lock.lock_irqsave(&mut rflags); // 如果进程是IDLE进程,那么就不加入队列 if pcb.pid == 0 { - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); return; } self.queue.push_back(pcb); - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); } /// @brief 将pcb从调度队列头部取出,若队列为空,则返回None pub fn dequeue(&mut self) -> Option<&'static mut process_control_block> { let res: Option<&'static mut process_control_block>; - let mut rflags = 0u64; + let mut rflags = 0usize; self.lock.lock_irqsave(&mut rflags); if self.queue.len() > 0 { // 队列不为空,返回下一个要执行的pcb @@ -74,20 +74,20 @@ impl RTQueue { // 如果队列为空,则返回None res = None; } - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); return res; } pub fn enqueue_front(&mut self, pcb: &'static mut process_control_block) { - let mut rflags = 0u64; + let mut rflags = 0usize; self.lock.lock_irqsave(&mut rflags); // 如果进程是IDLE进程,那么就不加入队列 if pcb.pid == 0 { - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); return; } self.queue.push_front(pcb); - self.lock.unlock_irqrestore(&rflags); + self.lock.unlock_irqrestore(rflags); } pub fn get_rt_queue_size(&mut self) -> usize { return self.queue.len(); diff --git a/kernel/src/sched/syscall.rs b/kernel/src/sched/syscall.rs index 15cffc90..f62821fe 100644 --- a/kernel/src/sched/syscall.rs +++ b/kernel/src/sched/syscall.rs @@ -1,9 +1,6 @@ use crate::{ - arch::{ - asm::current::current_pcb, - context::switch_process, - interrupt::{cli, sti}, - }, + arch::{asm::current::current_pcb, context::switch_process, CurrentIrqArch}, + exception::InterruptArch, syscall::{Syscall, SystemError}, }; @@ -14,7 +11,8 @@ impl Syscall { /// 请注意,该系统调用不能由ring3的程序发起 #[inline(always)] pub fn sched(from_user: bool) -> Result { - cli(); + let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() }; + // 进行权限校验,拒绝用户态发起调度 if from_user { return Err(SystemError::EPERM); @@ -25,7 +23,7 @@ impl Syscall { if pcb.is_some() { switch_process(current_pcb(), pcb.unwrap()); } - sti(); + drop(irq_guard); return Ok(0); } } diff --git a/kernel/src/smp/c_adapter.rs b/kernel/src/smp/c_adapter.rs new file mode 100644 index 00000000..e1d8e544 --- /dev/null +++ b/kernel/src/smp/c_adapter.rs @@ -0,0 +1,13 @@ +use super::kick_cpu; + +#[no_mangle] +pub extern "C" fn rs_kick_cpu(cpu_id: usize) -> usize { + return kick_cpu(cpu_id) + .map(|_| 0usize) + .unwrap_or_else(|e| e.to_posix_errno() as usize); +} + +#[no_mangle] +pub unsafe extern "C" fn rs_smp_init_idle() { + crate::smp::init_smp_idle_process(); +} diff --git a/kernel/src/smp/mod.rs b/kernel/src/smp/mod.rs index 021f7b39..ee4a9698 100644 --- a/kernel/src/smp/mod.rs +++ b/kernel/src/smp/mod.rs @@ -1,9 +1,11 @@ use crate::{ - arch::interrupt::ipi::send_ipi, + arch::{asm::current::current_pcb, interrupt::ipi::send_ipi}, exception::ipi::{IpiKind, IpiTarget}, + mm::INITIAL_PROCESS_ADDRESS_SPACE, syscall::SystemError, }; +pub mod c_adapter; pub mod core; pub fn kick_cpu(cpu_id: usize) -> Result<(), SystemError> { @@ -13,9 +15,7 @@ pub fn kick_cpu(cpu_id: usize) -> Result<(), SystemError> { return Ok(()); } -#[no_mangle] -pub extern "C" fn rs_kick_cpu(cpu_id: usize) -> usize { - return kick_cpu(cpu_id) - .map(|_| 0usize) - .unwrap_or_else(|e| e.to_posix_errno() as usize); +/// 初始化AP核的idle进程 +pub unsafe fn init_smp_idle_process() { + current_pcb().set_address_space(INITIAL_PROCESS_ADDRESS_SPACE()); } diff --git a/kernel/src/smp/smp.c b/kernel/src/smp/smp.c index 316663df..3e8dfbca 100644 --- a/kernel/src/smp/smp.c +++ b/kernel/src/smp/smp.c @@ -14,6 +14,7 @@ #include "ipi.h" static void __smp_kick_cpu_handler(uint64_t irq_num, uint64_t param, struct pt_regs *regs); +static void __smp__flush_tlb_ipi_handler(uint64_t irq_num, uint64_t param, struct pt_regs *regs); static spinlock_t multi_core_starting_lock = {1}; // 多核启动锁 @@ -22,13 +23,22 @@ static uint32_t total_processor_num = 0; static int current_starting_cpu = 0; static int num_cpu_started = 1; +extern void rs_smp_init_idle(); + +// 在head.S中定义的,APU启动时,要加载的页表 +// 由于内存管理模块初始化的时候,重置了页表,因此我们要把当前的页表传给APU +extern uint64_t __APU_START_CR3; // kick cpu 功能所使用的中断向量号 #define KICK_CPU_IRQ_NUM 0xc8 +#define FLUSH_TLB_IRQ_NUM 0xc9 void smp_init() { spin_init(&multi_core_starting_lock); // 初始化多核启动锁 + // 设置多核启动时,要加载的页表 + __APU_START_CR3 = (uint64_t)get_CR3(); + ul tmp_vaddr[MAX_SUPPORTED_PROCESSOR_NUM] = {0}; apic_get_ics(ACPI_ICS_TYPE_PROCESSOR_LOCAL_APIC, tmp_vaddr, &total_processor_num); @@ -57,6 +67,7 @@ void smp_init() kdebug("total_processor_num=%d", total_processor_num); // 注册接收kick_cpu功能的处理函数。(向量号200) ipi_regiserIPI(KICK_CPU_IRQ_NUM, NULL, &__smp_kick_cpu_handler, NULL, NULL, "IPI kick cpu"); + ipi_regiserIPI(FLUSH_TLB_IRQ_NUM, NULL, &__smp__flush_tlb_ipi_handler, NULL, NULL, "IPI flush tlb"); int core_to_start = 0; // total_processor_num = 3; @@ -104,7 +115,7 @@ void smp_init() cpu_core_info[current_starting_cpu].tss_vaddr = (uint64_t)&initial_tss[current_starting_cpu]; memset(&initial_tss[current_starting_cpu], 0, sizeof(struct tss_struct)); - + // kdebug("core %d, set tss", current_starting_cpu); set_tss_descriptor(10 + (current_starting_cpu * 2), (void *)(cpu_core_info[current_starting_cpu].tss_vaddr)); io_mfence(); set_tss64( @@ -116,12 +127,14 @@ void smp_init() cpu_core_info[current_starting_cpu].ist_stack_start); io_mfence(); + // kdebug("core %d, to send start up", current_starting_cpu); // 连续发送两次start-up IPI ipi_send_IPI(DEST_PHYSICAL, IDLE, ICR_LEVEL_DE_ASSERT, EDGE_TRIGGER, 0x20, ICR_Start_up, ICR_No_Shorthand, proc_local_apic_structs[i]->local_apic_id); io_mfence(); ipi_send_IPI(DEST_PHYSICAL, IDLE, ICR_LEVEL_DE_ASSERT, EDGE_TRIGGER, 0x20, ICR_Start_up, ICR_No_Shorthand, proc_local_apic_structs[i]->local_apic_id); + // kdebug("core %d, send start up ok", current_starting_cpu); } io_mfence(); while (num_cpu_started != (core_to_start + 1)) @@ -130,12 +143,7 @@ void smp_init() kinfo("Cleaning page table remapping...\n"); // 由于ap处理器初始化过程需要用到0x00处的地址,因此初始化完毕后才取消内存地址的重映射 - uint64_t *global_CR3 = get_CR3(); - for (int i = 0; i < 256; ++i) - { - io_mfence(); - *(ul *)(phys_2_virt(global_CR3) + i) = 0UL; - } + rs_unmap_at_low_addr(); kdebug("init proc's preempt_count=%ld", current_pcb->preempt_count); kinfo("Successfully cleaned page table remapping!\n"); } @@ -149,8 +157,10 @@ void smp_ap_start() // 切换栈基地址 // uint64_t stack_start = (uint64_t)kmalloc(STACK_SIZE, 0) + STACK_SIZE; - __asm__ __volatile__("movq %0, %%rbp \n\t" ::"m"(cpu_core_info[current_starting_cpu].stack_start) : "memory"); - __asm__ __volatile__("movq %0, %%rsp \n\t" ::"m"(cpu_core_info[current_starting_cpu].stack_start) : "memory"); + __asm__ __volatile__("movq %0, %%rbp \n\t" ::"m"(cpu_core_info[current_starting_cpu].stack_start) + : "memory"); + __asm__ __volatile__("movq %0, %%rsp \n\t" ::"m"(cpu_core_info[current_starting_cpu].stack_start) + : "memory"); ksuccess("AP core %d successfully started!", current_starting_cpu); io_mfence(); @@ -164,7 +174,8 @@ void smp_ap_start() barrier(); current_pcb->state = PROC_RUNNING; current_pcb->flags = PF_KTHREAD; - current_pcb->mm = &initial_mm; + current_pcb->address_space = NULL; + rs_smp_init_idle(); list_init(¤t_pcb->list); current_pcb->addr_limit = KERNEL_BASE_LINEAR_ADDR; @@ -190,8 +201,9 @@ void smp_ap_start() preempt_disable(); // 由于ap处理器的pcb与bsp的不同,因此ap处理器放锁时,需要手动恢复preempt count io_mfence(); current_pcb->flags |= PF_NEED_SCHED; - sti(); + apic_timer_ap_core_init(); + sti(); sched(); while (1) @@ -222,6 +234,12 @@ static void __smp_kick_cpu_handler(uint64_t irq_num, uint64_t param, struct pt_r sched(); } +static void __smp__flush_tlb_ipi_handler(uint64_t irq_num, uint64_t param, struct pt_regs *regs) +{ + if (user_mode(regs)) + return; + flush_tlb(); +} /** * @brief 获取当前全部的cpu数目 diff --git a/kernel/src/syscall/mod.rs b/kernel/src/syscall/mod.rs index 57e3750a..92e75dde 100644 --- a/kernel/src/syscall/mod.rs +++ b/kernel/src/syscall/mod.rs @@ -6,27 +6,30 @@ use core::{ use num_traits::{FromPrimitive, ToPrimitive}; use crate::{ - arch::cpu::cpu_reset, + arch::{cpu::cpu_reset, MMArch}, filesystem::vfs::{ file::FileMode, syscall::{SEEK_CUR, SEEK_END, SEEK_MAX, SEEK_SET}, MAX_PATHLEN, }, - include::bindings::bindings::{mm_stat_t, pid_t, verify_area, PAGE_2M_SIZE, PAGE_4K_SIZE}, + include::bindings::bindings::{pid_t, verify_area, PAGE_2M_SIZE, PAGE_4K_SIZE}, io::SeekFrom, kinfo, + libs::align::page_align_up, + mm::{MemoryManagementArch, VirtAddr}, net::syscall::SockAddr, time::{ - syscall::{PosixTimeZone, PosixTimeval, SYS_TIMEZONE}, + syscall::{PosixTimeZone, PosixTimeval}, TimeSpec, }, }; +pub mod user_access; + #[repr(i32)] #[derive(Debug, FromPrimitive, ToPrimitive, PartialEq, Eq, Clone)] #[allow(dead_code, non_camel_case_types)] pub enum SystemError { - /// 操作不被允许 Operation not permitted. EPERM = 1, /// 没有指定的文件或目录 No such file or directory. ENOENT = 2, @@ -331,9 +334,8 @@ pub const SYS_NANOSLEEP: usize = 18; /// todo: 该系统调用与Linux不一致,将来需要删除该系统调用!!! 删的时候记得改C版本的libc pub const SYS_CLOCK: usize = 19; pub const SYS_PIPE: usize = 20; - -/// todo: 该系统调用不是符合POSIX标准的,在将来需要删除!!! -pub const SYS_MSTAT: usize = 21; +/// 系统调用21曾经是SYS_MSTAT,但是现在已经废弃 +pub const __NOT_USED: usize = 21; pub const SYS_UNLINK_AT: usize = 22; pub const SYS_KILL: usize = 23; pub const SYS_SIGACTION: usize = 24; @@ -358,6 +360,9 @@ pub const SYS_ACCEPT: usize = 40; pub const SYS_GETSOCKNAME: usize = 41; pub const SYS_GETPEERNAME: usize = 42; pub const SYS_GETTIMEOFDAY: usize = 43; +pub const SYS_MMAP: usize = 44; +pub const SYS_MUNMAP: usize = 45; +pub const SYS_MPROTECT: usize = 46; #[derive(Debug)] pub struct Syscall; @@ -473,13 +478,13 @@ impl Syscall { } SYS_BRK => { - let new_brk = args[0]; - Self::brk(new_brk) + let new_brk = VirtAddr::new(args[0]); + Self::brk(new_brk).map(|vaddr| vaddr.data()) } SYS_SBRK => { let increment = args[0] as isize; - Self::sbrk(increment) + Self::sbrk(increment).map(|vaddr| vaddr.data()) } SYS_REBOOT => Self::reboot(), @@ -642,18 +647,6 @@ impl Syscall { } } - SYS_MSTAT => { - let dst = args[0] as *mut mm_stat_t; - if from_user - && unsafe { !verify_area(dst as u64, core::mem::size_of::() as u64) } - { - Err(SystemError::EFAULT) - } else if dst.is_null() { - Err(SystemError::EFAULT) - } else { - Self::mstat(dst, from_user) - } - } SYS_UNLINK_AT => { let dirfd = args[0] as i32; let pathname = args[1] as *const c_char; @@ -873,7 +866,7 @@ impl Syscall { } SYS_GETTIMEOFDAY => { let timeval = args[0] as *mut PosixTimeval; - let timezone_ptr = args[1] as *const PosixTimeZone; + let timezone_ptr = args[1] as *mut PosixTimeZone; let security_check = || { if unsafe { verify_area(timeval as u64, core::mem::size_of::() as u64) @@ -896,18 +889,49 @@ impl Syscall { if r.is_err() { Err(r.unwrap_err()) } else { - let timezone = if !timezone_ptr.is_null() { - &SYS_TIMEZONE - } else { - unsafe { timezone_ptr.as_ref().unwrap() } - }; if !timeval.is_null() { - Self::gettimeofday(timeval, timezone) + Self::gettimeofday(timeval, timezone_ptr) } else { Err(SystemError::EFAULT) } } } + SYS_MMAP => { + let len = page_align_up(args[1]); + if unsafe { !verify_area(args[0] as u64, len as u64) } { + Err(SystemError::EFAULT) + } else { + Self::mmap( + VirtAddr::new(args[0]), + len, + args[2], + args[3], + args[4] as i32, + args[5], + ) + } + } + SYS_MUNMAP => { + let addr = args[0]; + let len = page_align_up(args[1]); + if addr & MMArch::PAGE_SIZE != 0 { + // The addr argument is not a multiple of the page size + Err(SystemError::EINVAL) + } else { + Self::munmap(VirtAddr::new(addr), len) + } + } + SYS_MPROTECT => { + let addr = args[0]; + let len = page_align_up(args[1]); + if addr & MMArch::PAGE_SIZE != 0 { + // The addr argument is not a multiple of the page size + Err(SystemError::EINVAL) + } else { + Self::mprotect(VirtAddr::new(addr), len, args[2]) + } + } + _ => panic!("Unsupported syscall ID: {}", syscall_num), }; diff --git a/kernel/src/syscall/syscall.c b/kernel/src/syscall/syscall.c index 6bd038f7..64121e6e 100644 --- a/kernel/src/syscall/syscall.c +++ b/kernel/src/syscall/syscall.c @@ -73,102 +73,6 @@ ul do_put_string(char *s, uint32_t front_color, uint32_t background_color) return 0; } -/** - * @brief 将堆内存调整为arg0 - * - * @param arg0 新的堆区域的结束地址 - * @return uint64_t 错误码 - * - */ -uint64_t sys_do_brk(uint64_t newaddr) -{ - uint64_t new_brk = PAGE_2M_ALIGN(newaddr); - // kdebug("sys_brk input= %#010lx , new_brk= %#010lx bytes current_pcb->mm->brk_start=%#018lx - // current->end_brk=%#018lx", regs->r8, new_brk, current_pcb->mm->brk_start, current_pcb->mm->brk_end); - struct mm_struct *mm = current_pcb->mm; - if (new_brk < mm->brk_start || new_brk > new_brk >= current_pcb->addr_limit) - return mm->brk_end; - - if (mm->brk_end == new_brk) - return new_brk; - - int64_t offset; - if (new_brk >= current_pcb->mm->brk_end) - offset = (int64_t)(new_brk - current_pcb->mm->brk_end); - else - offset = -(int64_t)(current_pcb->mm->brk_end - new_brk); - - new_brk = mm_do_brk(current_pcb->mm->brk_end, offset); // 扩展堆内存空间 - - current_pcb->mm->brk_end = new_brk; - return mm->brk_end; -} - -/** - * @brief 将堆内存空间加上offset(注意,该系统调用只应在普通进程中调用,而不能是内核线程) - * - * @param incr offset偏移量 - * @return uint64_t the previous program break - */ -uint64_t sys_do_sbrk(int64_t incr) -{ - uint64_t retval = current_pcb->mm->brk_end; - if ((int64_t)incr > 0) - { - - uint64_t new_brk = PAGE_2M_ALIGN(retval + incr); - if (new_brk > current_pcb->addr_limit) // 堆地址空间超过限制 - { - kdebug("exceed mem limit, new_brk = %#018lx", new_brk); - return -ENOMEM; - } - } - else - { - if ((__int128_t)current_pcb->mm->brk_end + (__int128_t)incr < current_pcb->mm->brk_start) - return retval; - } - // kdebug("do brk"); - uint64_t new_brk = mm_do_brk(current_pcb->mm->brk_end, (int64_t)incr); // 调整堆内存空间 - // kdebug("do brk done, new_brk = %#018lx", new_brk); - current_pcb->mm->brk_end = new_brk; - return retval; -} - -/** - * @brief 执行新的程序 - * - * @param user_path(r8寄存器) 文件路径 - * @param argv(r9寄存器) 参数列表 - * @return uint64_t - */ -uint64_t c_sys_execve(char *user_path, char **argv, char **envp, struct pt_regs *regs) -{ - - int path_len = strnlen_user(user_path, PAGE_4K_SIZE); - - if (path_len >= PAGE_4K_SIZE) - return -ENAMETOOLONG; - else if (path_len <= 0) - return -EFAULT; - - char *path = (char *)kmalloc(path_len + 1, 0); - if (path == NULL) - return -ENOMEM; - - memset(path, 0, path_len + 1); - - // 拷贝文件路径 - strncpy_from_user(path, user_path, path_len); - path[path_len] = '\0'; - - // 执行新的程序 - uint64_t retval = do_execve(regs, path, argv, NULL); - - kfree(path); - return retval; -} - /** * @brief 等待进程退出 * diff --git a/kernel/src/syscall/syscall_num.h b/kernel/src/syscall/syscall_num.h index c076f461..809ab2de 100644 --- a/kernel/src/syscall/syscall_num.h +++ b/kernel/src/syscall/syscall_num.h @@ -30,7 +30,6 @@ #define SYS_CLOCK 19 // 获取当前cpu时间 #define SYS_PIPE 20 // 创建管道 -#define SYS_MSTAT 21 // 获取系统的内存状态信息 #define SYS_UNLINK_AT 22 // 删除文件夹/删除文件链接 #define SYS_KILL 23 // kill一个进程(向这个进程发出信号) #define SYS_SIGACTION 24 // 设置进程的信号处理动作 @@ -41,18 +40,20 @@ #define SYS_DUP2 29 #define SYS_SOCKET 30 // 创建一个socket -#define SYS_SETSOCKOPT 31 // 设置socket的选项 -#define SYS_GETSOCKOPT 32 // 获取socket的选项 -#define SYS_CONNECT 33 // 连接到一个socket -#define SYS_BIND 34 // 绑定一个socket -#define SYS_SENDTO 35 // 向一个socket发送数据 -#define SYS_RECVFROM 36 // 从一个socket接收数据 -#define SYS_RECVMSG 37 // 从一个socket接收消息 -#define SYS_LISTEN 38 // 监听一个socket -#define SYS_SHUTDOWN 39 // 关闭socket -#define SYS_ACCEPT 40 // 接受一个socket连接 -#define SYS_GETSOCKNAME 41 // 获取socket的名字 -#define SYS_GETPEERNAME 42 // 获取socket的对端名字 -#define SYS_GETTIMEOFDAY 43 // 获取当前时间 +#define SYS_SETSOCKOPT 31 // 设置socket的选项 +#define SYS_GETSOCKOPT 32 // 获取socket的选项 +#define SYS_CONNECT 33 // 连接到一个socket +#define SYS_BIND 34 // 绑定一个socket +#define SYS_SENDTO 35 // 向一个socket发送数据 +#define SYS_RECVFROM 36 // 从一个socket接收数据 +#define SYS_RECVMSG 37 // 从一个socket接收消息 +#define SYS_LISTEN 38 // 监听一个socket +#define SYS_SHUTDOWN 39 // 关闭socket +#define SYS_ACCEPT 40 // 接受一个socket连接 -#define SYS_AHCI_END_REQ 255 // AHCI DMA请求结束end_request的系统调用 \ No newline at end of file +#define SYS_GETSOCKNAME 41 // 获取socket的名字 +#define SYS_GETPEERNAME 42 // 获取socket的对端名字 +#define SYS_GETTIMEOFDAY 43 // 获取当前时间 +#define SYS_MMAP 44 // 内存映射 +#define SYS_MUNMAP 45 // 内存解除映射 +#define SYS_MPROTECT 46 // 内存保护 diff --git a/kernel/src/syscall/user_access.rs b/kernel/src/syscall/user_access.rs new file mode 100644 index 00000000..1078e417 --- /dev/null +++ b/kernel/src/syscall/user_access.rs @@ -0,0 +1,141 @@ +//! 这个文件用于放置一些内核态访问用户态数据的函数 +use core::mem::size_of; + +use alloc::{string::String, vec::Vec}; + +use crate::mm::{verify_area, VirtAddr}; + +use super::SystemError; + +/// 清空用户空间指定范围内的数据 +/// +/// ## 参数 +/// +/// - `dest`:用户空间的目标地址 +/// - `len`:要清空的数据长度 +/// +/// ## 返回值 +/// +/// 返回清空的数据长度 +/// +/// ## 错误 +/// +/// - `EFAULT`:目标地址不合法 +pub unsafe fn clear_user(dest: VirtAddr, len: usize) -> Result { + verify_area(dest, len).map_err(|_| SystemError::EFAULT)?; + + let p = dest.data() as *mut u8; + // 清空用户空间的数据 + p.write_bytes(0, len); + return Ok(len); +} + +pub unsafe fn copy_to_user(dest: VirtAddr, src: &[u8]) -> Result { + verify_area(dest, src.len()).map_err(|_| SystemError::EFAULT)?; + + let p = dest.data() as *mut u8; + // 拷贝数据 + p.copy_from_nonoverlapping(src.as_ptr(), src.len()); + return Ok(src.len()); +} + +/// 从用户空间拷贝数据到内核空间 +pub unsafe fn copy_from_user(dst: &mut [u8], src: VirtAddr) -> Result { + verify_area(src, dst.len()).map_err(|_| SystemError::EFAULT)?; + + let src: &[u8] = core::slice::from_raw_parts(src.data() as *const u8, dst.len()); + // 拷贝数据 + dst.copy_from_slice(&src); + + return Ok(dst.len()); +} + +/// 检查并从用户态拷贝一个 C 字符串。 +/// +/// 一旦遇到非法地址,就会返回错误 +/// +/// ## 参数 +/// +/// - `user`:用户态的 C 字符串指针 +/// - `max_length`:最大拷贝长度 +/// +/// ## 返回值 +/// +/// 返回拷贝的 C 字符串 +/// +/// ## 错误 +/// +/// - `EFAULT`:用户态地址不合法 +pub fn check_and_clone_cstr( + user: *const u8, + max_length: Option, +) -> Result { + if user.is_null() { + return Ok(String::new()); + } + + // 从用户态读取,直到遇到空字符 '\0' 或者达到最大长度 + let mut buffer = Vec::new(); + for i in 0.. { + if max_length.is_some() && max_length.as_ref().unwrap() <= &i { + break; + } + + let addr = unsafe { user.add(i) }; + let mut c = [0u8; 1]; + unsafe { + copy_from_user(&mut c, VirtAddr::new(addr as usize))?; + } + if c[0] == 0 { + break; + } + buffer.push(c[0]); + } + return Ok(String::from_utf8(buffer).map_err(|_| SystemError::EFAULT)?); +} + +/// 检查并从用户态拷贝一个 C 字符串数组 +/// +/// 一旦遇到空指针,就会停止拷贝. 一旦遇到非法地址,就会返回错误 +/// ## 参数 +/// +/// - `user`:用户态的 C 字符串指针数组 +/// +/// ## 返回值 +/// +/// 返回拷贝的 C 字符串数组 +/// +/// ## 错误 +/// +/// - `EFAULT`:用户态地址不合法 +pub fn check_and_clone_cstr_array(user: *const *const u8) -> Result, SystemError> { + if user.is_null() { + Ok(Vec::new()) + } else { + // kdebug!("check_and_clone_cstr_array: {:p}\n", user); + let mut buffer = Vec::new(); + for i in 0.. { + let addr = unsafe { user.add(i) }; + let str_ptr: *const u8; + // 读取这个地址的值(这个值也是一个指针) + unsafe { + let dst = [0usize; 1]; + let mut dst = core::mem::transmute::<[usize; 1], [u8; size_of::()]>(dst); + copy_from_user(&mut dst, VirtAddr::new(addr as usize))?; + let dst = core::mem::transmute::<[u8; size_of::()], [usize; 1]>(dst); + str_ptr = dst[0] as *const u8; + + // kdebug!("str_ptr: {:p}, addr:{addr:?}\n", str_ptr); + } + + if str_ptr.is_null() { + break; + } + // 读取这个指针指向的字符串 + let string = check_and_clone_cstr(str_ptr, None)?; + // 将字符串放入 buffer 中 + buffer.push(string); + } + return Ok(buffer); + } +} diff --git a/kernel/src/time/clocksource.rs b/kernel/src/time/clocksource.rs index 7a1a470f..917b68c2 100644 --- a/kernel/src/time/clocksource.rs +++ b/kernel/src/time/clocksource.rs @@ -625,7 +625,7 @@ pub fn clocksource_resume() { match ele.resume() { Ok(_) => continue, Err(_) => { - kdebug!("clocksource {:?} resume failed", data.name) + kdebug!("clocksource {:?} resume failed", data.name); } } } @@ -641,7 +641,7 @@ pub fn clocksource_suspend() { match ele.suspend() { Ok(_) => continue, Err(_) => { - kdebug!("clocksource {:?} suspend failed", data.name) + kdebug!("clocksource {:?} suspend failed", data.name); } } } diff --git a/kernel/src/time/jiffies.rs b/kernel/src/time/jiffies.rs index 57cea867..3ec9da29 100644 --- a/kernel/src/time/jiffies.rs +++ b/kernel/src/time/jiffies.rs @@ -3,7 +3,7 @@ use alloc::{ sync::{Arc, Weak}, }; -use crate::{kdebug, libs::spinlock::SpinLock, syscall::SystemError}; +use crate::{kerror, kinfo, libs::spinlock::SpinLock, syscall::SystemError}; use super::{ clocksource::{Clocksource, ClocksourceData, ClocksourceFlags, ClocksourceMask, CycleNum, HZ}, @@ -89,9 +89,13 @@ pub fn jiffies_init() { //注册jiffies let jiffies = clocksource_default_clock() as Arc; match jiffies.register() { - Ok(_) => kdebug!("jiffies_init sccessfully"), - Err(_) => kdebug!("jiffies_init failed, no default clock running"), - } + Ok(_) => { + kinfo!("jiffies_init sccessfully"); + } + Err(_) => { + kerror!("jiffies_init failed, no default clock running"); + } + }; } #[no_mangle] diff --git a/kernel/src/time/syscall.rs b/kernel/src/time/syscall.rs index 59372bd5..15cfa014 100644 --- a/kernel/src/time/syscall.rs +++ b/kernel/src/time/syscall.rs @@ -4,7 +4,6 @@ use core::{ }; use crate::{ - kdebug, syscall::{Syscall, SystemError}, time::{sleep::nanosleep, TimeSpec}, }; @@ -79,10 +78,10 @@ impl Syscall { pub fn gettimeofday( tv: *mut PosixTimeval, - _timezone: &PosixTimeZone, + timezone: *mut PosixTimeZone, ) -> Result { // TODO; 处理时区信息 - kdebug!("enter sys_do_gettimeofday"); + // kdebug!("enter sys_do_gettimeofday"); if tv == null_mut() { return Err(SystemError::EFAULT); } @@ -91,7 +90,13 @@ impl Syscall { (*tv).tv_sec = posix_time.tv_sec; (*tv).tv_usec = posix_time.tv_usec; } - kdebug!("exit sys_do_gettimeofday"); + + if !timezone.is_null() { + unsafe { + *timezone = SYS_TIMEZONE; + } + } + // kdebug!("exit sys_do_gettimeofday"); return Ok(0); } } diff --git a/kernel/src/time/timekeeping.rs b/kernel/src/time/timekeeping.rs index 966faa64..8b714819 100644 --- a/kernel/src/time/timekeeping.rs +++ b/kernel/src/time/timekeeping.rs @@ -5,7 +5,7 @@ use x86_64::align_up; use crate::{ arch::CurrentIrqArch, exception::InterruptArch, - kdebug, + kdebug, kinfo, libs::rwlock::RwLock, time::{jiffies::clocksource_default_clock, timekeep::ktime_get_real_ns, TimeSpec}, }; @@ -118,6 +118,7 @@ impl Timekeeper { timekeeper.cycle_interval = CycleNum(temp); timekeeper.xtime_interval = temp * clock_data.mult as u64; + // 这里可能存在下界溢出问题,debug模式下会报错panic timekeeper.xtime_remainder = (ntpinterval - timekeeper.xtime_interval) as i64; timekeeper.raw_interval = (timekeeper.xtime_interval >> clock_data.shift) as i64; timekeeper.xtime_nsec = 0; @@ -154,7 +155,7 @@ pub fn timekeeper_init() { /// /// * 'TimeSpec' - 时间戳 pub fn getnstimeofday() -> TimeSpec { - kdebug!("enter getnstimeofday"); + // kdebug!("enter getnstimeofday"); // let mut nsecs: u64 = 0;0 let mut _xtime = TimeSpec { @@ -201,6 +202,7 @@ pub fn do_gettimeofday() -> PosixTimeval { /// # 初始化timekeeping模块 pub fn timekeeping_init() { + kinfo!("Initializing timekeeping module..."); let irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() }; timekeeper_init(); @@ -229,7 +231,7 @@ pub fn timekeeping_init() { __ADDED_SEC.store(0, Ordering::SeqCst); drop(irq_guard); - kdebug!("timekeeping_init successfully"); + kinfo!("timekeeping_init successfully"); } /// # 使用当前时钟源增加wall time diff --git a/kernel/src/time/timer.h b/kernel/src/time/timer.h index a690149d..c27f3f86 100644 --- a/kernel/src/time/timer.h +++ b/kernel/src/time/timer.h @@ -12,4 +12,5 @@ extern int64_t rs_timer_get_first_expire(); extern uint64_t rs_timer_next_n_ms_jiffies(uint64_t expire_ms); extern int64_t rs_schedule_timeout(int64_t timeout); -extern uint64_t rs_clock(); \ No newline at end of file +extern uint64_t rs_clock(); +extern void rs_jiffies_init(); diff --git a/kernel/src/time/timer.rs b/kernel/src/time/timer.rs index 47ee96a2..b831adc2 100644 --- a/kernel/src/time/timer.rs +++ b/kernel/src/time/timer.rs @@ -17,7 +17,7 @@ use crate::{ InterruptArch, }, include::bindings::bindings::{process_control_block, process_wakeup, PROC_RUNNING}, - kdebug, kerror, + kdebug, kerror, kinfo, libs::spinlock::SpinLock, syscall::SystemError, }; @@ -105,7 +105,6 @@ impl Timer { break; } } - let mut temp_list: LinkedList> = timer_list.split_off(split_pos); timer_list.push_back(inner_guard.self_ref.upgrade().unwrap()); timer_list.append(&mut temp_list); @@ -216,7 +215,7 @@ pub fn timer_init() { softirq_vectors() .register_softirq(SoftirqNumber::TIMER, do_timer_softirq) .expect("Failed to register timer softirq"); - kdebug!("timer initiated successfully"); + kinfo!("timer initialized successfully"); } /// 计算接下来n毫秒对应的定时器时间片 diff --git a/user/apps/about/about.c b/user/apps/about/about.c index 7bc5d35b..b049a784 100644 --- a/user/apps/about/about.c +++ b/user/apps/about/about.c @@ -32,6 +32,9 @@ void print_copyright() printf(" If you find any problems during use, please visit:\n"); put_string(" https://bbs.DragonOS.org\n", COLOR_ORANGE, COLOR_BLACK); printf("\n"); + printf(" Join our development community:\n"); + put_string(" https://DragonOS.zulipchat.com\n", COLOR_ORANGE, COLOR_BLACK); + printf("\n"); } int main() diff --git a/user/apps/shell/cmd.c b/user/apps/shell/cmd.c index 64cacb8d..87b06049 100644 --- a/user/apps/shell/cmd.c +++ b/user/apps/shell/cmd.c @@ -21,10 +21,21 @@ char *shell_current_path = NULL; * */ struct built_in_cmd_t shell_cmds[] = { - {"cd", shell_cmd_cd}, {"cat", shell_cmd_cat}, {"exec", shell_cmd_exec}, {"ls", shell_cmd_ls}, - {"mkdir", shell_cmd_mkdir}, {"pwd", shell_cmd_pwd}, {"rm", shell_cmd_rm}, {"rmdir", shell_cmd_rmdir}, - {"reboot", shell_cmd_reboot}, {"touch", shell_cmd_touch}, {"about", shell_cmd_about}, {"free", shell_cmd_free}, - {"help", shell_help}, {"pipe", shell_pipe_test}, {"kill", shell_cmd_kill}, + {"cd", shell_cmd_cd}, + {"cat", shell_cmd_cat}, + {"exec", shell_cmd_exec}, + {"ls", shell_cmd_ls}, + {"mkdir", shell_cmd_mkdir}, + {"pwd", shell_cmd_pwd}, + {"rm", shell_cmd_rm}, + {"rmdir", shell_cmd_rmdir}, + {"reboot", shell_cmd_reboot}, + {"touch", shell_cmd_touch}, + {"about", shell_cmd_about}, + {"free", shell_cmd_free}, + {"help", shell_help}, + {"pipe", shell_pipe_test}, + {"kill", shell_cmd_kill}, }; // 总共的内建命令数量 @@ -610,7 +621,7 @@ int parse_command(char *buf, int *argc, char ***argv) while (index < INPUT_BUFFER_SIZE && buf[index] == ' ') ++index; // 如果去除前导空格后第一项为0x00,则归为空命令 - if(!buf[index]) + if (!buf[index]) return -1; // 计算参数数量 @@ -622,12 +633,12 @@ int parse_command(char *buf, int *argc, char ***argv) if (buf[i] != ' ' && (buf[i + 1] == ' ' || buf[i + 1] == '\0')) ++(*argc); } - + // printf("\nargc=%d\n", *argc); // 为指向每个指令的指针分配空间 - *argv = (char **)malloc(sizeof(char **) * (*argc)); - memset(*argv, 0, sizeof(char **) * (*argc)); + *argv = (char **)malloc(sizeof(char **) * (*argc + 1)); + memset(*argv, 0, sizeof(char **) * (*argc + 1)); // 将每个命令都单独提取出来 for (int i = 0; i < *argc && index < INPUT_BUFFER_SIZE; ++i) { diff --git a/user/apps/test_relibc/Makefile b/user/apps/test_relibc/Makefile index 02e62ad6..064ea638 100644 --- a/user/apps/test_relibc/Makefile +++ b/user/apps/test_relibc/Makefile @@ -21,4 +21,4 @@ main.o: main.c $(CC) $(CFLAGS) -c main.c -o main.o clean: - rm -f *.o \ No newline at end of file + rm -f *.o diff --git a/user/libs/libc/src/malloc.c b/user/libs/libc/src/malloc.c index b0d05a9c..d1c02ade 100644 --- a/user/libs/libc/src/malloc.c +++ b/user/libs/libc/src/malloc.c @@ -156,6 +156,7 @@ static int malloc_enlarge(int64_t size) // 在新分配的内存的底部放置header // printf("managed addr = %#018lx\n", brk_managed_addr); malloc_mem_chunk_t *new_ck = (malloc_mem_chunk_t *)brk_managed_addr; + memset(new_ck, 0, sizeof(malloc_mem_chunk_t)); new_ck->length = brk_max_addr - brk_managed_addr; // printf("new_ck->start_addr=%#018lx\tbrk_max_addr=%#018lx\tbrk_managed_addr=%#018lx\n", (uint64_t)new_ck, brk_max_addr, brk_managed_addr); new_ck->prev = NULL; diff --git a/user/libs/libc/src/unistd.c b/user/libs/libc/src/unistd.c index 2c969172..8c79fde1 100644 --- a/user/libs/libc/src/unistd.c +++ b/user/libs/libc/src/unistd.c @@ -70,7 +70,8 @@ pid_t fork(void) * * @return int 如果失败返回负数 */ -int pipe(int fd[2]){ +int pipe(int fd[2]) +{ return (int)syscall_invoke(SYS_PIPE, fd, 0, 0, 0, 0, 0, 0, 0); } /** @@ -95,7 +96,8 @@ pid_t vfork(void) uint64_t brk(uint64_t end_brk) { uint64_t x = (uint64_t)syscall_invoke(SYS_BRK, (uint64_t)end_brk, 0, 0, 0, 0, 0, 0, 0); - if (x < end_brk){ + if (x < end_brk) + { errno = -ENOMEM; return -1; } @@ -217,7 +219,7 @@ pid_t getpid(void) int dup(int fd) { - return syscall_invoke(SYS_DUP, fd, 0, 0, 0, 0, 0, 0, 0); + return syscall_invoke(SYS_DUP, fd, 0, 0, 0, 0, 0, 0, 0); } int dup2(int ofd, int nfd)