Kernel: Add a WaitQueue for Thread queueing/waking and use it for Lock

The kernel's Lock class now uses a proper wait queue internally instead
of just having everyone wake up regularly to try to acquire the lock.

We also keep the donation mechanism, so that whenever someone tries to
take the lock and fails, that thread donates the remainder of its
timeslice to the current lock holder.

After unlocking a Lock, the unlocking thread calls WaitQueue::wake_one,
which unblocks the next thread in queue.
This commit is contained in:
Andreas Kling 2019-12-01 11:57:20 +01:00
parent cada332e95
commit f067730f6b
8 changed files with 119 additions and 9 deletions

View file

@ -1,4 +1,5 @@
#include <Kernel/Lock.h>
#include <Kernel/Thread.h>
void Lock::lock()
{
@ -18,8 +19,8 @@ void Lock::lock()
return;
}
m_lock.store(false, AK::memory_order_release);
(void)current->donate_remaining_timeslice_and_block<Thread::WaitQueueBlocker>(m_holder, m_name, m_queue);
}
Scheduler::donate_to(m_holder, m_name);
}
}
@ -36,10 +37,12 @@ void Lock::unlock()
return;
}
m_holder = nullptr;
m_queue.wake_one();
m_lock.store(false, AK::memory_order_release);
return;
}
Scheduler::donate_to(m_holder, m_name);
// I don't know *who* is using "m_lock", so just yield.
Scheduler::yield();
}
}
@ -64,7 +67,10 @@ bool Lock::unlock_if_locked()
}
m_holder = nullptr;
m_lock.store(false, AK::memory_order_release);
m_queue.wake_one();
return true;
}
// I don't know *who* is using "m_lock", so just yield.
Scheduler::yield();
}
}

View file

@ -1,11 +1,12 @@
#pragma once
#include <AK/Assertions.h>
#include <AK/Types.h>
#include <AK/Atomic.h>
#include <AK/Types.h>
#include <Kernel/Arch/i386/CPU.h>
#include <Kernel/KSyms.h>
#include <Kernel/Scheduler.h>
#include <Kernel/WaitQueue.h>
class Thread;
extern Thread* current;
@ -29,6 +30,7 @@ private:
u32 m_level { 0 };
Thread* m_holder { nullptr };
const char* m_name { nullptr };
WaitQueue m_queue;
};
class Locker {
@ -46,7 +48,6 @@ private:
Lock& m_lock;
};
#define LOCKER(lock) Locker locker(lock)
template<typename T>

View file

@ -96,6 +96,7 @@ CXX_OBJS = \
VM/RangeAllocator.o \
VM/Region.o \
VM/VMObject.o \
WaitQueue.o \
init.o \
kprintf.o

View file

@ -82,9 +82,22 @@ bool Thread::JoinBlocker::should_unblock(Thread& joiner, time_t, long)
return !joiner.m_joinee;
}
Thread::WaitQueueBlocker::WaitQueueBlocker(WaitQueue& queue)
: m_queue(queue)
{
m_queue.enqueue(*current);
}
bool Thread::WaitQueueBlocker::should_unblock(Thread&, time_t, long)
{
// Someone else will have to unblock us by calling wake_one() or wake_all() on the queue.
return false;
}
Thread::FileDescriptionBlocker::FileDescriptionBlocker(const FileDescription& description)
: m_blocked_description(description)
{}
{
}
const FileDescription& Thread::FileDescriptionBlocker::blocked_description() const
{
@ -236,7 +249,8 @@ bool Thread::WaitBlocker::should_unblock(Thread& thread, time_t, long)
Thread::SemiPermanentBlocker::SemiPermanentBlocker(Reason reason)
: m_reason(reason)
{}
{
}
bool Thread::SemiPermanentBlocker::should_unblock(Thread&, time_t, long)
{

View file

@ -180,6 +180,14 @@ void Thread::yield_without_holding_big_lock()
process().big_lock().lock();
}
void Thread::donate_and_yield_without_holding_big_lock(Thread* beneficiary, const char* reason)
{
bool did_unlock = process().big_lock().unlock_if_locked();
Scheduler::donate_to(beneficiary, reason);
if (did_unlock)
process().big_lock().lock();
}
u64 Thread::sleep(u32 ticks)
{
ASSERT(state() == Thread::Running);

View file

@ -18,6 +18,7 @@ class FileDescription;
class Process;
class ProcessInspectionHandle;
class Region;
class WaitQueue;
enum class ShouldUnblockThread {
No = 0,
@ -110,6 +111,16 @@ public:
void*& m_joinee_exit_value;
};
class WaitQueueBlocker final : public Blocker {
public:
explicit WaitQueueBlocker(WaitQueue&);
virtual bool should_unblock(Thread&, time_t now_s, long us) override;
virtual const char* state_string() const override { return "WaitQueued"; }
private:
WaitQueue& m_queue;
};
class FileDescriptionBlocker : public Blocker {
public:
const FileDescription& blocked_description() const;
@ -257,7 +268,7 @@ public:
};
template<typename T, class... Args>
[[nodiscard]] BlockResult block(Args&&... args)
[[nodiscard]] BlockResult block_impl(Thread* beneficiary, const char* reason, Args&&... args)
{
// We should never be blocking a blocked (or otherwise non-active) thread.
ASSERT(state() == Thread::Running);
@ -270,7 +281,11 @@ public:
set_state(Thread::Blocked);
// Yield to the scheduler, and wait for us to resume unblocked.
yield_without_holding_big_lock();
if (beneficiary) {
donate_and_yield_without_holding_big_lock(beneficiary, reason);
} else {
yield_without_holding_big_lock();
}
// We should no longer be blocked once we woke up
ASSERT(state() != Thread::Blocked);
@ -282,7 +297,19 @@ public:
return BlockResult::InterruptedBySignal;
return BlockResult::WokeNormally;
};
}
template<typename T, class... Args>
[[nodiscard]] BlockResult block(Args&&... args)
{
return block_impl<T>(nullptr, nullptr, forward<Args>(args)...);
}
template<typename T, class... Args>
[[nodiscard]] BlockResult donate_remaining_timeslice_and_block(Thread* beneficiary, const char* reason, Args&&... args)
{
return block_impl<T>(beneficiary, reason, forward<Args>(args)...);
}
[[nodiscard]] BlockResult block_until(const char* state_string, Function<bool()>&& condition)
{
@ -398,6 +425,7 @@ private:
bool m_should_die { false };
void yield_without_holding_big_lock();
void donate_and_yield_without_holding_big_lock(Thread* beneficiary, const char* reason);
};
HashTable<Thread*>& thread_table();

34
Kernel/WaitQueue.cpp Normal file
View file

@ -0,0 +1,34 @@
#include <Kernel/Thread.h>
#include <Kernel/WaitQueue.h>
WaitQueue::WaitQueue()
{
}
WaitQueue::~WaitQueue()
{
}
void WaitQueue::enqueue(Thread& thread)
{
InterruptDisabler disabler;
m_threads.append(&thread);
}
void WaitQueue::wake_one()
{
InterruptDisabler disabler;
if (m_threads.is_empty())
return;
if (auto* thread = m_threads.take_first())
thread->unblock();
}
void WaitQueue::wake_all()
{
InterruptDisabler disabler;
if (m_threads.is_empty())
return;
while (!m_threads.is_empty())
m_threads.take_first()->unblock();
}

18
Kernel/WaitQueue.h Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <AK/SinglyLinkedList.h>
class Thread;
class WaitQueue {
public:
WaitQueue();
~WaitQueue();
void enqueue(Thread&);
void wake_one();
void wake_all();
private:
SinglyLinkedList<Thread*> m_threads;
};