diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..82d2068b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,12 @@ +{ + "rust-analyzer.server.path": "rust-analyzer", + "rust-analyzer.linkedProjects": [ + "os/Cargo.toml", + "user/init/Cargo.toml", + "user/hello/Cargo.toml" + ], + "rust-analyzer.cargo.allTargets": false, + "rust-analyzer.check.allTargets": false, + "rust-analyzer.cargo.targetDir": "target/rust-analyzer", + "rust-analyzer.diagnostics.enable": false +} diff --git a/Makefile b/Makefile index 6932bb5e..52c8ac4f 100644 --- a/Makefile +++ b/Makefile @@ -36,8 +36,23 @@ else PROFILE_DIR := release endif +# 评测内核默认启用 oscomp 特性:启动时探测双盘、把测试镜像挂到 /tests, +# 由 rootfs 的 rcS 自动跑测试并主动关机(赛题要求“自动运行 + 自动关闭”)。 +# 本地交互式开发请用 `make run`(os/Makefile,不带 oscomp)。 +OSCOMP_FEATURE ?= --features oscomp + +# 本地复现评测用 QEMU 参数(可在命令行覆盖,如 `make run-oscomp-rv OSCOMP_RV_MEM=2G`)。 +# 测试镜像默认取仓库根的 sdcard-{rv,la}.img;rootfs 用 make all 产出的 disk{,-la}.img。 +TESTIMG_RV ?= sdcard-rv.img +TESTIMG_LA ?= sdcard-la.img +OSCOMP_RV_MEM ?= 4G +OSCOMP_RV_SMP ?= 1 +OSCOMP_LA_MEM ?= 4G +OSCOMP_LA_SMP ?= 1 + .PHONY: docker build_docker fmt run build clean clean-all gdb .PHONY: all kernel-rv kernel-la os-cargo-config +.PHONY: run-oscomp-rv run-oscomp-la prepare-testimg-rv prepare-testimg-la docker: docker run --rm -it -v ${PWD}:/mnt -w /mnt --name comix ${DOCKER_TAG} bash @@ -96,12 +111,12 @@ all: os-cargo-config kernel-rv kernel-la disk.img disk-la.img kernel-rv: os-cargo-config @echo "[OSCOMP] 构建 RISC-V 内核 (ELF, $(PROFILE_DIR)): kernel-rv" - cd $(OS_DIR) && ARCH=riscv cargo build $(CARGO_PROFILE_FLAG) --target $(RV_TARGET) + cd $(OS_DIR) && ARCH=riscv cargo build $(CARGO_PROFILE_FLAG) --target $(RV_TARGET) $(OSCOMP_FEATURE) cp -f $(OS_DIR)/target/$(RV_TARGET)/$(PROFILE_DIR)/$(OS_BIN) kernel-rv kernel-la: os-cargo-config @echo "[OSCOMP] 构建 LoongArch 内核 (ELF, $(PROFILE_DIR)): kernel-la" - cd $(OS_DIR) && ARCH=loongarch cargo build $(CARGO_PROFILE_FLAG) --target $(LA_TARGET) + cd $(OS_DIR) && ARCH=loongarch cargo build $(CARGO_PROFILE_FLAG) --target $(LA_TARGET) $(OSCOMP_FEATURE) cp -f $(OS_DIR)/target/$(LA_TARGET)/$(PROFILE_DIR)/$(OS_BIN) kernel-la # rootfs 镜像由 os/build.rs 在对应内核构建时生成(os/fs-{arch}.img),此处拷到根目录。 @@ -115,6 +130,46 @@ disk-la.img: kernel-la @test -f $(OS_DIR)/fs-loongarch.img cp -f $(OS_DIR)/fs-loongarch.img disk-la.img +# ------------------------------------------------------------ +# 本地复现评测:启动 QEMU,挂测试镜像(x0) + 我们的 rootfs(x1), +# 内核自动跑测试并主动关机;-no-reboot 让关机时 QEMU 退出。 +# 设备型号对齐 os/qemu-run.sh(riscv: virtio-mmio)与 os/qemu-loongarch-run.sh(loongarch: pci)。 +# ------------------------------------------------------------ +prepare-testimg-rv: + @test -f "$(TESTIMG_RV)" || ( \ + echo "[OSCOMP] 缺少 RISC-V 测试镜像: $(TESTIMG_RV)"; \ + echo "[OSCOMP] 请把官方测试镜像放到该路径,或用 TESTIMG_RV=... 覆盖。"; \ + exit 1; ) + @echo "[OSCOMP] 使用 RISC-V 测试镜像: $(TESTIMG_RV)" + +prepare-testimg-la: + @test -f "$(TESTIMG_LA)" || ( \ + echo "[OSCOMP] 缺少 LoongArch 测试镜像: $(TESTIMG_LA)"; \ + echo "[OSCOMP] 请把官方测试镜像放到该路径,或用 TESTIMG_LA=... 覆盖。"; \ + exit 1; ) + @echo "[OSCOMP] 使用 LoongArch 测试镜像: $(TESTIMG_LA)" + +run-oscomp-rv: kernel-rv disk.img prepare-testimg-rv + @echo "[OSCOMP] 运行 RISC-V QEMU(测试镜像 + rootfs,自动跑测试并关机)" + qemu-system-riscv64 -machine virt -kernel kernel-rv -m $(OSCOMP_RV_MEM) -nographic \ + -smp $(OSCOMP_RV_SMP) -bios default -no-reboot -rtc base=utc \ + -drive file=$(TESTIMG_RV),if=none,format=raw,id=x0 \ + -device virtio-blk-device,drive=x0,bus=virtio-mmio-bus.0 \ + -drive file=disk.img,if=none,format=raw,id=x1 \ + -device virtio-blk-device,drive=x1,bus=virtio-mmio-bus.1 \ + -device virtio-net-device,netdev=net -netdev user,id=net + +run-oscomp-la: kernel-la disk-la.img prepare-testimg-la + @echo "[OSCOMP] 运行 LoongArch QEMU(测试镜像 + rootfs,自动跑测试并关机)" + qemu-system-loongarch64 -machine virt -kernel kernel-la -m $(OSCOMP_LA_MEM) -nographic \ + -smp $(OSCOMP_LA_SMP) -no-reboot -rtc base=utc \ + -drive file=$(TESTIMG_LA),if=none,format=raw,id=x0 \ + -device virtio-blk-pci,drive=x0 \ + -drive file=disk-la.img,if=none,format=raw,id=x1 \ + -device virtio-blk-pci,drive=x1 \ + -device virtio-net-pci,netdev=net0 \ + -netdev user,id=net0,hostfwd=tcp::5555-:5555,hostfwd=udp::5555-:5555 + # 清理 OS 构建产物 clean: cd os && cargo clean diff --git a/data/loongarch_musl/etc/init.d/rcS b/data/loongarch_musl/etc/init.d/rcS index cde3274b..b2dc878a 100755 --- a/data/loongarch_musl/etc/init.d/rcS +++ b/data/loongarch_musl/etc/init.d/rcS @@ -51,5 +51,82 @@ echo "Mounting file systems via fstab..." echo "System initialization finished." +run_oscomp_tests_if_present() { + # 评测测试镜像由内核挂到 /tests(init_oscomp_filesystems())。 + # 检测到官方测试脚本就非交互地依次执行,跑完主动关机。 + [ -d /tests ] || return 1 + + # 官方测试二进制硬编码了 ELF 解释器路径(如 /lib64/ld-linux-*.so.*)。 + # 我们是 musl rootfs,把测试镜像里的 glibc loader/库软链到常见 ABI libdir 以便加载。 + if [ -d /tests/glibc/lib ]; then + [ -d /lib ] || /bin/mkdir -p /lib + [ -d /lib64 ] || /bin/mkdir -p /lib64 + for f in /tests/glibc/lib/ld-linux-*.so.*; do + [ -f "$f" ] || continue + base="${f##*/}" + /bin/ln -sf "$f" "/lib/$base" + /bin/ln -sf "$f" "/lib64/$base" + done + for base in libc.so.6 libm.so.6 libpthread.so.0 libdl.so.2 librt.so.1 libresolv.so.2; do + if [ -f "/tests/glibc/lib/$base" ]; then + /bin/ln -sf "/tests/glibc/lib/$base" "/lib/$base" + /bin/ln -sf "/tests/glibc/lib/$base" "/lib64/$base" + fi + done + fi + + scripts="" + add_script() { + # 简单去重,避免 VFS 把 /tests 暴露成 /tests/tests 之类的病态情况。 + case " $scripts " in + *" $1 "*) return 0 ;; + esac + scripts="$scripts $1" + } + + # 1) /tests 根下的脚本。 + for f in /tests/*_testcode.sh; do + [ -f "$f" ] || continue + add_script "$f" + done + + # 2) 下一层:/tests/*/*,匹配官方镜像布局 /tests/glibc/*.sh、/tests/musl/*.sh。 + for d in /tests/*; do + [ -d "$d" ] || continue + [ "$d" = "/tests/tests" ] && continue + for f in "$d"/*_testcode.sh; do + [ -f "$f" ] || continue + add_script "$f" + done + done + [ -n "$scripts" ] || return 1 + + echo "[OSCOMP] detected test scripts under /tests; running in rcS" + export PATH="/bin:/sbin:/usr/bin:/usr/sbin:/tests" + export HOME="/" + + for f in $scripts; do + dir="${f%/*}" + base="${f##*/}" + echo "[OSCOMP] running $f" + ( + cd "$dir" || exit 1 + # 用 "." 执行避免依赖内核 shebang/ENOEXEC 行为;重定向 stdin 避免阻塞。 + . "./$base" Result<(), crate::vfs::FsError> { Ok(()) } +// ============================================================ +// OSCOMP 评测模式文件系统初始化(仅 `oscomp` feature 下编译) +// ============================================================ +// 评测机用 QEMU 同时挂两块盘: +// - 评测测试镜像(含 *_testcode.sh) +// - 我们的 rootfs(disk.img / disk-la.img,含 /bin/sh) +// virtio-blk 探测顺序在不同 QEMU 配置下不稳定,故按内容判别: +// - rootfs 认 `/bin/sh`(或 `/bin/ash`) +// - testfs 认(根或下一层)`*_testcode.sh`,挂到 /tests +// 测试发现/执行/关机由 rootfs 的 rcS 负责(见 data/*/etc/init.d/rcS)。 + +/// 确保 rootfs 上存在某个顶层挂载点目录(如 /dev、/tests)。 +#[cfg(feature = "oscomp")] +fn ensure_top_level_dir(path: &str, mode: FileMode) -> Result<(), FsError> { + if vfs_lookup(path).is_ok() { + return Ok(()); + } + let name = path.strip_prefix('/').ok_or(FsError::InvalidArgument)?; + if name.is_empty() || name.contains('/') { + return Err(FsError::InvalidArgument); + } + let root = crate::vfs::get_root_dentry()?; + root.inode.mkdir(name, mode)?; + Ok(()) +} + +/// 挂载新的根文件系统后,把当前任务的 root/cwd 同步到新的 VFS 根。 +#[cfg(feature = "oscomp")] +fn set_current_task_root_cwd_to_vfs_root() -> Result<(), FsError> { + let root = crate::vfs::get_root_dentry()?; + let task = crate::kernel::current_task(); + let task_guard = task.lock(); + let mut fs = task_guard.fs.lock(); + fs.root = Some(root.clone()); + fs.cwd = Some(root); + Ok(()) +} + +#[cfg(feature = "oscomp")] +fn clear_current_task_root_cwd() { + let task = crate::kernel::current_task(); + let task_guard = task.lock(); + let mut fs = task_guard.fs.lock(); + fs.root = None; + fs.cwd = None; +} + +/// OSCOMP 评测模式:探测并挂载 rootfs(/)与评测测试镜像(/tests)。 +#[cfg(feature = "oscomp")] +pub fn init_oscomp_filesystems() -> Result<(), FsError> { + use crate::config::EXT4_BLOCK_SIZE; + + pr_info!("[OSCOMP][Ext4] Initializing filesystems (rootfs + testfs)"); + + let blk_list = BLK_DRIVERS.read(); + if blk_list.is_empty() { + pr_info!("[OSCOMP][Ext4] No block device found"); + return Err(FsError::NoDevice); + } + let devices: alloc::vec::Vec<_> = blk_list.iter().cloned().collect(); + drop(blk_list); + + // 1) 探测并挂载 rootfs 到 "/"(认 /bin/sh 或 /bin/ash)。 + let mut root_idx: Option = None; + for (idx, dev) in devices.iter().enumerate() { + let bytes = dev.total_blocks().saturating_mul(dev.block_size()); + let total_blocks = bytes / EXT4_BLOCK_SIZE; + let Ok(fs) = Ext4FileSystem::open(dev.clone(), EXT4_BLOCK_SIZE, total_blocks, idx) else { + continue; + }; + MOUNT_TABLE.mount( + fs, + "/", + MountFlags::empty(), + Some(alloc::format!("virtio-blk{}", idx)), + )?; + set_current_task_root_cwd_to_vfs_root()?; + if vfs_lookup("/bin/sh").is_ok() || vfs_lookup("/bin/ash").is_ok() { + pr_info!( + "[OSCOMP][Ext4] Selected rootfs: virtio-blk{} (device_bytes={} MB)", + idx, + bytes / 1024 / 1024 + ); + root_idx = Some(idx); + break; + } else { + let _ = MOUNT_TABLE.umount_root_probe(); + clear_current_task_root_cwd(); + } + } + let root_idx = root_idx.ok_or(FsError::NoDevice)?; + + // 确保 rootfs 上存在常用挂载点。 + let dir_mode = FileMode::S_IFDIR | FileMode::from_bits_truncate(0o755); + let _ = ensure_top_level_dir("/dev", dir_mode); + let _ = ensure_top_level_dir("/proc", dir_mode); + let _ = ensure_top_level_dir("/sys", dir_mode); + let _ = ensure_top_level_dir("/tmp", dir_mode); + let _ = ensure_top_level_dir("/tests", dir_mode); + + // 检测某挂载点(根或下一层)是否含 *_testcode.sh。 + fn testsuite_has_scripts(mount_root: &str) -> bool { + let Ok(root) = vfs_lookup(mount_root) else { + return false; + }; + let Ok(ents) = root.inode.readdir() else { + return false; + }; + if ents.iter().any(|e| e.name.ends_with("_testcode.sh")) { + return true; + } + for e in ents { + if e.inode_type != crate::vfs::InodeType::Directory { + continue; + } + if e.name == "." || e.name == ".." { + continue; + } + let sub = alloc::format!("{}/{}", mount_root.trim_end_matches('/'), e.name); + let Ok(d) = vfs_lookup(&sub) else { + continue; + }; + let Ok(sub_ents) = d.inode.readdir() else { + continue; + }; + if sub_ents.iter().any(|se| se.name.ends_with("_testcode.sh")) { + return true; + } + } + false + } + + // 2) 探测并挂载 testfs 到 "/tests"(认 *_testcode.sh)。 + let mut test_found = false; + for (idx, dev) in devices.iter().enumerate() { + if idx == root_idx { + continue; + } + let bytes = dev.total_blocks().saturating_mul(dev.block_size()); + let total_blocks = bytes / EXT4_BLOCK_SIZE; + let Ok(fs) = Ext4FileSystem::open(dev.clone(), EXT4_BLOCK_SIZE, total_blocks, idx) else { + continue; + }; + MOUNT_TABLE.mount( + fs, + "/tests", + MountFlags::empty(), + Some(alloc::format!("virtio-blk{}", idx)), + )?; + if testsuite_has_scripts("/tests") { + pr_info!( + "[OSCOMP][Ext4] Selected testfs: virtio-blk{} (device_bytes={} MB)", + idx, + bytes / 1024 / 1024 + ); + test_found = true; + break; + } else { + let _ = MOUNT_TABLE.umount("/tests"); + } + } + if !test_found { + pr_info!("[OSCOMP][Ext4] Testfs not found; rcS will scan / for test scripts"); + } + + Ok(()) +} + /// 挂载 tmpfs 到指定路径 /// /// # 参数 diff --git a/os/src/kernel/boot.rs b/os/src/kernel/boot.rs index 0d3d8828..655d7e9a 100644 --- a/os/src/kernel/boot.rs +++ b/os/src/kernel/boot.rs @@ -203,12 +203,28 @@ pub fn rest_init() { fn init() { create_kthreadd(); - if let Err(e) = crate::fs::init_ext4_from_block_device() { - pr_err!( - "[Init] Warning: Failed to initialize Ext4 filesystem: {:?}", - e - ); - pr_info!("[Init] Continuing without filesystem..."); + // OSCOMP 评测模式:探测双盘(rootfs + 测试镜像),把测试镜像挂到 /tests。 + // 测试发现/执行/关机交给 rootfs 的 rcS(见 data/*/etc/init.d/rcS)。 + #[cfg(feature = "oscomp")] + { + if let Err(e) = crate::fs::init_oscomp_filesystems() { + pr_err!( + "[Init][OSCOMP] Warning: Failed to initialize filesystems: {:?}", + e + ); + pr_info!("[Init][OSCOMP] Continuing without filesystem..."); + } + } + // 非评测模式:保持原有单盘启动(本地交互式开发)。 + #[cfg(not(feature = "oscomp"))] + { + if let Err(e) = crate::fs::init_ext4_from_block_device() { + pr_err!( + "[Init] Warning: Failed to initialize Ext4 filesystem: {:?}", + e + ); + pr_info!("[Init] Continuing without filesystem..."); + } } if let Err(e) = crate::net::config::NetworkConfigManager::init_default_interface() { diff --git a/os/src/mm/memory_space/space/kernel_space.rs b/os/src/mm/memory_space/space/kernel_space.rs index a69c3cf5..44eb175d 100644 --- a/os/src/mm/memory_space/space/kernel_space.rs +++ b/os/src/mm/memory_space/space/kernel_space.rs @@ -95,10 +95,33 @@ impl MemorySpace { UniversalPTEFlag::kernel_rw(), )?; - // 5. 映射物理内存(从 ekernel 到 MEMORY_END 的直接映射) + // 5. 映射物理内存(从 ekernel 到物理内存末端的直接映射) + // + // 评测机可能使用较大的 -m(如 1G/4G),QEMU 把 DTB 放在 RAM 顶端(-m 4G 时约 + // 0xbfe0_0000)。若只映射到编译期 MEMORY_END(128MB),切换页表后解引用 DTB 会缺页, + // 且帧分配器也无法管理评测所需的大内存。改用设备树报告的真实 DRAM 末端,并与帧分配器 + // 范围保持一致(见 mm::init)。 let ekernel_paddr = unsafe { crate::arch::va_to_pa(VA::from_usize(ekernel as usize)) }; + + let phys_mem_end_paddr = match crate::device::device_tree::dram_info() { + Some((dram_start, dram_size)) => dram_start.saturating_add(dram_size), + None => MEMORY_END, + }; + + // LoongArch virt 报告超大 RAM 窗口,用 4K 页映射多 GB 极慢;且 LoongArch 经 DMW 硬件 + // 直映射访问物理内存(不经页表)。把直映射窗口 cap 在 1GiB,与帧分配器一致。RISC-V 不 + // 设上限,覆盖全部 DRAM(确保 DTB 也在范围内)。 + #[cfg(target_arch = "loongarch64")] + let phys_mem_end_paddr = { + const MAX_KERNEL_DIRECT_MAP_BYTES: usize = 1024 * 1024 * 1024; // 1GiB + let cap_end = ekernel_paddr + .as_usize() + .saturating_add(MAX_KERNEL_DIRECT_MAP_BYTES); + phys_mem_end_paddr.min(cap_end) + }; + let phys_mem_start_vaddr = crate::arch::pa_to_va(ekernel_paddr); - let phys_mem_end_vaddr = crate::arch::pa_to_va(PA::from_usize(MEMORY_END)); + let phys_mem_end_vaddr = crate::arch::pa_to_va(PA::from_usize(phys_mem_end_paddr)); let phys_mem_start = Vpn::from_addr_ceil(phys_mem_start_vaddr); let phys_mem_end = Vpn::from_addr_floor(phys_mem_end_vaddr); @@ -113,6 +136,28 @@ impl MemorySpace { phys_mem_area.map(&mut self.page_table)?; self.areas.push(phys_mem_area); + // 确保 DTB 被映射(即使在上面 cap 了物理内存直映射窗口之后)。 + // + // RISC-V 依赖页表直映射访问 DTB;LoongArch 用 DMW 硬件窗口,无需在页表里额外映射。 + // 正常情况下 DTB 落在上面的物理内存直映射范围内,此处仅作兜底。 + #[cfg(not(target_arch = "loongarch64"))] + { + let dtb_paddr = + crate::device::device_tree::DTP.load(core::sync::atomic::Ordering::Acquire); + if dtb_paddr != 0 { + let dtb_start = dtb_paddr & !(PAGE_SIZE - 1); + // 仅当 DTB 不在(可能被 cap 的)物理内存直映射窗口内时才单独映射。 + if dtb_start < ekernel_paddr.as_usize() || dtb_start >= phys_mem_end_paddr { + let dtb_vaddr = crate::arch::pa_to_va(PA::from_usize(dtb_start)); + let vpn = Vpn::from_addr_floor(dtb_vaddr); + if self.find_area(vpn).is_none() { + // 2MiB 足够覆盖 DTB(通常 < 1MiB),且对齐简单。 + self.map_mmio_region(dtb_vaddr, 2 * 1024 * 1024)?; + } + } + } + } + // 暂时移除自动 MMIO 映射 // // 6. 映射 MMIO 区域 // for &(_device, mmio_base, mmio_size) in crate::config::MMIO { diff --git a/os/src/mm/mod.rs b/os/src/mm/mod.rs index 11033933..6a7a45e7 100644 --- a/os/src/mm/mod.rs +++ b/os/src/mm/mod.rs @@ -53,7 +53,46 @@ pub fn init() -> alloc::sync::Arc { + let e = dram_start.saturating_add(dram_size); + crate::println!( + "[MM] DRAM from device tree: {:#x} - {:#x} (size {:#x})", + dram_start, + e, + dram_size + ); + e + } + None => { + crate::println!( + "[MM] DRAM info unavailable, using MEMORY_END {:#x}", + MEMORY_END + ); + MEMORY_END + } + }; + + // LoongArch virt 在 DTB 中报告超大 RAM 窗口,且 LoongArch 通过 DMW 硬件直映射访问物理 + // 内存(不经页表);用 4K 页映射多 GB 既极慢又无意义。把帧分配器范围 cap 在 1GiB, + // 与 map_kernel_space 的直映射上限一致。RISC-V 不设上限,覆盖全部 DRAM。 + let end_usize = { + #[cfg(target_arch = "loongarch64")] + { + const MAX_USABLE_PHYS_BYTES: usize = 1024 * 1024 * 1024; // 1GiB + dram_end.min(start.as_usize().saturating_add(MAX_USABLE_PHYS_BYTES)) + } + #[cfg(not(target_arch = "loongarch64"))] + { + dram_end + } + }; + let end = PA::from_usize(end_usize); // 初始化物理帧分配器 init_frame_allocator(start, end); diff --git a/os/src/vfs/mount.rs b/os/src/vfs/mount.rs index 6ef872bc..750714be 100644 --- a/os/src/vfs/mount.rs +++ b/os/src/vfs/mount.rs @@ -384,6 +384,24 @@ impl MountTable { Ok(()) } + #[cfg(feature = "oscomp")] + pub(crate) fn umount_root_probe(&self) -> Result<(), FsError> { + let mut mounts = self.mounts.lock(); + let stack = mounts.get_mut("/").ok_or(FsError::NotFound)?; + let mount_point = stack.pop().ok_or(FsError::NotFound)?; + + if stack.is_empty() { + mounts.remove("/"); + } + + drop(mounts); + + mount_point.fs.sync()?; + mount_point.fs.umount()?; + + Ok(()) + } + /// 查找给定路径的挂载点 /// /// 返回最长匹配的挂载点(栈顶) diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000..76a1eb1c --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2025-01-18"