ladybird/Kernel/Locking/Mutex.h
kleines Filmröllchen a6a439243f Kernel: Turn lock ranks into template parameters
This step would ideally not have been necessary (increases amount of
refactoring and templates necessary, which in turn increases build
times), but it gives us a couple of nice properties:
- SpinlockProtected inside Singleton (a very common combination) can now
  obtain any lock rank just via the template parameter. It was not
  previously possible to do this with SingletonInstanceCreator magic.
- SpinlockProtected's lock rank is now mandatory; this is the majority
  of cases and allows us to see where we're still missing proper ranks.
- The type already informs us what lock rank a lock has, which aids code
  readability and (possibly, if gdb cooperates) lock mismatch debugging.
- The rank of a lock can no longer be dynamic, which is not something we
  wanted in the first place (or made use of). Locks randomly changing
  their rank sounds like a disaster waiting to happen.
- In some places, we might be able to statically check that locks are
  taken in the right order (with the right lock rank checking
  implementation) as rank information is fully statically known.

This refactoring even more exposes the fact that Mutex has no lock rank
capabilites, which is not fixed here.
2023-01-02 18:15:27 -05:00

184 lines
5 KiB
C++

/*
* Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
* Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <AK/Assertions.h>
#include <AK/Atomic.h>
#include <AK/HashMap.h>
#include <AK/Types.h>
#include <Kernel/Forward.h>
#include <Kernel/Locking/LockLocation.h>
#include <Kernel/Locking/LockMode.h>
#include <Kernel/WaitQueue.h>
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>;
// FIXME: Allow any lock rank.
void block(Thread&, Mode, SpinlockLocker<Spinlock<LockRank::None>>&, 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.
LockRefPtr<Thread> 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;
}
};
// FIXME: Use a specific lock rank passed by constructor.
SpinlockProtected<BlockedThreadLists, LockRank::None> m_blocked_thread_lists {};
// FIXME: See above.
mutable Spinlock<LockRank::None> m_lock {};
#if LOCK_SHARED_UPGRADE_DEBUG
HashMap<Thread*, u32> 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 };
};
}