Serenity Operating System
1/*
2 * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
3 * Copyright (c) 2022, Idan Horowitz <idan.horowitz@serenityos.org>
4 *
5 * SPDX-License-Identifier: BSD-2-Clause
6 */
7
8#pragma once
9
10#include <AK/Assertions.h>
11#include <AK/Atomic.h>
12#include <AK/HashMap.h>
13#include <AK/Types.h>
14#include <Kernel/Forward.h>
15#include <Kernel/Locking/LockLocation.h>
16#include <Kernel/Locking/LockMode.h>
17#include <Kernel/WaitQueue.h>
18
19namespace Kernel {
20
21class Mutex {
22 friend class Thread;
23
24 AK_MAKE_NONCOPYABLE(Mutex);
25 AK_MAKE_NONMOVABLE(Mutex);
26
27public:
28 using Mode = LockMode;
29
30 // FIXME: remove this after annihilating Process::m_big_lock
31 enum class MutexBehavior {
32 Regular,
33 BigLock,
34 };
35
36 Mutex(StringView name = {}, MutexBehavior behavior = MutexBehavior::Regular)
37 : m_name(name)
38 , m_behavior(behavior)
39 {
40 }
41 ~Mutex() = default;
42
43 void lock(Mode mode = Mode::Exclusive, LockLocation const& location = LockLocation::current());
44 void restore_exclusive_lock(u32, LockLocation const& location = LockLocation::current());
45
46 void unlock();
47 [[nodiscard]] Mode force_unlock_exclusive_if_locked(u32&);
48 [[nodiscard]] bool is_locked() const
49 {
50 SpinlockLocker lock(m_lock);
51 return m_mode != Mode::Unlocked;
52 }
53
54 [[nodiscard]] bool is_exclusively_locked_by_current_thread() const
55 {
56 SpinlockLocker lock(m_lock);
57 VERIFY(m_mode != Mode::Shared); // This method should only be used on exclusively-held locks
58 if (m_mode == Mode::Unlocked)
59 return false;
60 return m_holder == Thread::current();
61 }
62
63 [[nodiscard]] StringView name() const { return m_name; }
64
65 static StringView mode_to_string(Mode mode)
66 {
67 switch (mode) {
68 case Mode::Unlocked:
69 return "unlocked"sv;
70 case Mode::Exclusive:
71 return "exclusive"sv;
72 case Mode::Shared:
73 return "shared"sv;
74 default:
75 return "invalid"sv;
76 }
77 }
78
79private:
80 using BlockedThreadList = IntrusiveList<&Thread::m_blocked_threads_list_node>;
81
82 // FIXME: remove this after annihilating Process::m_big_lock
83 using BigLockBlockedThreadList = IntrusiveList<&Thread::m_big_lock_blocked_threads_list_node>;
84
85 // FIXME: Allow any lock rank.
86 void block(Thread&, Mode, SpinlockLocker<Spinlock<LockRank::None>>&, u32);
87 void unblock_waiters(Mode);
88
89 StringView m_name;
90 Mode m_mode { Mode::Unlocked };
91
92 // FIXME: remove this after annihilating Process::m_big_lock
93 MutexBehavior m_behavior;
94
95 // When locked exclusively, only the thread already holding the lock can
96 // lock it again. When locked in shared mode, any thread can do that.
97 u32 m_times_locked { 0 };
98
99 // One of the threads that hold this lock, or nullptr. When locked in shared
100 // mode, this is stored on best effort basis: nullptr value does *not* mean
101 // the lock is unlocked, it just means we don't know which threads hold it.
102 // When locked exclusively, this is always the one thread that holds the
103 // lock.
104 LockRefPtr<Thread> m_holder;
105 size_t m_shared_holders { 0 };
106
107 struct BlockedThreadLists {
108 BlockedThreadList exclusive;
109 BlockedThreadList shared;
110
111 // FIXME: remove this after annihilating Process::m_big_lock
112 BigLockBlockedThreadList exclusive_big_lock;
113
114 ALWAYS_INLINE BlockedThreadList& list_for_mode(Mode mode)
115 {
116 VERIFY(mode == Mode::Exclusive || mode == Mode::Shared);
117 return mode == Mode::Exclusive ? exclusive : shared;
118 }
119 };
120 // FIXME: Use a specific lock rank passed by constructor.
121 SpinlockProtected<BlockedThreadLists, LockRank::None> m_blocked_thread_lists {};
122
123 // FIXME: See above.
124 mutable Spinlock<LockRank::None> m_lock {};
125
126#if LOCK_SHARED_UPGRADE_DEBUG
127 HashMap<Thread*, u32> m_shared_holders_map;
128#endif
129};
130
131class MutexLocker {
132 AK_MAKE_NONCOPYABLE(MutexLocker);
133
134public:
135 ALWAYS_INLINE explicit MutexLocker()
136 : m_lock(nullptr)
137 , m_locked(false)
138 {
139 }
140
141 ALWAYS_INLINE explicit MutexLocker(Mutex& l, Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current())
142 : m_lock(&l)
143 {
144 m_lock->lock(mode, location);
145 }
146
147 ALWAYS_INLINE ~MutexLocker()
148 {
149 if (m_locked)
150 unlock();
151 }
152
153 ALWAYS_INLINE void unlock()
154 {
155 VERIFY(m_lock);
156 VERIFY(m_locked);
157 m_locked = false;
158 m_lock->unlock();
159 }
160
161 ALWAYS_INLINE void attach_and_lock(Mutex& lock, Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current())
162 {
163 VERIFY(!m_locked);
164 m_lock = &lock;
165 m_locked = true;
166
167 m_lock->lock(mode, location);
168 }
169
170 ALWAYS_INLINE void lock(Mutex::Mode mode = Mutex::Mode::Exclusive, LockLocation const& location = LockLocation::current())
171 {
172 VERIFY(m_lock);
173 VERIFY(!m_locked);
174 m_locked = true;
175
176 m_lock->lock(mode, location);
177 }
178
179private:
180 Mutex* m_lock;
181 bool m_locked { true };
182};
183
184}