diff --git a/kernel/aster-nix/src/syscall/mmap.rs b/kernel/aster-nix/src/syscall/mmap.rs index aa98835a..7a35aa2d 100644 --- a/kernel/aster-nix/src/syscall/mmap.rs +++ b/kernel/aster-nix/src/syscall/mmap.rs @@ -52,6 +52,8 @@ fn do_sys_mmap( addr, len, vm_perms, option, fd, offset ); + check_option(&option)?; + let len = len.align_up(PAGE_SIZE); if offset % PAGE_SIZE != 0 { @@ -78,6 +80,11 @@ fn do_sys_mmap( // TODO: support MAP_32BIT. MAP_32BIT requires the map range to be below 2GB warn!("MAP_32BIT is not supported"); } + + if option.typ() == MMapType::Shared { + options = options.is_shared(true); + } + options }; let map_addr = vm_map_options.build()?; @@ -121,6 +128,14 @@ fn alloc_filebacked_vmo( } } +fn check_option(option: &MMapOptions) -> Result<()> { + if option.typ() == MMapType::File { + return_errno_with_message!(Errno::EINVAL, "Invalid mmap type"); + } + + Ok(()) +} + // Definition of MMap flags, conforming to the linux mmap interface: // https://man7.org/linux/man-pages/man2/mmap.2.html // diff --git a/kernel/aster-nix/src/vm/vmar/dyn_cap.rs b/kernel/aster-nix/src/vm/vmar/dyn_cap.rs index 289ddeb6..1a7a9232 100644 --- a/kernel/aster-nix/src/vm/vmar/dyn_cap.rs +++ b/kernel/aster-nix/src/vm/vmar/dyn_cap.rs @@ -153,7 +153,7 @@ impl Vmar { /// The method requires the Read right. pub fn fork_from(vmar: &Vmar) -> Result { vmar.check_rights(Rights::READ)?; - let vmar_ = vmar.0.new_cow_root()?; + let vmar_ = vmar.0.new_fork_root()?; Ok(Vmar(vmar_, Rights::all())) } } diff --git a/kernel/aster-nix/src/vm/vmar/mod.rs b/kernel/aster-nix/src/vm/vmar/mod.rs index 6852a99b..7c4b1380 100644 --- a/kernel/aster-nix/src/vm/vmar/mod.rs +++ b/kernel/aster-nix/src/vm/vmar/mod.rs @@ -706,16 +706,16 @@ impl Vmar_ { Ok(()) } - pub(super) fn new_cow_root(self: &Arc) -> Result> { + pub(super) fn new_fork_root(self: &Arc) -> Result> { if self.parent.upgrade().is_some() { return_errno_with_message!(Errno::EINVAL, "can only dup cow vmar for root vmar"); } - self.new_cow(None) + self.new_fork(None) } /// Create a new vmar by creating cow child for all mapped vmos. - fn new_cow(&self, parent: Option<&Arc>) -> Result> { + fn new_fork(&self, parent: Option<&Arc>) -> Result> { let new_vmar_ = { let vmar_inner = VmarInner::new(); // If this is not a root `Vmar`, we clone the `VmSpace` from parent. @@ -742,7 +742,7 @@ impl Vmar_ { // Clone child vmars. for (child_vmar_base, child_vmar_) in &inner.child_vmar_s { - let new_child_vmar = child_vmar_.new_cow(Some(&new_vmar_))?; + let new_child_vmar = child_vmar_.new_fork(Some(&new_vmar_))?; new_vmar_ .inner .lock() @@ -752,7 +752,7 @@ impl Vmar_ { // Clone vm mappings. for (vm_mapping_base, vm_mapping) in &inner.vm_mappings { - let new_mapping = Arc::new(vm_mapping.new_cow(&new_vmar_)?); + let new_mapping = Arc::new(vm_mapping.new_fork(&new_vmar_)?); new_vmar_ .inner .lock() diff --git a/kernel/aster-nix/src/vm/vmar/static_cap.rs b/kernel/aster-nix/src/vm/vmar/static_cap.rs index 2e69d546..14aab512 100644 --- a/kernel/aster-nix/src/vm/vmar/static_cap.rs +++ b/kernel/aster-nix/src/vm/vmar/static_cap.rs @@ -160,7 +160,7 @@ impl Vmar> { /// The method requires the Read right. pub fn fork_from(vmar: &Vmar) -> Result { vmar.check_rights(Rights::READ)?; - let vmar_ = vmar.0.new_cow_root()?; + let vmar_ = vmar.0.new_fork_root()?; Ok(Vmar(vmar_, TRightSet(R::new()))) } diff --git a/kernel/aster-nix/src/vm/vmar/vm_mapping.rs b/kernel/aster-nix/src/vm/vmar/vm_mapping.rs index 652ad543..e57b6483 100644 --- a/kernel/aster-nix/src/vm/vmar/vm_mapping.rs +++ b/kernel/aster-nix/src/vm/vmar/vm_mapping.rs @@ -24,6 +24,10 @@ pub struct VmMapping { parent: Weak, /// The mapped vmo. The mapped vmo is with dynamic capability. vmo: Vmo, + /// Whether the mapping is shared among processes + /// TODO: support file-backed shared mappings. + /// only anonyous memory can be mapped shared now. + is_shared: bool, } impl VmMapping { @@ -34,6 +38,7 @@ impl VmMapping { inner: Mutex::new(inner), parent: self.parent.clone(), vmo, + is_shared: self.is_shared, }) } } @@ -73,6 +78,7 @@ impl VmMapping { offset, align, can_overwrite, + is_shared, } = option; let Vmar(parent_vmar, _) = parent; let vmo_size = vmo.size(); @@ -102,6 +108,7 @@ impl VmMapping { inner: Mutex::new(vm_mapping_inner), parent: Arc::downgrade(&parent_vmar), vmo: vmo.to_dyn(), + is_shared, }) } @@ -274,13 +281,17 @@ impl VmMapping { Ok(()) } - pub(super) fn new_cow(&self, new_parent: &Arc) -> Result { + pub(super) fn new_fork(&self, new_parent: &Arc) -> Result { let VmMapping { inner, vmo, .. } = self; let child_vmo = { let parent_vmo = vmo.dup().unwrap(); let vmo_size = parent_vmo.size(); - VmoChildOptions::new_cow(parent_vmo, 0..vmo_size).alloc()? + if self.is_shared { + VmoChildOptions::new_slice_rights(parent_vmo, 0..vmo_size).alloc()? + } else { + VmoChildOptions::new_cow(parent_vmo, 0..vmo_size).alloc()? + } }; let new_inner = { @@ -299,6 +310,7 @@ impl VmMapping { inner: Mutex::new(new_inner), parent: Arc::downgrade(new_parent), vmo: child_vmo, + is_shared: self.is_shared, }) } @@ -614,6 +626,8 @@ pub struct VmarMapOptions { offset: Option, align: usize, can_overwrite: bool, + // Whether the mapping is mapped with `MAP_SHARED` + is_shared: bool, } impl VmarMapOptions { @@ -634,6 +648,7 @@ impl VmarMapOptions { offset: None, align: PAGE_SIZE, can_overwrite: false, + is_shared: false, } } @@ -703,6 +718,17 @@ impl VmarMapOptions { self } + /// Sets whether the mapping can be shared with other process. + /// + /// The default value is false. + /// + /// If this value is set to true, the mapping will be shared with child + /// process (by creating slice child vmo) when forking. + pub fn is_shared(mut self, is_shared: bool) -> Self { + self.is_shared = is_shared; + self + } + /// Creates the mapping. /// /// All options will be checked at this point. diff --git a/kernel/aster-nix/src/vm/vmo/options.rs b/kernel/aster-nix/src/vm/vmo/options.rs index 2d2ae374..34735982 100644 --- a/kernel/aster-nix/src/vm/vmo/options.rs +++ b/kernel/aster-nix/src/vm/vmo/options.rs @@ -571,4 +571,17 @@ mod test { vmo.resize(PAGE_SIZE).unwrap(); assert_eq!(vmo.read_val::(10).unwrap(), 42); } + + #[ktest] + fn resize_cow() { + let vmo = VmoOptions::::new(10 * PAGE_SIZE) + .flags(VmoFlags::RESIZABLE) + .alloc() + .unwrap(); + + let cow_child = VmoChildOptions::new_cow(vmo, 0..PAGE_SIZE).alloc().unwrap(); + + cow_child.resize(2 * PAGE_SIZE).unwrap(); + assert_eq!(cow_child.size(), 2 * PAGE_SIZE); + } } diff --git a/regression/apps/Makefile b/regression/apps/Makefile index 0c8eba9a..43c34678 100644 --- a/regression/apps/Makefile +++ b/regression/apps/Makefile @@ -19,6 +19,7 @@ TEST_APPS := \ hello_c \ hello_pie \ hello_world \ + mmap \ mongoose \ network \ pthread \ diff --git a/regression/apps/mmap/Makefile b/regression/apps/mmap/Makefile new file mode 100644 index 00000000..ce42e33b --- /dev/null +++ b/regression/apps/mmap/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MPL-2.0 + +include ../test_common.mk + +EXTRA_C_FLAGS := \ No newline at end of file diff --git a/regression/apps/mmap/map_shared_anon.c b/regression/apps/mmap/map_shared_anon.c new file mode 100644 index 00000000..eb76f3ce --- /dev/null +++ b/regression/apps/mmap/map_shared_anon.c @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MPL-2.0 + +#include +#include +#include +#include +#include + +int main() +{ + int *shared_memory; + int value = 123; + + // Create an anonymous memory region for sharing + shared_memory = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, + MAP_SHARED | MAP_ANONYMOUS, -1, 0); + if (shared_memory == MAP_FAILED) { + perror("mmap failed"); + exit(1); + } + + // Create a child process + pid_t pid = fork(); + + if (pid < 0) { + perror("fork"); + exit(1); + } else if (pid == 0) { + // Child process + + // Modify the value in the shared memory in the child process + *shared_memory = value; + + printf("Child process: Value in shared memory: %d\n", + *shared_memory); + + } else { + // Parent process + + // Wait for the child process to finish + wait(NULL); + + printf("Parent process: Value in shared memory: %d\n", + *shared_memory); + + if (*shared_memory != value) { + perror("shared memory contains invalid value"); + exit(1); + } + + // Unmap the memory + if (munmap(shared_memory, sizeof(int)) == -1) { + perror("munmap failed"); + exit(1); + } + } + + return 0; +} \ No newline at end of file diff --git a/regression/apps/scripts/process.sh b/regression/apps/scripts/process.sh index b2324139..d6d20f1a 100755 --- a/regression/apps/scripts/process.sh +++ b/regression/apps/scripts/process.sh @@ -8,7 +8,7 @@ SCRIPT_DIR=/regression cd ${SCRIPT_DIR}/.. echo "Start process test......" -tests="hello_world/hello_world fork/fork execve/execve fork_c/fork signal_c/signal_test pthread/pthread_test hello_pie/hello pty/open_pty getpid/getpid eventfd2/eventfd2" +tests="hello_world/hello_world fork/fork execve/execve fork_c/fork signal_c/signal_test pthread/pthread_test hello_pie/hello pty/open_pty getpid/getpid eventfd2/eventfd2 mmap/map_shared_anon" for testcase in ${tests} do echo "Running test ${testcase}......" diff --git a/regression/syscall_test/Makefile b/regression/syscall_test/Makefile index 317b994c..f85bb10d 100644 --- a/regression/syscall_test/Makefile +++ b/regression/syscall_test/Makefile @@ -17,6 +17,7 @@ TESTS ?= \ link_test \ lseek_test \ mkdir_test \ + mmap_test \ open_create_test \ open_test \ pty_test \ diff --git a/regression/syscall_test/blocklists/mmap_test b/regression/syscall_test/blocklists/mmap_test new file mode 100644 index 00000000..12b1ca2e --- /dev/null +++ b/regression/syscall_test/blocklists/mmap_test @@ -0,0 +1,18 @@ +MMapFileTest* +MMapNoFixtureTest* +ReadWriteSharedPrivate* +MMapDeathTest.TruncateAfterCOWBreak +MMapTest.FixedAlignment +MMapTest.InvalidLength +MMapTest.ProtWriteOnlyReadable +MMapTest.MprotectNotPageAligned +MMapTest.MprotectHugeLength +MMapTest.HugeLength +MMapTest.ExceedLimit* +MMapTest.NoExceedLimitAS +MMapTest.AccessCOWInvalidatesCachedSegments +MMapTest.MapPipe +MMapTest.MapDevZero* +MMapTest.MapCharDevice +MMapTest.MapDirectory +MMapTest.PrivateAndShared