Selaa lähdekoodia

feat: implement shared memory syscalls and mmap syscall for file mapping

zhuowei shao 10 kuukautta sitten
vanhempi
commit
fe995aa528
10 muutettua tiedostoa jossa 265 lisäystä ja 59 poistoa
  1. 10 10
      Cargo.lock
  2. 8 5
      Cargo.toml
  3. 1 0
      src/fs/mod.rs
  4. 97 0
      src/fs/shm.rs
  5. 3 2
      src/io.rs
  6. 1 0
      src/kernel/constants.rs
  7. 127 41
      src/kernel/syscall/mm.rs
  8. 4 1
      src/kernel/task/loader/elf.rs
  9. 4 0
      src/kernel/task/process.rs
  10. 10 0
      src/kernel/vfs/file.rs

+ 10 - 10
Cargo.lock

@@ -25,9 +25,9 @@ version = "0.1.0"
 
 [[package]]
 name = "autocfg"
-version = "1.4.0"
+version = "1.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
 
 [[package]]
 name = "bit_field"
@@ -51,9 +51,9 @@ dependencies = [
 
 [[package]]
 name = "cfg-if"
-version = "1.0.0"
+version = "1.0.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
 
 [[package]]
 name = "critical-section"
@@ -392,9 +392,9 @@ dependencies = [
 
 [[package]]
 name = "syn"
-version = "2.0.101"
+version = "2.0.103"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf"
+checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -459,18 +459,18 @@ checksum = "2fe21bcc34ca7fe6dd56cc2cb1261ea59d6b93620215aefb5ea6032265527784"
 
 [[package]]
 name = "zerocopy"
-version = "0.8.25"
+version = "0.8.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1702d9583232ddb9174e01bb7c15a2ab8fb1bc6f227aa1233858c351a3ba0cb"
+checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f"
 dependencies = [
  "zerocopy-derive",
 ]
 
 [[package]]
 name = "zerocopy-derive"
-version = "0.8.25"
+version = "0.8.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28a6e20d751156648aa063f3800b706ee209a32c0b4d9f24be3d980b01be55ef"
+checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181"
 dependencies = [
  "proc-macro2",
  "quote",

+ 8 - 5
Cargo.toml

@@ -43,23 +43,26 @@ log_trace = ["trace_syscall", "trace_scheduler"]
 log_debug = []
 smp = []
 
+[profile.release]
+debug = true
+
 [profile.dev]
 panic = "abort"
 
 [profile.dev.package.eonix_preempt]
-opt-level = 2
+opt-level = "s"
 
 [profile.dev.package.eonix_runtime]
-opt-level = 0
+opt-level = "s"
 
 [profile.dev.package.eonix_sync]
-opt-level = 2
+opt-level = "s"
 
 [profile.dev.package.intrusive_list]
-opt-level = 2
+opt-level = "s"
 
 [profile.dev.package.eonix_hal]
-opt-level = 0
+opt-level = "s"
 
 [profile.dev.package."*"]
 opt-level = "s"

+ 1 - 0
src/fs/mod.rs

@@ -1,4 +1,5 @@
 pub mod fat32;
 pub mod procfs;
+pub mod shm;
 pub mod tmpfs;
 pub mod ext4;

+ 97 - 0
src/fs/shm.rs

@@ -0,0 +1,97 @@
+use core::sync::atomic::{AtomicU32, Ordering};
+
+use alloc::{collections::btree_map::BTreeMap, sync::Arc};
+use bitflags::bitflags;
+use eonix_sync::{LazyLock, Mutex};
+
+use crate::{
+    fs::tmpfs::{DirectoryInode, FileInode, TmpFs},
+    kernel::{constants::ENOSPC, vfs::inode::Mode},
+    prelude::KResult,
+};
+
+bitflags! {
+    #[derive(Debug, Clone, Copy)]
+    pub struct ShmFlags: u32 {
+        /// Create a new segment. If this flag is not used, then shmget() will
+        /// find the segment associated with key and check to see if the user
+        /// has permission to access the segment.
+        const IPC_CREAT = 0o1000;
+        /// This flag is used with IPC_CREAT to ensure that this call creates
+        /// the segment.  If the segment already exists, the call fails.
+        const IPC_EXCL = 0o2000;
+
+        /// Attach the segment for read-only access.If this flag is not specified,
+        /// the segment is attached for read and write access, and the process
+        /// must have read and write permission for the segment.
+        const SHM_RDONLY = 0o10000;
+        /// round attach address to SHMLBA boundary
+        const SHM_RND = 0o20000;
+        /// Allow the contents of the segment to be executed.
+        const SHM_EXEC = 0o100000;
+    }
+}
+
+pub const IPC_PRIVATE: usize = 0;
+
+pub struct ShmManager {
+    tmpfs: Arc<TmpFs>,
+    root: Arc<DirectoryInode>,
+    areas: BTreeMap<u32, ShmArea>,
+}
+
+pub struct ShmArea {
+    pub area: Arc<FileInode>,
+    pub size: usize,
+}
+
+// A big lock here to protect the shared memory area.
+// Can be improved with finer-grained locking?
+pub static SHM_MANAGER: LazyLock<Mutex<ShmManager>> =
+    LazyLock::new(|| Mutex::new(ShmManager::new()));
+
+impl ShmManager {
+    fn new() -> Self {
+        let (tmpfs, root) = TmpFs::create(false).expect("should create shm_area successfully");
+        Self {
+            tmpfs,
+            root,
+            areas: BTreeMap::new(),
+        }
+    }
+
+    pub fn create_shared_area(&self, size: usize, mode: Mode) -> ShmArea {
+        let ino = self.tmpfs.assign_ino();
+        let vfs = Arc::downgrade(&self.tmpfs);
+        ShmArea {
+            area: FileInode::new(ino, vfs, mode),
+            size,
+        }
+    }
+
+    pub fn get(&self, shmid: u32) -> Option<&ShmArea> {
+        self.areas.get(&shmid)
+    }
+
+    pub fn insert(&mut self, shmid: u32, area: ShmArea) {
+        self.areas.insert(shmid, area);
+    }
+}
+
+pub fn gen_shm_id(key: usize) -> KResult<u32> {
+    const SHM_MAGIC: u32 = 114514000;
+
+    static NEXT_SHMID: AtomicU32 = AtomicU32::new(0);
+
+    if key == IPC_PRIVATE {
+        let shmid = NEXT_SHMID.fetch_add(1, Ordering::Relaxed);
+
+        if shmid < SHM_MAGIC {
+            return Err(ENOSPC);
+        } else {
+            return Ok(shmid);
+        }
+    }
+
+    (key as u32).checked_add(SHM_MAGIC).ok_or(ENOSPC)
+}

+ 3 - 2
src/io.rs

@@ -3,6 +3,7 @@ use crate::prelude::*;
 use core::{cmp, mem::MaybeUninit};
 
 #[must_use]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum FillResult {
     Done(usize),
     Partial(usize),
@@ -63,7 +64,7 @@ pub trait StreamRead {
     fn read_till_end(
         &mut self,
         buffer: &mut [u8],
-        func: impl Fn(&mut [u8]) -> KResult<()>,
+        func: impl FnMut(&mut [u8]) -> KResult<()>,
     ) -> KResult<usize>;
 
     fn ignore_all(&mut self) -> KResult<usize>;
@@ -76,7 +77,7 @@ where
     fn read_till_end(
         &mut self,
         buffer: &mut [u8],
-        func: impl Fn(&mut [u8]) -> KResult<()>,
+        mut func: impl FnMut(&mut [u8]) -> KResult<()>,
     ) -> KResult<usize> {
         let mut total = 0;
         while let Some(data) = self.poll_data(buffer)? {

+ 1 - 0
src/kernel/constants.rs

@@ -35,6 +35,7 @@ pub const ENOTDIR: u32 = 20;
 pub const EISDIR: u32 = 21;
 pub const EINVAL: u32 = 22;
 pub const ENOTTY: u32 = 25;
+pub const ENOSPC: u32 = 28;
 pub const ESPIPE: u32 = 29;
 // pub const EROFS: u32 = 30;
 pub const EPIPE: u32 = 32;

+ 127 - 41
src/kernel/syscall/mm.rs

@@ -1,6 +1,9 @@
 use super::FromSyscallArg;
-use crate::kernel::constants::{EINVAL, ENOMEM};
+use crate::fs::shm::{gen_shm_id, ShmFlags, IPC_PRIVATE, SHM_MANAGER};
+use crate::kernel::constants::{EBADF, EEXIST, EINVAL, ENOENT, ENOMEM};
+use crate::kernel::mem::FileMapping;
 use crate::kernel::task::Thread;
+use crate::kernel::vfs::filearray::FD;
 use crate::{
     kernel::{
         constants::{UserMmapFlags, UserMmapProtocol},
@@ -26,8 +29,15 @@ impl FromSyscallArg for UserMmapFlags {
     }
 }
 
+impl FromSyscallArg for ShmFlags {
+    fn from_arg(value: usize) -> Self {
+        ShmFlags::from_bits_truncate(value as u32)
+    }
+}
+
 /// Check whether we are doing an implemented function.
 /// If `condition` is false, return `Err(err)`.
+#[allow(unused)]
 fn check_impl(condition: bool, err: u32) -> KResult<()> {
     if !condition {
         Err(err)
@@ -42,60 +52,57 @@ fn do_mmap2(
     len: usize,
     prot: UserMmapProtocol,
     flags: UserMmapFlags,
-    fd: u32,
+    fd: FD,
     pgoffset: usize,
 ) -> KResult<usize> {
     let addr = VAddr::from(addr);
-    if !addr.is_page_aligned() || len == 0 {
+    if !addr.is_page_aligned() || pgoffset % PAGE_SIZE != 0 || len == 0 {
         return Err(EINVAL);
     }
 
     let len = len.align_up(PAGE_SIZE);
-    check_impl(flags.contains(UserMmapFlags::MAP_ANONYMOUS), ENOMEM)?;
-    check_impl(flags.contains(UserMmapFlags::MAP_PRIVATE), EINVAL)?;
-    if fd != u32::MAX || pgoffset != 0 {
-        return Err(EINVAL);
-    }
-
     let mm_list = &thread.process.mm_list;
+    let is_shared = flags.contains(UserMmapFlags::MAP_SHARED);
+
+    let mapping = if flags.contains(UserMmapFlags::MAP_ANONYMOUS) {
+        if pgoffset != 0 {
+            return Err(EINVAL);
+        }
+
+        if !is_shared {
+            Mapping::Anonymous
+        } else {
+            // The mode is unimportant here, since we are checking prot in mm_area.
+            let shared_area = Task::block_on(SHM_MANAGER.lock()).create_shared_area(len, 0x777);
+            Mapping::File(FileMapping::new(shared_area.area.clone(), 0, len))
+        }
+    } else {
+        let file = thread
+            .files
+            .get(fd)
+            .ok_or(EBADF)?
+            .get_inode()?
+            .ok_or(EBADF)?;
+
+        Mapping::File(FileMapping::new(file, pgoffset, len))
+    };
+
+    let permission = Permission {
+        read: prot.contains(UserMmapProtocol::PROT_READ),
+        write: prot.contains(UserMmapProtocol::PROT_WRITE),
+        execute: prot.contains(UserMmapProtocol::PROT_EXEC),
+    };
 
     // TODO!!!: If we are doing mmap's in 32-bit mode, we should check whether
     //          `addr` is above user reachable memory.
     let addr = if flags.contains(UserMmapFlags::MAP_FIXED) {
         if prot.is_empty() {
-            Task::block_on(mm_list.protect(
-                addr,
-                len,
-                Permission {
-                    read: prot.contains(UserMmapProtocol::PROT_READ),
-                    write: prot.contains(UserMmapProtocol::PROT_WRITE),
-                    execute: prot.contains(UserMmapProtocol::PROT_EXEC),
-                },
-            ))
-            .map(|_| addr)
+            Task::block_on(mm_list.protect(addr, len, permission)).map(|_| addr)
         } else {
-            mm_list.mmap_fixed(
-                addr,
-                len,
-                Mapping::Anonymous,
-                Permission {
-                    read: prot.contains(UserMmapProtocol::PROT_READ),
-                    write: prot.contains(UserMmapProtocol::PROT_WRITE),
-                    execute: prot.contains(UserMmapProtocol::PROT_EXEC),
-                },
-            )
+            mm_list.mmap_fixed(addr, len, mapping, permission, is_shared)
         }
     } else {
-        mm_list.mmap_hint(
-            addr,
-            len,
-            Mapping::Anonymous,
-            Permission {
-                read: prot.contains(UserMmapProtocol::PROT_READ),
-                write: prot.contains(UserMmapProtocol::PROT_WRITE),
-                execute: prot.contains(UserMmapProtocol::PROT_EXEC),
-            },
-        )
+        mm_list.mmap_hint(addr, len, mapping, permission, is_shared)
     };
 
     addr.map(|addr| addr.addr())
@@ -108,7 +115,7 @@ fn mmap(
     len: usize,
     prot: UserMmapProtocol,
     flags: UserMmapFlags,
-    fd: u32,
+    fd: FD,
     offset: usize,
 ) -> KResult<usize> {
     do_mmap2(thread, addr, len, prot, flags, fd, offset / PAGE_SIZE)
@@ -121,7 +128,7 @@ fn mmap2(
     len: usize,
     prot: UserMmapProtocol,
     flags: UserMmapFlags,
-    fd: u32,
+    fd: FD,
     pgoffset: usize,
 ) -> KResult<usize> {
     do_mmap2(thread, addr, len, prot, flags, fd, pgoffset)
@@ -169,6 +176,85 @@ fn mprotect(addr: usize, len: usize, prot: UserMmapProtocol) -> KResult<()> {
     ))
 }
 
+#[eonix_macros::define_syscall(SYS_SHMGET)]
+fn shmget(key: usize, size: usize, shmflg: ShmFlags) -> KResult<u32> {
+    let size = size.align_up(PAGE_SIZE);
+
+    let mut shm_manager = Task::block_on(SHM_MANAGER.lock());
+    let shmid = gen_shm_id(key)?;
+
+    if key == IPC_PRIVATE {
+        let new_shm = shm_manager.create_shared_area(size, shmflg.bits() & 0x777);
+        shm_manager.insert(shmid, new_shm);
+        return Ok(shmid);
+    }
+
+    if let Some(_) = shm_manager.get(shmid) {
+        if shmflg.contains(ShmFlags::IPC_CREAT | ShmFlags::IPC_EXCL) {
+            return Err(EEXIST);
+        }
+
+        return Ok(shmid);
+    }
+
+    if shmflg.contains(ShmFlags::IPC_CREAT) {
+        let new_shm = shm_manager.create_shared_area(size, shmflg.bits() & 0x777);
+        shm_manager.insert(shmid, new_shm);
+        return Ok(shmid);
+    }
+
+    return Err(ENOENT);
+}
+
+#[eonix_macros::define_syscall(SYS_SHMAT)]
+fn shmat(shmid: u32, addr: usize, shmflg: ShmFlags) -> KResult<usize> {
+    let mm_list = &thread.process.mm_list;
+    let shm_manager = Task::block_on(SHM_MANAGER.lock());
+    let shm_area = shm_manager.get(shmid).ok_or(EINVAL)?;
+
+    let mut permission = Permission {
+        read: true,
+        write: true,
+        execute: false,
+    };
+
+    if shmflg.contains(ShmFlags::SHM_EXEC) {
+        permission.execute = true;
+    }
+    if shmflg.contains(ShmFlags::SHM_RDONLY) {
+        permission.write = false;
+    }
+
+    let mapping = Mapping::File(FileMapping {
+        file: shm_area.area.clone(),
+        offset: 0,
+        length: shm_area.size,
+    });
+
+    let addr = if addr != 0 {
+        if addr % PAGE_SIZE != 0 && !shmflg.contains(ShmFlags::SHM_RND) {
+            return Err(EINVAL);
+        }
+        let addr = VAddr::from(addr.align_down(PAGE_SIZE));
+        mm_list.mmap_fixed(addr, shm_area.size, mapping, permission, true)
+    } else {
+        mm_list.mmap_hint(VAddr::NULL, shm_area.size, mapping, permission, true)
+    }?;
+
+    thread.process.shm_areas.lock().insert(addr, shm_area.size);
+
+    Ok(addr.addr())
+}
+
+#[eonix_macros::define_syscall(SYS_SHMDT)]
+fn shmdt(addr: usize) -> KResult<usize> {
+    let addr = VAddr::from(addr);
+    let mut shm_areas = thread.process.shm_areas.lock();
+    let size = *shm_areas.get(&addr).ok_or(EINVAL)?;
+    shm_areas.remove(&addr);
+    return Task::block_on(thread.process.mm_list.unmap(addr, size)).map(|_| 0);
+}
+
 #[eonix_macros::define_syscall(SYS_MEMBARRIER)]
 fn membarrier(_cmd: usize, _flags: usize) -> KResult<()> {
     Ok(())

+ 4 - 1
src/kernel/task/loader/elf.rs

@@ -274,6 +274,7 @@ impl<E: ElfArch> Elf<E> {
                 write: true,
                 execute: false,
             },
+            false,
         )?;
 
         StackInitializer::new(&mm_list, E::STACK_BASE_ADDR, args, envs, aux_vec).init()
@@ -356,11 +357,12 @@ impl<E: ElfArch> Elf<E> {
                 vmap_start,
                 file_len,
                 Mapping::File(FileMapping::new(
-                    self.file.clone(),
+                    self.file.get_inode()?,
                     file_offset,
                     real_file_length,
                 )),
                 permission,
+                false,
             )?;
         }
 
@@ -370,6 +372,7 @@ impl<E: ElfArch> Elf<E> {
                 vmem_len - file_len,
                 Mapping::Anonymous,
                 permission,
+                false,
             )?;
         }
 

+ 4 - 0
src/kernel/task/process.rs

@@ -15,6 +15,7 @@ use alloc::{
     sync::{Arc, Weak},
 };
 use core::sync::atomic::{AtomicU32, Ordering};
+use eonix_mm::address::VAddr;
 use eonix_runtime::task::Task;
 use eonix_sync::{
     AsProof as _, AsProofMut as _, Locked, Proof, ProofMut, RwLockReadGuard, SpinGuard,
@@ -47,6 +48,8 @@ pub struct Process {
 
     pub exit_signal: Option<Signal>,
 
+    pub shm_areas: Spin<BTreeMap<VAddr, usize>>,
+
     /// Parent process
     ///
     /// `parent` must be valid during the whole life of the process.
@@ -221,6 +224,7 @@ impl ProcessBuilder {
             pid: self.pid.expect("should set pid before building"),
             wait_list: WaitList::new(),
             mm_list,
+            shm_areas: Spin::new(BTreeMap::new()),
             exit_signal: self.exit_signal,
             parent: RCUPointer::empty(),
             pgroup: RCUPointer::empty(),

+ 10 - 0
src/kernel/vfs/file.rs

@@ -11,6 +11,7 @@ use crate::{
         task::Thread,
         terminal::{Terminal, TerminalIORequest},
         user::{UserPointer, UserPointerMut},
+        vfs::inode::Inode,
         CharDevice,
     },
     prelude::*,
@@ -86,6 +87,15 @@ pub struct File {
     file_type: FileType,
 }
 
+impl File {
+    pub fn get_inode(&self) -> KResult<Option<Arc<dyn Inode>>> {
+        match &self.file_type {
+            FileType::Inode(inode_file) => Ok(Some(inode_file.dentry.get_inode()?)),
+            _ => Ok(None),
+        }
+    }
+}
+
 pub enum SeekOption {
     Set(usize),
     Current(isize),