/* * Copyright (c) 2018-2020, Andreas Kling * Copyright (c) 2022, Idan Horowitz * * SPDX-License-Identifier: BSD-2-Clause */ #pragma once #include #include #include #include #include #include #include #include namespace Kernel { class Mutex { friend class Thread; AK_MAKE_NONCOPYABLE(Mutex); AK_MAKE_NONMOVABLE(Mutex); public: using Mode = LockMode; // FIXME: remove this after annihilating Process::m_big_lock enum class MutexBehavior { Regular, BigLock, }; Mutex(StringView name = {}, MutexBehavior behavior = MutexBehavior::Regular) : m_name(name) , m_behavior(behavior) { } ~Mutex() = default; void lock(Mode mode = Mode::Exclusive, LockLocation const& location = LockLocation::current()); void restore_exclusive_lock(u32, LockLocation const& location = LockLocation::current()); void unlock(); [[nodiscard]] Mode force_unlock_exclusive_if_locked(u32&); [[nodiscard]] bool is_locked() const { SpinlockLocker lock(m_lock); return m_mode != Mode::Unlocked; } [[nodiscard]] bool is_exclusively_locked_by_current_thread() const { SpinlockLocker lock(m_lock); VERIFY(m_mode != Mode::Shared); // This method should only be used on exclusively-held locks if (m_mode == Mode::Unlocked) return false; return m_holder == Thread::current(); } [[nodiscard]] StringView name() const { return m_name; } static StringView mode_to_string(Mode mode) { switch (mode) { case Mode::Unlocked: return "unlocked"sv; case Mode::Exclusive: return "exclusive"sv; case Mode::Shared: return "shared"sv; default: return "invalid"sv; } } private: using BlockedThreadList = IntrusiveList<&Thread::m_blocked_threads_list_node>; // FIXME: remove this after annihilating Process::m_big_lock using BigLockBlockedThreadList = IntrusiveList<&Thread::m_big_lock_blocked_threads_list_node>; void block(Thread&, Mode, SpinlockLocker&, u32); void unblock_waiters(Mode); StringView m_name; Mode m_mode { Mode::Unlocked }; // FIXME: remove this after annihilating Process::m_big_lock MutexBehavior m_behavior; // When locked exclusively, only the thread already holding the lock can // lock it again. When locked in shared mode, any thread can do that. u32 m_times_locked { 0 }; // One of the threads that hold this lock, or nullptr. When locked in shared // mode, this is stored on best effort basis: nullptr value does *not* mean // the lock is unlocked, it just means we don't know which threads hold it. // When locked exclusively, this is always the one thread that holds the // lock. RefPtr m_holder; size_t m_shared_holders { 0 }; struct BlockedThreadLists { BlockedThreadList exclusive; BlockedThreadList shared; // FIXME: remove this after annihilating Process::m_big_lock BigLockBlockedThreadList exclusive_big_lock; ALWAYS_INLINE BlockedThreadList& list_for_mode(Mode mode) { VERIFY(mode == Mode::Exclusive || mode == Mode::Shared); return mode == Mode::Exclusive ? exclusive : shared; } }; SpinlockProtected m_blocked_thread_lists; mutable Spinlock m_lock; #if LOCK_SHARED_UPGRADE_DEBUG HashMap m_shared_holders_map; #endif }; class MutexLocker { AK_MAKE_NONCOPYABLE(MutexLocker); public: ALWAYS_INLINE explicit MutexLocker() : m_lock(nullptr) , m_locked(false) { } ALWAYS_INLINE explicit MutexLocker(Mutex& l, Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current()) : m_lock(&l) { m_lock->lock(mode, location); } ALWAYS_INLINE ~MutexLocker() { if (m_locked) unlock(); } ALWAYS_INLINE void unlock() { VERIFY(m_lock); VERIFY(m_locked); m_locked = false; m_lock->unlock(); } ALWAYS_INLINE void attach_and_lock(Mutex& lock, Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current()) { VERIFY(!m_locked); m_lock = &lock; m_locked = true; m_lock->lock(mode, location); } ALWAYS_INLINE void lock(Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current()) { VERIFY(m_lock); VERIFY(!m_locked); m_locked = true; m_lock->lock(mode, location); } private: Mutex* m_lock; bool m_locked { true }; }; }