diff --git a/src/libos/src/ipc/mod.rs b/src/libos/src/ipc/mod.rs new file mode 100644 index 00000000..0a00f5a9 --- /dev/null +++ b/src/libos/src/ipc/mod.rs @@ -0,0 +1,7 @@ +use super::*; + +mod shm; +mod syscalls; + +pub use self::shm::{key_t, shmids_t, SHM_MANAGER}; +pub use self::syscalls::{do_shmat, do_shmctl, do_shmdt, do_shmget}; diff --git a/src/libos/src/ipc/shm.rs b/src/libos/src/ipc/shm.rs new file mode 100644 index 00000000..bcfa8a8a --- /dev/null +++ b/src/libos/src/ipc/shm.rs @@ -0,0 +1,509 @@ +use super::*; + +use crate::fs::FileMode; +use crate::process::{do_getegid, do_geteuid, gid_t, uid_t, ThreadRef}; +use crate::time::{do_gettimeofday, time_t}; +use crate::vm::{ + ChunkRef, VMInitializer, VMMapOptionsBuilder, VMPerms, VMRange, USER_SPACE_VM_MANAGER, +}; +use std::collections::{HashMap, HashSet}; + +#[allow(non_camel_case_types)] +pub type key_t = u32; +pub type ShmId = u32; +pub type CmdId = u32; + +// min shared seg size (bytes) +const SHMMIN: usize = 1; +// max shared seg size (bytes) +const SHMMAX: usize = (usize::MAX - (1_usize << 24)); +// max num of segs system wide, +// also indicates the max shmid - 1 in Occlum +const SHMMNI: ShmId = 4096; + +const IPC_PRIVATE: key_t = 0; + +// For cmd in shmctl() +const IPC_RMID: CmdId = 0; +const IPC_SET: CmdId = 1; +const IPC_STAT: CmdId = 2; +const IPC_INFO: CmdId = 3; +const SHM_LOCK: CmdId = 11; +const SHM_UNLOCK: CmdId = 12; +const SHM_STAT: CmdId = 13; +const SHM_INFO: CmdId = 14; +const SHM_STAT_ANY: CmdId = 15; + +#[allow(non_camel_case_types)] +#[derive(Debug)] +#[repr(C)] +struct ipc_perm_t { + key: key_t, + uid: uid_t, + gid: gid_t, + cuid: uid_t, + cgid: gid_t, + mode: u16, + pad1: u16, + seq: u16, + pad2: u16, + unused1: u64, + unused2: u64, +} + +#[allow(non_camel_case_types)] +#[derive(Debug)] +#[repr(C)] +pub struct shmids_t { + shm_perm: ipc_perm_t, + shm_segsz: size_t, + shm_atime: time_t, + shm_dtime: time_t, + shm_ctime: time_t, + shm_cpid: pid_t, + shm_lpid: pid_t, + shm_nattach: u64, + unused1: u64, + unused2: u64, +} + +// shared memory segment status +bitflags! { + struct ShmStatus : u16 { + // segment will be destroyed on last detach + const SHM_DEST = 0o1000; + // segment will not be swapped, unused now + const SHM_LOCKED = 0o2000; + } +} + +bitflags! { + pub struct ShmFlags: u32 { + const IPC_CREAT = 0o1000; + const IPC_EXCL = 0o2000; + + /// read by owner + const S_IRUSR = FileMode::S_IRUSR.bits() as u32; + /// write by owner + const S_IWUSR = FileMode::S_IWUSR.bits() as u32; + /// execute/search by owner + const S_IXUSR = FileMode::S_IXUSR.bits() as u32; + /// read by group + const S_IRGRP = FileMode::S_IRGRP.bits() as u32; + /// write by group + const S_IWGRP = FileMode::S_IWGRP.bits() as u32; + /// execute/search by group + const S_IXGRP = FileMode::S_IXGRP.bits() as u32; + /// read by others + const S_IROTH = FileMode::S_IROTH.bits() as u32; + /// write by others + const S_IWOTH = FileMode::S_IWOTH.bits() as u32; + /// execute/search by others + const S_IXOTH = FileMode::S_IXOTH.bits() as u32; + } +} + +impl ShmFlags { + fn to_file_mode(&self) -> FileMode { + let mut shmflgs = *self; + shmflgs.remove(ShmFlags::IPC_CREAT); + shmflgs.remove(ShmFlags::IPC_EXCL); + let file_mode = FileMode::from_bits(shmflgs.bits as u16 & FileMode::all().bits()).unwrap(); + file_mode + } +} + +#[derive(Debug)] +struct ShmSegment { + shmid: ShmId, + key: key_t, + + uid: uid_t, + gid: gid_t, + cuid: uid_t, + cgid: gid_t, + mode: FileMode, + status: ShmStatus, + + shm_atime: time_t, + shm_dtime: time_t, + shm_ctime: time_t, + + shm_cpid: pid_t, + shm_lpid: pid_t, + + shm_nattach: u64, + chunk: ChunkRef, + process_set: HashSet, +} + +impl ShmSegment { + fn new(shmid: ShmId, key: key_t, size: size_t, mode: FileMode) -> Result { + let vm_option = VMMapOptionsBuilder::default() + .size(size) + // Currently, Occlum only support shared memory segment with rw permission + .perms(VMPerms::READ | VMPerms::WRITE) + .initializer(VMInitializer::FillZeros()) + .build()?; + let chunk = USER_SPACE_VM_MANAGER.internal().mmap_chunk(&vm_option)?; + + Ok(ShmSegment { + shmid: shmid, + key: key, + uid: do_geteuid().unwrap() as u32, + cuid: do_geteuid().unwrap() as u32, + gid: do_getegid().unwrap() as u32, + cgid: do_getegid().unwrap() as u32, + mode: mode, + status: ShmStatus::empty(), + shm_atime: 0, + shm_dtime: 0, + shm_ctime: ShmManager::current_time(), + shm_cpid: current!().process().pid(), + shm_lpid: 0, + shm_nattach: 0, + chunk: chunk, + process_set: HashSet::new(), + }) + } + + fn check_perm(&self) -> Result<()> { + // TODO: Add permission control + Ok(()) + } + + fn set_destruction(&mut self) { + self.status.insert(ShmStatus::SHM_DEST) + } + + fn is_destruction(&self) -> bool { + self.status.contains(ShmStatus::SHM_DEST) + } + + fn shm_start(&self) -> usize { + self.chunk.range().start() + } + + fn shm_size(&self) -> usize { + self.chunk.range().size() + } + + fn shm_add_pid(&mut self, pid: &pid_t) -> Result<()> { + if self.process_set.contains(pid) { + return_errno!(EINVAL, "this pid has been attached to the shm"); + } + self.process_set.insert(*pid); + Ok(()) + } + + fn shm_remove_pid(&mut self, pid: &pid_t) -> Result<()> { + if !self.process_set.contains(pid) { + return_errno!(EINVAL, " this pid has not been attached to the shm"); + } + self.process_set.remove(&pid); + Ok(()) + } +} + +impl Drop for ShmSegment { + fn drop(&mut self) { + debug!("drop shm: {:?}", self); + assert!(self.shm_nattach == 0); + assert!(self.process_set.len() == 0); + USER_SPACE_VM_MANAGER + .internal() + .munmap_chunk(&self.chunk, None); + } +} + +#[derive(Debug)] +struct ShmIdManager { + used_id: HashSet, + free_num: u32, + last_alloc_id: ShmId, +} + +impl ShmIdManager { + fn new() -> Self { + let used_id = HashSet::new(); + let free_num = SHMMNI as u32; + let last_alloc_id = SHMMNI - 1; + ShmIdManager { + used_id, + free_num, + last_alloc_id, + } + } + + // Always return next free id for shmid + fn get_new_shmid(&mut self) -> Result { + if self.free_num == 0 { + return_errno!(ENOSPC, "all possible shared memory IDs have been taken"); + } else { + self.free_num -= 1; + } + let mut id = self.last_alloc_id + 1; + loop { + if id == SHMMNI { + id = 0; + } + if !self.used_id.contains(&id) { + break; + } + id += 1; + } + self.last_alloc_id = id; + Ok(id) + } + + fn free_shmid(&mut self, shmid: &ShmId) -> Result<()> { + self.free_num += 1; + self.used_id.remove(shmid); + Ok(()) + } +} + +lazy_static! { + pub static ref SHM_MANAGER: ShmManager = ShmManager::new(); +} + +#[derive(Debug)] +pub struct ShmManager { + shm_segments: RwLock>, + shmid_manager: RwLock, +} + +impl ShmManager { + fn new() -> Self { + ShmManager { + shm_segments: RwLock::new(HashMap::new()), + shmid_manager: RwLock::new(ShmIdManager::new()), + } + } + + fn current_time() -> time_t { + do_gettimeofday().sec() + } + + fn get_new_shmid(&self) -> Result { + let mut shmid_manager = self.shmid_manager.write().unwrap(); + shmid_manager.get_new_shmid() + } + + fn free_shmid(&self, shmid: &ShmId) -> Result<()> { + let mut shmid_manager = self.shmid_manager.write().unwrap(); + shmid_manager.free_shmid(&shmid) + } + + fn shmctl_rmshm(&self, shmid: ShmId) -> Result<()> { + let mut shm_segments = self.shm_segments.write().unwrap(); + let shm = shm_segments.get_mut(&shmid); + + if let Some(shm) = shm { + shm.shm_ctime = ShmManager::current_time(); + shm.set_destruction(); + if shm.shm_nattach == 0 { + let shmid = shm.shmid; + self.free_shmid(&shmid)?; + shm_segments.remove(&shmid); + } + } else { + return_errno!(EINVAL, "cannot find shm by shmid"); + } + Ok(()) + } + + fn shmctl_ipcstat(&self, shmid: ShmId, buf: Option<&mut shmids_t>) -> Result<()> { + let shm_segments = self.shm_segments.read().unwrap(); + let shm = shm_segments.get(&shmid); + if let Some(shm) = shm { + shm.check_perm()?; + let mut buf = match buf { + Some(buf) => buf, + None => return_errno!(EFAULT, "buf is empty"), + }; + let shm_perm = ipc_perm_t { + key: shm.key, + uid: shm.uid, + gid: shm.uid, + cuid: shm.cuid, + cgid: shm.cgid, + mode: shm.mode.bits() | shm.status.bits(), + pad1: 0, + seq: 0, + pad2: 0, + unused1: 0, + unused2: 0, + }; + let shmids = shmids_t { + shm_perm: shm_perm, + shm_segsz: shm.shm_size(), + shm_atime: shm.shm_atime, + shm_dtime: shm.shm_dtime, + shm_ctime: shm.shm_ctime, + shm_cpid: shm.shm_cpid, + shm_lpid: shm.shm_lpid, + shm_nattach: shm.shm_nattach, + unused1: 0, + unused2: 0, + }; + *buf = shmids; + } else { + return_errno!(EINVAL, "cannot find shm by shmid"); + } + Ok(()) + } + + pub fn do_shmget(&self, key: key_t, size: usize, shmflg: ShmFlags) -> Result { + debug!( + "do_shmget: key: {:?}, size: {:?}, shmflg: {:?}", + key, size, shmflg + ); + + // Check the size from user + if size < SHMMIN || size > SHMMAX { + return_errno!(EINVAL, "invalid size"); + } + + let mut mode = shmflg.to_file_mode(); + // The creator and user must have the read and write permission to the segment + let read_per = mode.contains(FileMode::S_IRUSR) + || mode.contains(FileMode::S_IRGRP) + || mode.contains(FileMode::S_IROTH); + let write_per = mode.contains(FileMode::S_IWUSR) + || mode.contains(FileMode::S_IWGRP) + || mode.contains(FileMode::S_IWOTH); + if !(read_per && write_per) { + return_errno!( + EINVAL, + "shared memory segement in occlum should have rw permission now" + ); + } + + let mut shm_segments = self.shm_segments.write().unwrap(); + let shmid = if key == IPC_PRIVATE { + let shmid = self.get_new_shmid()?; + let shm = ShmSegment::new(shmid, key, size, mode)?; + shm_segments.insert(shm.shmid, shm); + shmid + } else { + // Get the shm from key if the segment is not marked to be destroyed + let shm = shm_segments + .values() + .find(|&shm| !shm.is_destruction() && shm.key == key); + let shmid = if let Some(shm) = shm { + if shmflg.contains(ShmFlags::IPC_CREAT) && shmflg.contains(ShmFlags::IPC_EXCL) { + return_errno!( + EEXIST, + "the shared memory segment already exists for given key" + ); + } + // The size from user must be less than the actual size + if size > shm.shm_size() { + return_errno!(EINVAL, "the size from user is too large"); + } + // Check the permission + shm.check_perm()?; + shm.shmid + } else { + if !shmflg.contains(ShmFlags::IPC_CREAT) { + return_errno!(ENOENT, "no segment exists for given key"); + } + let shmid = self.get_new_shmid()?; + let shm = ShmSegment::new(shmid, key, size, mode)?; + shm_segments.insert(shm.shmid, shm); + shmid + }; + shmid + }; + Ok(shmid) + } + + pub fn do_shmat(&self, shmid: ShmId, addr: usize, shmflg: ShmFlags) -> Result { + debug!( + "do_shmat: shmid: {:?}, addr: {:?}, shmflg: {:?}", + shmid, addr, shmflg + ); + let pid = current!().process().pid(); + let mut shm_segments = self.shm_segments.write().unwrap(); + let shm = shm_segments.get_mut(&shmid); + let addr = if let Some(shm) = shm { + if addr != 0 && addr != shm.shm_start() { + return_errno!(EINVAL, "invalid addr"); + } + // Check the permission + shm.check_perm()?; + + shm.shm_nattach += 1; + shm.shm_atime = ShmManager::current_time(); + shm.shm_add_pid(&pid)?; + shm.shm_lpid = pid; + shm.shm_start() + } else { + return_errno!(EINVAL, "cannot find shm by shmid"); + }; + Ok(addr) + } + + pub fn do_shmdt(&self, addr: usize) -> Result<()> { + debug!("do_shmdt: addr: {:?}", addr); + let pid = current!().process().pid(); + let mut shm_segments = self.shm_segments.write().unwrap(); + let shm = shm_segments + .values_mut() + .find(|shm| shm.shm_start() == addr); + + if let Some(shm) = shm { + shm.shm_dtime = ShmManager::current_time(); + shm.shm_lpid = pid; + shm.shm_remove_pid(&pid)?; + shm.shm_nattach -= 1; + if shm.is_destruction() && shm.shm_nattach == 0 { + let shmid = shm.shmid; + self.free_shmid(&shmid); + shm_segments.remove(&shmid); + } + } else { + return_errno!(EINVAL, "cannot find shm by addr"); + } + Ok(()) + } + + pub fn do_shmctl(&self, shmid: ShmId, cmd: CmdId, buf: Option<&mut shmids_t>) -> Result<()> { + debug!( + "do_shmctl: shmid: {:?}, cmd: {:?}, buf: {:?}", + shmid, cmd, buf + ); + match cmd { + IPC_RMID => self.shmctl_rmshm(shmid), + IPC_STAT => self.shmctl_ipcstat(shmid, buf), + _ => return_errno!(EINVAL, "unimplemented cmd"), + } + } + + pub fn detach_shm_when_process_exit(&self, thread: &ThreadRef) { + let pid = &thread.process().pid(); + let mut shm_segments = self.shm_segments.write().unwrap(); + shm_segments.drain_filter(|shmid, shm| match shm.shm_remove_pid(&pid) { + // There exists a shm that has been attached to the current process + Ok(_) => { + shm.shm_nattach -= 1; + if shm.is_destruction() && shm.shm_nattach == 0 { + self.free_shmid(&shmid); + true + } else { + false + } + } + Err(_) => false, + }); + } + + pub fn clean_when_libos_exit(&self) { + let mut shm_segments = self.shm_segments.write().unwrap(); + for (_, shm) in shm_segments.drain() { + debug!("clean shm: {:?}", shm); + self.free_shmid(&shm.shmid); + } + } +} diff --git a/src/libos/src/ipc/syscalls.rs b/src/libos/src/ipc/syscalls.rs new file mode 100644 index 00000000..c8cfbb44 --- /dev/null +++ b/src/libos/src/ipc/syscalls.rs @@ -0,0 +1,36 @@ +use super::*; + +use util::mem_util::from_user; + +use super::shm::{shmids_t, CmdId, ShmFlags, ShmId, SHM_MANAGER}; + +pub fn do_shmget(key: key_t, size: size_t, shmflg: i32) -> Result { + let shmflg = + ShmFlags::from_bits(shmflg as u32).ok_or_else(|| errno!(EINVAL, "invalid flags"))?; + let shmid = SHM_MANAGER.do_shmget(key, size, shmflg)?; + Ok(shmid as isize) +} + +pub fn do_shmat(shmid: i32, shmaddr: usize, shmflg: i32) -> Result { + let shmflg = + ShmFlags::from_bits(shmflg as u32).ok_or_else(|| errno!(EINVAL, "invalid flags"))?; + let addr = SHM_MANAGER.do_shmat(shmid as ShmId, shmaddr, shmflg)?; + Ok(addr as isize) +} + +pub fn do_shmdt(shmaddr: usize) -> Result { + SHM_MANAGER.do_shmdt(shmaddr)?; + Ok(0) +} + +pub fn do_shmctl(shmid: i32, cmd: i32, buf_u: *mut shmids_t) -> Result { + let buf = if !buf_u.is_null() { + from_user::check_mut_ptr(buf_u)?; + let mut buf = unsafe { &mut *buf_u }; + Some(buf) + } else { + None + }; + SHM_MANAGER.do_shmctl(shmid as ShmId, cmd as CmdId, buf)?; + Ok(0) +} diff --git a/src/libos/src/lib.rs b/src/libos/src/lib.rs index 3e26fb76..9172efc9 100644 --- a/src/libos/src/lib.rs +++ b/src/libos/src/lib.rs @@ -81,6 +81,7 @@ mod events; mod exception; mod fs; mod interrupt; +mod ipc; mod misc; mod net; mod process; diff --git a/src/libos/src/process/do_exit.rs b/src/libos/src/process/do_exit.rs index aac82c84..4dee8c77 100644 --- a/src/libos/src/process/do_exit.rs +++ b/src/libos/src/process/do_exit.rs @@ -6,6 +6,7 @@ use super::do_vfork::{is_vforked_child_process, vfork_return_to_parent}; use super::pgrp::clean_pgrp_when_exit; use super::process::{Process, ProcessFilter}; use super::{table, ProcessRef, TermStatus, ThreadRef, ThreadStatus}; +use crate::ipc::SHM_MANAGER; use crate::prelude::*; use crate::signal::{KernelSignal, SigNum}; use crate::syscall::CpuContext; @@ -107,6 +108,7 @@ fn exit_process(thread: &ThreadRef, term_status: TermStatus) { let mut process_inner = process.inner(); // Clean used VM USER_SPACE_VM_MANAGER.free_chunks_when_exit(thread); + SHM_MANAGER.detach_shm_when_process_exit(thread); // The parent is the idle process if parent_inner.is_none() { diff --git a/src/libos/src/process/mod.rs b/src/libos/src/process/mod.rs index 03971582..be4dbc8d 100644 --- a/src/libos/src/process/mod.rs +++ b/src/libos/src/process/mod.rs @@ -68,6 +68,8 @@ pub mod task; pub type pid_t = u32; #[allow(non_camel_case_types)] pub type uid_t = u32; +#[allow(non_camel_case_types)] +pub type gid_t = u32; pub type ProcessRef = Arc; pub type ThreadRef = Arc; diff --git a/src/libos/src/syscall/mod.rs b/src/libos/src/syscall/mod.rs index 0ea8d0c1..83000e8b 100644 --- a/src/libos/src/syscall/mod.rs +++ b/src/libos/src/syscall/mod.rs @@ -35,6 +35,7 @@ use crate::fs::{ FileRef, HostStdioFds, Stat, Statfs, }; use crate::interrupt::{do_handle_interrupt, sgx_interrupt_info_t}; +use crate::ipc::{do_shmat, do_shmctl, do_shmdt, do_shmget, key_t, shmids_t}; use crate::misc::{resource_t, rlimit_t, sysinfo_t, utsname_t, RandFlags}; use crate::net::{ do_accept, do_accept4, do_bind, do_connect, do_epoll_create, do_epoll_create1, do_epoll_ctl, @@ -118,9 +119,9 @@ macro_rules! process_syscall_table_with_callback { (Msync = 26) => do_msync(addr: usize, size: usize, flags: u32), (Mincore = 27) => handle_unsupported(), (Madvise = 28) => handle_unsupported(), - (Shmget = 29) => handle_unsupported(), - (Shmat = 30) => handle_unsupported(), - (Shmctl = 31) => handle_unsupported(), + (Shmget = 29) => do_shmget(key: key_t, size: size_t, shmflg: i32), + (Shmat = 30) => do_shmat(shmid: i32, shmaddr: usize, shmflg: i32), + (Shmctl = 31) => do_shmctl(shmid: i32, cmd: i32, buf: *mut shmids_t), (Dup = 32) => do_dup(old_fd: FileDesc), (Dup2 = 33) => do_dup2(old_fd: FileDesc, new_fd: FileDesc), (Pause = 34) => handle_unsupported(), @@ -156,7 +157,7 @@ macro_rules! process_syscall_table_with_callback { (Semget = 64) => handle_unsupported(), (Semop = 65) => handle_unsupported(), (Semctl = 66) => handle_unsupported(), - (Shmdt = 67) => handle_unsupported(), + (Shmdt = 67) => do_shmdt(shmaddr: usize), (Msgget = 68) => handle_unsupported(), (Msgsnd = 69) => handle_unsupported(), (Msgrcv = 70) => handle_unsupported(), diff --git a/src/libos/src/vm/mod.rs b/src/libos/src/vm/mod.rs index a4119bf6..8752166d 100644 --- a/src/libos/src/vm/mod.rs +++ b/src/libos/src/vm/mod.rs @@ -75,10 +75,12 @@ mod vm_util; use self::vm_layout::VMLayout; +pub use self::chunk::ChunkRef; pub use self::process_vm::{MMapFlags, MRemapFlags, MSyncFlags, ProcessVM, ProcessVMBuilder}; pub use self::user_space_vm::USER_SPACE_VM_MANAGER; pub use self::vm_perms::VMPerms; pub use self::vm_range::VMRange; +pub use self::vm_util::{VMInitializer, VMMapOptionsBuilder}; pub fn do_mmap( addr: usize, diff --git a/src/libos/src/vm/process_vm.rs b/src/libos/src/vm/process_vm.rs index 6dbe4eca..5f7a7785 100644 --- a/src/libos/src/vm/process_vm.rs +++ b/src/libos/src/vm/process_vm.rs @@ -2,6 +2,7 @@ use super::*; use super::chunk::*; use super::config; +use super::ipc::SHM_MANAGER; use super::process::elf_file::{ElfFile, ProgramHeaderExt}; use super::user_space_vm::USER_SPACE_VM_MANAGER; use super::vm_area::VMArea; diff --git a/src/libos/src/vm/user_space_vm.rs b/src/libos/src/vm/user_space_vm.rs index 094de65f..054e452b 100644 --- a/src/libos/src/vm/user_space_vm.rs +++ b/src/libos/src/vm/user_space_vm.rs @@ -1,3 +1,4 @@ +use super::ipc::SHM_MANAGER; use super::*; use crate::ctor::dtor; use config::LIBOS_CONFIG; @@ -46,6 +47,7 @@ impl UserSpaceVMManager { // be called after the main function. Static variables are still safe to visit at this time. #[dtor] fn free_user_space() { + SHM_MANAGER.clean_when_libos_exit(); let range = USER_SPACE_VM_MANAGER.range(); assert!(USER_SPACE_VM_MANAGER.verified_clean_when_exit()); let addr = range.start() as *const c_void;