Implement the syscall `shmat`

This commit is contained in:
Zhe Tang 2025-12-30 03:24:39 +00:00
parent f0a9804fd8
commit 07c64cf6a4
3 changed files with 229 additions and 10 deletions

View File

@ -30,4 +30,125 @@ bitflags! {
}
pub fn sys_shmat(shmid: i32, addr: u64, flags: i32, ctx: &Context) -> Result<SyscallReturn> {
debug!(
"[sys_shmat] shmid = {}, addr = {:#x}, flags = {}",
shmid, addr, flags
);
if shmid < 0 {
return_errno!(Errno::EINVAL);
}
let manager = SHM_OBJ_MANAGER.get().ok_or(Errno::EINVAL)?;
let manager_guard = manager.read();
let shm_obj = manager_guard
.get_shm_obj(shmid as u64)
.ok_or(Errno::EINVAL)?;
let shm_flags = ShmFlags::from_bits(flags as u32).ok_or(Errno::EINVAL)?;
let mut vm_perms = VmPerms::empty();
if shm_flags.contains(ShmFlags::RDONLY) {
if !shm_obj.mode()?.contains(InodeMode::S_IRUSR) {
return_errno!(Errno::EACCES);
}
vm_perms |= VmPerms::READ;
} else {
vm_perms |= VmPerms::READ | VmPerms::WRITE;
}
if shm_flags.contains(ShmFlags::EXEC) {
if !shm_obj.mode()?.contains(InodeMode::S_IXUSR) {
return_errno!(Errno::EACCES);
}
vm_perms |= VmPerms::EXEC;
}
let len = shm_obj.size().align_up(PAGE_SIZE);
let addr = if addr == 0 {
// If addr is 0, the system chooses the address
0_usize
} else if shm_flags.contains(ShmFlags::RND) {
// If RND is set, align down the address to SHMLBA
addr.align_down(SHMLBA as u64) as usize
} else if !addr.is_multiple_of(SHMLBA as u64) {
// If the address is not aligned with SHMLBA, return error
return_errno!(Errno::EINVAL);
} else {
// Otherwise, use the provided address
addr as usize
};
// Check bounds
if len == 0 {
return_errno_with_message!(Errno::EINVAL, "shmat len cannot be zero");
}
if len > isize::MAX as usize {
return_errno_with_message!(Errno::ENOMEM, "shmat len too large");
}
if addr > isize::MAX as usize - len {
return_errno_with_message!(Errno::ENOMEM, "shmat (addr + len) too large");
}
// Check fixed address bounds if specified
if addr != 0 {
let map_end = addr.checked_add(len).ok_or(Errno::EINVAL)?;
if !(is_userspace_vaddr(addr) && is_userspace_vaddr(map_end - 1)) {
return_errno_with_message!(Errno::EINVAL, "Invalid shmat fixed addr");
}
}
// On x86, `PROT_WRITE` implies `PROT_READ`.
#[cfg(target_arch = "x86_64")]
let vm_perms = if !vm_perms.contains(VmPerms::READ) && vm_perms.contains(VmPerms::WRITE) {
vm_perms | VmPerms::READ
} else {
vm_perms
};
let mut may_perms = VmPerms::empty();
if vm_perms.contains(VmPerms::READ) {
may_perms |= VmPerms::MAY_READ;
}
if vm_perms.contains(VmPerms::WRITE) {
may_perms |= VmPerms::MAY_WRITE;
}
if vm_perms.contains(VmPerms::EXEC) {
may_perms |= VmPerms::MAY_EXEC;
}
let user_space = ctx.user_space();
let root_vmar = user_space.vmar();
if addr != 0 && !shm_flags.contains(ShmFlags::REMAP) {
let map_end = addr.checked_add(len).ok_or(Errno::EINVAL)?;
if root_vmar.query(addr..map_end).iter().next().is_some() {
return_errno!(Errno::EINVAL);
}
}
// Mark shared memory as attached and map it
let attached_shm = shm_obj.set_attached(ctx.process.pid());
// Use the manager guard as a global coordinator to avoid deleting the
// shared memory object during mapping
drop(manager_guard);
let vm_map_options = {
let mut options = root_vmar.new_map(len, vm_perms)?.may_perms(may_perms);
if addr != 0 {
options = options
.offset(addr)
.can_overwrite(shm_flags.contains(ShmFlags::REMAP));
}
options = options.is_shared(true);
let vmo = shm_obj.vmo();
options = options
.vmo(vmo)
.attached_shm(attached_shm)
.vmo_offset(0)
.handle_page_faults_around();
options
};
// FIXME: Need to check whether current process has permission to access
// the shared memory object.
let map_addr = vm_map_options.build()?;
Ok(SyscallReturn::Return(map_addr as _))
}

View File

@ -21,9 +21,11 @@ use super::{RssType, interval_set::Interval, util::is_intersected, vmar_impls::R
use crate::{
fs::utils::Inode,
prelude::*,
process::Process,
thread::exception::PageFaultInfo,
vm::{
perms::VmPerms,
shared_mem::{AttachedShm, SHM_OBJ_MANAGER},
vmo::{CommitFlags, Vmo, VmoCommitError},
},
};
@ -114,16 +116,26 @@ impl VmMapping {
pub(super) fn new_fork(&self) -> VmMapping {
VmMapping {
map_size: self.map_size,
map_to_addr: self.map_to_addr,
mapped_mem: self.mapped_mem.dup(),
inode: self.inode.clone(),
..*self
is_shared: self.is_shared,
handle_page_faults_around: self.handle_page_faults_around,
perms: self.perms,
}
}
pub(super) fn clone_for_remap_at(&self, va: Vaddr) -> VmMapping {
let mut vm_mapping = self.new_fork();
vm_mapping.map_to_addr = va;
vm_mapping
VmMapping {
map_size: self.map_size,
map_to_addr: va,
mapped_mem: self.mapped_mem.dup(),
inode: self.inode.clone(),
is_shared: self.is_shared,
handle_page_faults_around: self.handle_page_faults_around,
perms: self.perms,
}
}
/// Returns the mapping's start address.
@ -146,11 +158,37 @@ impl VmMapping {
self.perms
}
/// Returns the shared memory attachment identifier if the mapping comes
/// from `shmat`.
pub fn attached_shm(&self) -> Option<AttachedShm> {
match &self.mapped_mem {
MappedMemory::Vmo(vmo) => vmo.attached_shm(),
_ => None,
}
}
/// Returns the inode of the file that backs the mapping.
pub fn inode(&self) -> Option<&Arc<dyn Inode>> {
self.inode.as_ref()
}
/// Returns the mapped VMO offset (in bytes) if this mapping is VMO-backed.
pub fn vmo_offset(&self) -> Option<usize> {
match &self.mapped_mem {
MappedMemory::Vmo(vmo) => Some(vmo.offset()),
_ => None,
}
}
/// Returns the underlying VMO size (in bytes) if this mapping is
/// VMO-backed.
pub fn vmo_size(&self) -> Option<usize> {
match &self.mapped_mem {
MappedMemory::Vmo(vmo) => Some(vmo.vmo().size()),
_ => None,
}
}
/// Returns a reference to the VMO if this mapping is VMO-backed.
pub(super) fn vmo(&self) -> Option<&MappedVmo> {
match &self.mapped_mem {
@ -716,9 +754,9 @@ pub(super) enum MappedMemory {
Anonymous,
/// Memory in a [`Vmo`].
///
/// These pages are associated with regular files that are backed by the page cache. On-demand
/// population is possible by enabling page fault handlers to allocate pages and read the page
/// content from the disk.
/// These pages are associated with regular files or shared memory objects that are backed by
/// kernel-managed memory such as the page cache. On-demand population is possible by enabling
/// handlers to allocate pages and read the page content from the disk or shared memory.
Vmo(MappedVmo),
/// Device memory.
///
@ -748,11 +786,18 @@ pub(super) struct MappedVmo {
/// Whether the VMO's writable mappings need to be tracked, and the
/// mapping is writable to the VMO.
is_writable_tracked: bool,
/// Shared memory attachment identifier if this mapping comes from `shmat`.
attached_shm: Option<AttachedShm>,
}
impl MappedVmo {
/// Creates a `MappedVmo` used for the mapping.
pub(super) fn new(vmo: Arc<Vmo>, offset: usize, is_writable_tracked: bool) -> Result<Self> {
pub(super) fn new(
vmo: Arc<Vmo>,
offset: usize,
is_writable_tracked: bool,
attached_shm: Option<AttachedShm>,
) -> Result<Self> {
if is_writable_tracked {
vmo.writable_mapping_status().map()?;
}
@ -761,6 +806,7 @@ impl MappedVmo {
vmo,
offset,
is_writable_tracked,
attached_shm,
})
}
@ -835,12 +881,32 @@ impl MappedVmo {
self.vmo.writable_mapping_status().increment();
}
if let Some(attached_shm) = self.attached_shm
&& let Some(manager) = SHM_OBJ_MANAGER.get()
{
let pid = Process::current().map(|p| p.pid()).unwrap_or(0);
let shm_obj = {
let guard = manager.read();
guard.get_shm_obj(attached_shm.shmid)
};
if let Some(shm_obj) = shm_obj {
shm_obj.inc_nattch_for_mapping(pid);
}
}
Self {
vmo: self.vmo.clone(),
offset,
is_writable_tracked: self.is_writable_tracked,
attached_shm: self.attached_shm,
}
}
/// Returns the shared memory attachment identifier if this mapping comes
/// from `shmat`.
pub fn attached_shm(&self) -> Option<AttachedShm> {
self.attached_shm
}
}
impl Drop for MappedVmo {
@ -848,6 +914,24 @@ impl Drop for MappedVmo {
if self.is_writable_tracked {
self.vmo.writable_mapping_status().decrement();
}
if let Some(attached_shm) = self.attached_shm
&& let Some(manager) = SHM_OBJ_MANAGER.get()
{
let pid = Process::current().map(|p| p.pid()).unwrap_or(0);
let shm_obj = {
let guard = manager.read();
guard.get_shm_obj(attached_shm.shmid)
};
let Some(shm_obj) = shm_obj else {
return;
};
shm_obj.set_detached(pid);
if shm_obj.should_be_deleted() {
let _ = manager.write().try_delete_shm_obj(attached_shm.shmid);
}
}
}
}
@ -880,6 +964,9 @@ fn try_merge(left: &VmMapping, right: &VmMapping) -> Option<VmMapping> {
let r_vmo = r_vmo_obj.vmo();
if Arc::ptr_eq(l_vmo, r_vmo) {
if l_vmo_obj.attached_shm() != r_vmo_obj.attached_shm() {
return None;
}
let is_offset_contiguous =
l_vmo_obj.offset() + left.map_size() == r_vmo_obj.offset();
if !is_offset_contiguous {

View File

@ -6,7 +6,7 @@ use super::{MappedMemory, MappedVmo, RssDelta, VmMapping, Vmar};
use crate::{
fs::{file_handle::Mappable, ramfs::memfd::MemfdInode},
prelude::*,
vm::{perms::VmPerms, vmo::Vmo},
vm::{perms::VmPerms, shared_mem::AttachedShm, vmo::Vmo},
};
impl Vmar {
@ -59,6 +59,8 @@ pub struct VmarMapOptions<'a> {
can_overwrite: bool,
// Whether the mapping is mapped with `MAP_SHARED`
is_shared: bool,
// Optional shared memory attachment identifier for `shmat` segments.
attached_shm: Option<AttachedShm>,
// Whether the mapping needs to handle surrounding pages when handling page fault.
handle_page_faults_around: bool,
}
@ -79,6 +81,7 @@ impl<'a> VmarMapOptions<'a> {
align: PAGE_SIZE,
can_overwrite: false,
is_shared: false,
attached_shm: None,
handle_page_faults_around: false,
}
}
@ -181,6 +184,12 @@ impl<'a> VmarMapOptions<'a> {
self
}
/// Sets the shared memory attachment identifier for `shmat` mappings.
pub fn attached_shm(mut self, attached_shm: AttachedShm) -> Self {
self.attached_shm = Some(attached_shm);
self
}
/// Sets the mapping to handle surrounding pages when handling page fault.
pub fn handle_page_faults_around(mut self) -> Self {
self.handle_page_faults_around = true;
@ -232,6 +241,7 @@ impl<'a> VmarMapOptions<'a> {
align,
can_overwrite,
is_shared,
attached_shm,
handle_page_faults_around,
} = self;
@ -300,6 +310,7 @@ impl<'a> VmarMapOptions<'a> {
vmo.unwrap(),
vmo_offset,
is_writable_tracked,
attached_shm,
)?);
(mapped_mem, Some(inode), None)
}
@ -307,7 +318,7 @@ impl<'a> VmarMapOptions<'a> {
}
} else if let Some(vmo) = vmo {
(
MappedMemory::Vmo(MappedVmo::new(vmo, vmo_offset, false)?),
MappedMemory::Vmo(MappedVmo::new(vmo, vmo_offset, false, attached_shm)?),
None,
None,
)