asterinas/kernel/src/ipc/semaphore/system_v/sem_set.rs

365 lines
10 KiB
Rust

// SPDX-License-Identifier: MPL-2.0
use alloc::{collections::btree_map::BTreeMap, vec::Vec};
use core::sync::atomic::{AtomicU64, Ordering};
use aster_rights::ReadOp;
use id_alloc::IdAlloc;
use ostd::sync::{PreemptDisabled, RwLockReadGuard, RwLockWriteGuard};
use spin::Once;
use super::{
PermissionMode,
sem::{PendingOp, Status, update_pending_alter, wake_const_ops},
};
use crate::{
ipc::{IpcPermission, key_t, semaphore::system_v::sem::Semaphore},
prelude::*,
process::{Credentials, Pid},
time::clocks::RealTimeCoarseClock,
};
// The following constant values are derived from the default values in Linux.
/// Maximum number of semaphore sets.
pub const SEMMNI: usize = 32000;
/// Maximum number of semaphores per semaphore ID.
pub const SEMMSL: usize = 32000;
/// Maximum number of seaphores in all semaphore sets.
#[expect(dead_code)]
pub const SEMMNS: usize = SEMMNI * SEMMSL;
/// Maximum number of operations for semop.
pub const SEMOPM: usize = 500;
/// MAximum semaphore value.
pub const SEMVMX: i32 = 32767;
/// Maximum value that can be recorded for semaphore adjustment (SEM_UNDO).
#[expect(dead_code)]
pub const SEMAEM: i32 = SEMVMX;
#[derive(Debug)]
pub struct SemaphoreSet {
/// Number of semaphores in the set
nsems: usize,
/// Inner
inner: SpinLock<SemSetInner>,
/// Semaphore permission
permission: IpcPermission,
/// Creation time or last modification via `semctl`
sem_ctime: AtomicU64,
/// Last semop time.
sem_otime: AtomicU64,
}
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/ipcbuf.h
#[repr(C)]
#[derive(Debug, Copy, Clone, Default, Pod)]
pub struct IpcPerm {
key: u32,
uid: u32,
gid: u32,
cuid: u32,
cgid: u32,
mode: u16,
_pad1: u16,
seq: u16,
_pad2: u16,
_unused1: u64,
_unused2: u64,
}
// In Linux, most popular 64-bit architectures except x86_64 adopt the same
// layout of `semid_ds`.
// Reference: <https://elixir.bootlin.com/linux/v6.16.9/A/ident/semid64_ds>.
#[repr(C)]
#[derive(Debug, Copy, Clone, Default, Pod)]
pub struct SemidDs {
sem_perm: IpcPerm,
sem_otime: u64,
#[cfg(target_arch = "x86_64")]
_unused1: u64,
sem_ctime: u64,
#[cfg(target_arch = "x86_64")]
_unused2: u64,
sem_nsems: u64,
_unused3: u64,
_unused4: u64,
}
#[derive(Debug)]
pub(super) struct SemSetInner {
/// Semaphores
pub(super) sems: Box<[Semaphore]>,
/// Pending alter operations.
pub(super) pending_alter: LinkedList<PendingOp>,
/// Pending zeros operations.
pub(super) pending_const: LinkedList<PendingOp>,
}
impl SemSetInner {
pub(super) fn field_mut(
&mut self,
) -> (
&mut Box<[Semaphore]>,
&mut LinkedList<PendingOp>,
&mut LinkedList<PendingOp>,
) {
(
&mut self.sems,
&mut self.pending_alter,
&mut self.pending_const,
)
}
}
impl SemaphoreSet {
pub fn pending_const_count(&self, sem_num: u16) -> usize {
let inner = self.inner.lock();
let pending_const = &inner.pending_const;
let mut count = 0;
for i in pending_const.iter() {
for sem_buf in i.sops_iter() {
if sem_buf.sem_num() == sem_num {
count += 1;
}
}
}
count
}
pub fn pending_alter_count(&self, sem_num: u16) -> usize {
let inner = self.inner.lock();
let pending_alter = &inner.pending_alter;
let mut count = 0;
for i in pending_alter.iter() {
for sem_buf in i.sops_iter() {
if sem_buf.sem_num() == sem_num {
count += 1;
}
}
}
count
}
pub fn nsems(&self) -> usize {
self.nsems
}
pub fn setval(&self, sem_num: usize, val: i32, pid: Pid) -> Result<()> {
if !(0..SEMVMX).contains(&val) {
return_errno!(Errno::ERANGE);
}
let mut inner = self.inner();
let (sems, pending_alter, pending_const) = inner.field_mut();
let sem = sems.get_mut(sem_num).ok_or(Error::new(Errno::EINVAL))?;
sem.set_val(val);
sem.set_latest_modified_pid(pid);
let mut wake_queue = LinkedList::new();
if val == 0 {
wake_const_ops(sems, pending_const, &mut wake_queue);
} else {
update_pending_alter(sems, pending_alter, pending_const, &mut wake_queue);
}
for wake_op in wake_queue {
wake_op.set_status(Status::Normal);
if let Some(waker) = wake_op.waker() {
waker.wake_up();
}
}
self.update_ctime();
Ok(())
}
pub fn get<T>(&self, sem_num: usize, func: &dyn Fn(&Semaphore) -> T) -> Result<T> {
let inner = self.inner();
Ok(func(
inner.sems.get(sem_num).ok_or(Error::new(Errno::EINVAL))?,
))
}
pub fn permission(&self) -> &IpcPermission {
&self.permission
}
fn update_ctime(&self) {
self.sem_ctime.store(
RealTimeCoarseClock::get().read_time().as_secs(),
Ordering::Relaxed,
);
}
pub(super) fn update_otime(&self) {
self.sem_otime.store(
RealTimeCoarseClock::get().read_time().as_secs(),
Ordering::Relaxed,
);
}
pub(super) fn inner(&self) -> SpinLockGuard<'_, SemSetInner, PreemptDisabled> {
self.inner.lock()
}
fn new(key: key_t, nsems: usize, mode: u16, credentials: Credentials<ReadOp>) -> Result<Self> {
debug_assert!(nsems <= SEMMSL);
let mut sems = Vec::with_capacity(nsems);
for _ in 0..nsems {
sems.push(Semaphore::new(0));
}
let permission =
IpcPermission::new_sem_perm(key, credentials.euid(), credentials.egid(), mode);
Ok(Self {
nsems,
permission,
sem_ctime: AtomicU64::new(RealTimeCoarseClock::get().read_time().as_secs()),
sem_otime: AtomicU64::new(0),
inner: SpinLock::new(SemSetInner {
sems: sems.into_boxed_slice(),
pending_alter: LinkedList::new(),
pending_const: LinkedList::new(),
}),
})
}
pub fn semid_ds(&self) -> SemidDs {
let ipc_perm = IpcPerm {
key: self.permission.key() as u32,
uid: self.permission.uid().into(),
gid: self.permission.gid().into(),
cuid: self.permission.cuid().into(),
cgid: self.permission.cguid().into(),
mode: self.permission.mode(),
..IpcPerm::default()
};
SemidDs {
sem_perm: ipc_perm,
sem_otime: self.sem_otime.load(Ordering::Relaxed),
sem_ctime: self.sem_ctime.load(Ordering::Relaxed),
sem_nsems: self.nsems as u64,
..SemidDs::default()
}
}
}
impl Drop for SemaphoreSet {
fn drop(&mut self) {
let mut inner = self.inner();
let pending_alter = &mut inner.pending_alter;
for pending_alter in pending_alter.iter_mut() {
pending_alter.set_status(Status::Removed);
if let Some(waker) = pending_alter.waker() {
waker.wake_up();
}
}
pending_alter.clear();
let pending_const = &mut inner.pending_const;
for pending_const in pending_const.iter_mut() {
pending_const.set_status(Status::Removed);
if let Some(waker) = pending_const.waker() {
waker.wake_up();
}
}
pending_const.clear();
ID_ALLOCATOR
.get()
.unwrap()
.lock()
.free(self.permission.key() as usize);
}
}
pub fn create_sem_set_with_id(
id: key_t,
nsems: usize,
mode: u16,
credentials: Credentials<ReadOp>,
) -> Result<()> {
debug_assert!(nsems <= SEMMSL);
debug_assert!(id > 0);
if id as usize > SEMMNI {
return_errno_with_message!(Errno::ENOENT, "id larger than SEMMNI");
}
ID_ALLOCATOR
.get()
.unwrap()
.lock()
.alloc_specific(id as usize)
.ok_or(Error::new(Errno::EEXIST))?;
let mut sem_sets = SEMAPHORE_SETS.write();
sem_sets.insert(id, SemaphoreSet::new(id, nsems, mode, credentials)?);
Ok(())
}
/// Checks the semaphore. Return Ok if the semaphore exists and pass the check.
pub fn check_sem(id: key_t, nsems: Option<usize>, required_perm: PermissionMode) -> Result<()> {
debug_assert!(id > 0);
let sem_sets = SEMAPHORE_SETS.read();
let sem_set = sem_sets.get(&id).ok_or(Error::new(Errno::ENOENT))?;
if let Some(nsems) = nsems {
debug_assert!(nsems <= SEMMSL);
if nsems > sem_set.nsems() {
return_errno!(Errno::EINVAL);
}
}
if !required_perm.is_empty() {
// TODO: Support permission check
warn!("Semaphore doesn't support permission check now");
}
Ok(())
}
pub fn create_sem_set(nsems: usize, mode: u16, credentials: Credentials<ReadOp>) -> Result<key_t> {
debug_assert!(nsems <= SEMMSL);
let id = ID_ALLOCATOR
.get()
.unwrap()
.lock()
.alloc()
.ok_or(Error::new(Errno::ENOSPC))? as i32;
let mut sem_sets = SEMAPHORE_SETS.write();
sem_sets.insert(id, SemaphoreSet::new(id, nsems, mode, credentials)?);
Ok(id)
}
pub fn sem_sets<'a>() -> RwLockReadGuard<'a, BTreeMap<key_t, SemaphoreSet>, PreemptDisabled> {
SEMAPHORE_SETS.read()
}
pub fn sem_sets_mut<'a>() -> RwLockWriteGuard<'a, BTreeMap<key_t, SemaphoreSet>, PreemptDisabled> {
SEMAPHORE_SETS.write()
}
static ID_ALLOCATOR: Once<SpinLock<IdAlloc>> = Once::new();
/// Semaphore sets in system
static SEMAPHORE_SETS: RwLock<BTreeMap<key_t, SemaphoreSet>> = RwLock::new(BTreeMap::new());
pub(super) fn init_in_first_kthread() {
ID_ALLOCATOR.call_once(|| {
let mut id_alloc = IdAlloc::with_capacity(SEMMNI + 1);
// Remove the first index 0
id_alloc.alloc();
SpinLock::new(id_alloc)
});
}