Jelajahi Sumber

feat: implement futex

zhuowei shao 7 bulan lalu
induk
melakukan
5dd3a57da4
1 mengubah file dengan 258 tambahan dan 0 penghapusan
  1. 258 0
      src/kernel/task/futex.rs

+ 258 - 0
src/kernel/task/futex.rs

@@ -0,0 +1,258 @@
+use core::pin::pin;
+
+use alloc::sync::Arc;
+use alloc::vec::Vec;
+use bitflags::bitflags;
+use eonix_sync::{LazyLock, Mutex, MutexGuard, WaitList};
+use intrusive_collections::{intrusive_adapter, KeyAdapter, RBTree, RBTreeAtomicLink};
+
+use crate::{
+    kernel::{
+        constants::{EAGAIN, EINVAL},
+        user::UserPointer,
+    },
+    prelude::KResult,
+};
+
+#[derive(PartialEq, Debug, Clone, Copy)]
+#[repr(u32)]
+#[expect(non_camel_case_types)]
+pub enum FutexOp {
+    FUTEX_WAIT = 0,
+    FUTEX_WAKE = 1,
+    FUTEX_FD = 2,
+    FUTEX_REQUEUE = 3,
+    FUTEX_CMP_REQUEUE = 4,
+    FUTEX_WAKE_OP = 5,
+    FUTEX_LOCK_PI = 6,
+    FUTEX_UNLOCK_PI = 7,
+    FUTEX_TRYLOCK_PI = 8,
+    FUTEX_WAIT_BITSET = 9,
+    FUTEX_WAKE_BITSET = 10,
+}
+
+impl TryFrom<u32> for FutexOp {
+    type Error = u32;
+
+    fn try_from(value: u32) -> Result<Self, Self::Error> {
+        match value {
+            0 => Ok(FutexOp::FUTEX_WAIT),
+            1 => Ok(FutexOp::FUTEX_WAKE),
+            2 => Ok(FutexOp::FUTEX_FD),
+            3 => Ok(FutexOp::FUTEX_REQUEUE),
+            4 => Ok(FutexOp::FUTEX_CMP_REQUEUE),
+            5 => Ok(FutexOp::FUTEX_WAKE_OP),
+            6 => Ok(FutexOp::FUTEX_LOCK_PI),
+            7 => Ok(FutexOp::FUTEX_UNLOCK_PI),
+            8 => Ok(FutexOp::FUTEX_TRYLOCK_PI),
+            9 => Ok(FutexOp::FUTEX_WAIT_BITSET),
+            10 => Ok(FutexOp::FUTEX_WAKE_BITSET),
+            _ => Err(EINVAL),
+        }
+    }
+}
+
+bitflags! {
+    pub struct FutexFlags : u32 {
+        const FUTEX_PRIVATE         = 128;
+        const FUTEX_CLOCK_REALTIME  = 256;
+    }
+}
+
+const FUTEX_OP_MASK: u32 = 0x0000_000F;
+const FUTEX_FLAGS_MASK: u32 = 0xFFFF_FFF0;
+
+pub fn parse_futexop(bits: u32) -> KResult<(FutexOp, FutexFlags)> {
+    let op = FutexOp::try_from(bits & FUTEX_OP_MASK)?;
+
+    let flags = {
+        let flags_bits = bits & FUTEX_FLAGS_MASK;
+        FutexFlags::from_bits(flags_bits).ok_or(EINVAL)
+    }?;
+
+    Ok((op, flags))
+}
+
+struct FutexTable {
+    futex_buckets: Vec<Mutex<FutexBucket>>,
+}
+
+struct FutexBucket {
+    futex_items: RBTree<FutexItemAdapter>,
+}
+
+intrusive_adapter!(
+    FutexItemAdapter = Arc<FutexItem>: FutexItem { link: RBTreeAtomicLink }
+);
+
+impl<'a> KeyAdapter<'a> for FutexItemAdapter {
+    type Key = FutexKey;
+    fn get_key(&self, item: &'a FutexItem) -> Self::Key {
+        item.key
+    }
+}
+
+struct FutexItem {
+    link: RBTreeAtomicLink,
+    key: FutexKey,
+    // // A list of waiters that are waiting on this futex.
+    wait_list: WaitList,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
+struct FutexKey {
+    addr: usize,
+    pid: Option<u32>,
+}
+
+impl FutexKey {
+    fn new(addr: usize, pid: Option<u32>) -> Self {
+        FutexKey { addr, pid }
+    }
+
+    fn hash(&self) -> usize {
+        (self.addr >> 2) + self.pid.unwrap_or(0) as usize
+    }
+}
+
+static FUTEX_TABLE: LazyLock<FutexTable> = LazyLock::new(FutexTable::new);
+
+const FUTEX_BUCKETS_NUM: usize = 100;
+
+impl FutexTable {
+    fn new() -> Self {
+        let futex_buckets = (0..FUTEX_BUCKETS_NUM)
+            .map(|_| Mutex::new(FutexBucket::new()))
+            .collect();
+        FutexTable { futex_buckets }
+    }
+
+    fn get_bucket(&self, key: &FutexKey) -> (usize, &Mutex<FutexBucket>) {
+        let idx = key.hash() % self.futex_buckets.len();
+        (idx, &self.futex_buckets[idx])
+    }
+}
+
+impl FutexBucket {
+    fn new() -> Self {
+        FutexBucket {
+            futex_items: RBTree::new(FutexItemAdapter::new()),
+        }
+    }
+
+    fn find(&mut self, key: FutexKey) -> Option<Arc<FutexItem>> {
+        let cursor = self.futex_items.find(&key);
+        cursor.clone_pointer()
+    }
+
+    fn find_or_insert(&mut self, key: FutexKey) -> Arc<FutexItem> {
+        if let Some(item) = self.find(key) {
+            return item;
+        }
+
+        let item = Arc::new(FutexItem {
+            link: RBTreeAtomicLink::new(),
+            key,
+            wait_list: WaitList::new(),
+        });
+
+        self.futex_items.insert(item.clone());
+
+        item
+    }
+}
+
+pub async fn futex_wait(
+    uaddr: usize,
+    pid: Option<u32>,
+    expected_val: u32,
+    _timeout: Option<u32>,
+) -> KResult<()> {
+    let futex_key = FutexKey::new(uaddr, pid);
+
+    let futex_item = {
+        let (_, futex_bucket_ref) = FUTEX_TABLE.get_bucket(&futex_key);
+        let mut futex_bucket = futex_bucket_ref.lock().await;
+
+        let val = UserPointer::new(uaddr as *const u32)?.read()?;
+
+        if val != expected_val {
+            return Err(EAGAIN);
+        }
+
+        futex_bucket.find_or_insert(futex_key)
+    };
+
+    let mut wait = pin!(futex_item.wait_list.prepare_to_wait());
+    wait.as_mut().add_to_wait_list();
+    wait.await;
+    Ok(())
+}
+
+pub async fn futex_wake(uaddr: usize, pid: Option<u32>, max_wake_count: u32) -> KResult<usize> {
+    let futex_key = FutexKey::new(uaddr, pid);
+
+    let (_, futex_bucket_ref) = FUTEX_TABLE.get_bucket(&futex_key);
+    let mut futex_bucket = futex_bucket_ref.lock().await;
+
+    if let Some(futex_item) = futex_bucket.find(futex_key) {
+        let mut count = 0;
+        loop {
+            let not_empty = futex_item.wait_list.notify_one();
+            if not_empty == false || count == max_wake_count {
+                break;
+            }
+            count += 1;
+        }
+        return Ok(count as usize);
+    }
+
+    Ok(0)
+}
+
+// should make sure that two keys in different buckets
+// make the lock order to avoid deadlocks
+async fn double_lock_bucket(
+    futex_key0: FutexKey,
+    futex_key1: FutexKey,
+) -> (
+    MutexGuard<'static, FutexBucket>,
+    MutexGuard<'static, FutexBucket>,
+) {
+    let (bucket_idx0, bucket_ref0) = FUTEX_TABLE.get_bucket(&futex_key0);
+    let (bucket_idx1, bucket_ref1) = FUTEX_TABLE.get_bucket(&futex_key1);
+
+    if bucket_idx0 < bucket_idx1 {
+        let bucket0 = bucket_ref0.lock().await;
+        let bucket1 = bucket_ref1.lock().await;
+        (bucket0, bucket1)
+    } else {
+        let bucket1 = bucket_ref1.lock().await;
+        let bucket0 = bucket_ref0.lock().await;
+        (bucket0, bucket1)
+    }
+}
+
+async fn futex_requeue(
+    uaddr: usize,
+    pid: Option<u32>,
+    wake_count: u32,
+    requeue_uaddr: usize,
+    requeue_count: u32,
+) -> KResult<usize> {
+    let futex_key = FutexKey::new(uaddr, pid);
+    let futex_requeue_key = FutexKey::new(requeue_uaddr, pid);
+
+    let (bucket_idx0, bucket_ref0) = FUTEX_TABLE.get_bucket(&futex_key);
+    let (bucket_idx1, bucket_ref1) = FUTEX_TABLE.get_bucket(&futex_requeue_key);
+
+    if bucket_idx0 == bucket_idx1 {
+        // If the keys are the same, we can just wake up the waiters.
+        return futex_wake(uaddr, pid, wake_count).await;
+    }
+
+    let (futex_bucket, futex_requeue_bucket) =
+        double_lock_bucket(futex_key, futex_requeue_key).await;
+
+    todo!()
+}