summaryrefslogtreecommitdiff
path: root/Kernel/Locking/Mutex.h
blob: d083fb3baab20796ea763d09964b78c3ac0dd21f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
/*
 * 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 == bit_cast<uintptr_t>(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 };

    // The address of one of the threads that hold this lock, or 0.
    // When locked in shared mode, this is stored on best effort basis: 0 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.
    uintptr_t m_holder { 0 };
    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<uintptr_t, 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 };
};

}