diff options
author | Tom <tomut@yahoo.com> | 2020-11-29 16:05:27 -0700 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-11-30 13:17:02 +0100 |
commit | 046d6855f5e8a5039b319a47c3018a16d4c2f960 (patch) | |
tree | 9021179989bea74ec7d14a4c30d77eb2b2609f23 /Kernel/Thread.cpp | |
parent | 6a620562cc7298c2f591a06817ff560c9ef1deac (diff) | |
download | serenity-046d6855f5e8a5039b319a47c3018a16d4c2f960.zip |
Kernel: Move block condition evaluation out of the Scheduler
This makes the Scheduler a lot leaner by not having to evaluate
block conditions every time it is invoked. Instead evaluate them as
the states change, and unblock threads at that point.
This also implements some more waitid/waitpid/wait features and
behavior. For example, WUNTRACED and WNOWAIT are now supported. And
wait will now not return EINTR when SIGCHLD is delivered at the
same time.
Diffstat (limited to 'Kernel/Thread.cpp')
-rw-r--r-- | Kernel/Thread.cpp | 156 |
1 files changed, 104 insertions, 52 deletions
diff --git a/Kernel/Thread.cpp b/Kernel/Thread.cpp index 65e441b41b..e5e2db25e6 100644 --- a/Kernel/Thread.cpp +++ b/Kernel/Thread.cpp @@ -51,7 +51,7 @@ Thread::Thread(NonnullRefPtr<Process> process) : m_process(move(process)) , m_name(m_process->name()) { - if (m_process->m_thread_count.fetch_add(1, AK::MemoryOrder::memory_order_acq_rel) == 0) { + if (m_process->m_thread_count.fetch_add(1, AK::MemoryOrder::memory_order_relaxed) == 0) { // First thread gets TID == PID m_tid = m_process->pid().value(); } else { @@ -125,14 +125,27 @@ Thread::~Thread() ScopedSpinLock lock(g_scheduler_lock); g_scheduler_data->thread_list_for_state(m_state).remove(*this); } +} - ASSERT(!m_joiner); +void Thread::unblock_from_blocker(Blocker& blocker) +{ + ScopedSpinLock scheduler_lock(g_scheduler_lock); + ScopedSpinLock lock(m_lock); + if (m_blocker != &blocker) + return; + if (!is_stopped()) + unblock(); } -void Thread::unblock() +void Thread::unblock(u8 signal) { ASSERT(g_scheduler_lock.own_lock()); ASSERT(m_lock.own_lock()); + if (m_state != Thread::Blocked) + return; + ASSERT(m_blocker); + if (signal != 0) + m_blocker->set_interrupted_by_signal(signal); m_blocker = nullptr; if (Thread::current() == this) { set_state(Thread::Running); @@ -218,7 +231,7 @@ void Thread::die_if_needed() void Thread::exit(void* exit_value) { ASSERT(Thread::current() == this); - m_exit_value = exit_value; + m_join_condition.thread_did_exit(exit_value); set_should_die(); unlock_process_if_locked(); die_if_needed(); @@ -226,6 +239,7 @@ void Thread::exit(void* exit_value) void Thread::yield_without_holding_big_lock() { + ASSERT(!g_scheduler_lock.own_lock()); bool did_unlock = unlock_process_if_locked(); // NOTE: Even though we call Scheduler::yield here, unless we happen // to be outside of a critical section, the yield will be postponed @@ -310,11 +324,7 @@ void Thread::finalize() dbg() << "Finalizing thread " << *this; #endif set_state(Thread::State::Dead); - - if (auto* joiner = m_joiner.exchange(nullptr, AK::memory_order_acq_rel)) { - // Notify joiner that we exited - static_cast<JoinBlocker*>(joiner->m_blocker)->joinee_exited(m_exit_value); - } + m_join_condition.thread_finalizing(); } if (m_dump_backtrace_on_finalization) @@ -323,9 +333,12 @@ void Thread::finalize() kfree_aligned(m_fpu_state); auto thread_cnt_before = m_process->m_thread_count.fetch_sub(1, AK::MemoryOrder::memory_order_acq_rel); + ASSERT(thread_cnt_before != 0); if (thread_cnt_before == 1) - process().finalize(); + process().finalize(*this); + else + process().unblock_waiters(*this, Thread::WaitBlocker::UnblockFlags::Terminated); } void Thread::finalize_dying_threads() @@ -363,19 +376,26 @@ bool Thread::tick() bool Thread::has_pending_signal(u8 signal) const { ScopedSpinLock lock(g_scheduler_lock); - return m_pending_signals & (1 << (signal - 1)); + return pending_signals_for_state() & (1 << (signal - 1)); } u32 Thread::pending_signals() const { ScopedSpinLock lock(g_scheduler_lock); - return m_pending_signals; + return pending_signals_for_state(); +} + +u32 Thread::pending_signals_for_state() const +{ + ASSERT(g_scheduler_lock.own_lock()); + constexpr u32 stopped_signal_mask = (1 << (SIGCONT - 1)) | (1 << (SIGKILL - 1)) | (1 << (SIGTRAP - 1)); + return m_state != Stopped ? m_pending_signals : m_pending_signals & stopped_signal_mask; } void Thread::send_signal(u8 signal, [[maybe_unused]] Process* sender) { ASSERT(signal < 32); - ScopedSpinLock lock(g_scheduler_lock); + ScopedSpinLock scheduler_lock(g_scheduler_lock); // FIXME: Figure out what to do for masked signals. Should we also ignore them here? if (should_ignore_signal(signal)) { @@ -393,7 +413,15 @@ void Thread::send_signal(u8 signal, [[maybe_unused]] Process* sender) #endif m_pending_signals |= 1 << (signal - 1); - m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); + m_have_any_unmasked_pending_signals.store(pending_signals_for_state() & ~m_signal_mask, AK::memory_order_release); + + ScopedSpinLock lock(m_lock); + if (m_state == Stopped) { + if (pending_signals_for_state()) + resume_from_stopped(); + } else { + unblock(signal); + } } u32 Thread::update_signal_mask(u32 signal_mask) @@ -401,7 +429,7 @@ u32 Thread::update_signal_mask(u32 signal_mask) ScopedSpinLock lock(g_scheduler_lock); auto previous_signal_mask = m_signal_mask; m_signal_mask = signal_mask; - m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); + m_have_any_unmasked_pending_signals.store(pending_signals_for_state() & ~m_signal_mask, AK::memory_order_release); return previous_signal_mask; } @@ -419,7 +447,7 @@ u32 Thread::signal_mask_block(sigset_t signal_set, bool block) m_signal_mask &= ~signal_set; else m_signal_mask |= signal_set; - m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); + m_have_any_unmasked_pending_signals.store(pending_signals_for_state() & ~m_signal_mask, AK::memory_order_release); return previous_signal_mask; } @@ -440,15 +468,19 @@ void Thread::clear_signals() void Thread::send_urgent_signal_to_self(u8 signal) { ASSERT(Thread::current() == this); - ScopedSpinLock lock(g_scheduler_lock); - if (dispatch_signal(signal) == ShouldUnblockThread::No) - Scheduler::yield(); + DispatchSignalResult result; + { + ScopedSpinLock lock(g_scheduler_lock); + result = dispatch_signal(signal); + } + if (result == DispatchSignalResult::Yield) + yield_without_holding_big_lock(); } -ShouldUnblockThread Thread::dispatch_one_pending_signal() +DispatchSignalResult Thread::dispatch_one_pending_signal() { ASSERT(m_lock.own_lock()); - u32 signal_candidates = m_pending_signals & ~m_signal_mask; + u32 signal_candidates = pending_signals_for_state() & ~m_signal_mask; ASSERT(signal_candidates); u8 signal = 1; @@ -460,6 +492,17 @@ ShouldUnblockThread Thread::dispatch_one_pending_signal() return dispatch_signal(signal); } +DispatchSignalResult Thread::try_dispatch_one_pending_signal(u8 signal) +{ + ASSERT(signal != 0); + ScopedSpinLock scheduler_lock(g_scheduler_lock); + ScopedSpinLock lock(m_lock); + u32 signal_candidates = pending_signals_for_state() & ~m_signal_mask; + if (!(signal_candidates & (1 << (signal - 1)))) + return DispatchSignalResult::Continue; + return dispatch_signal(signal); +} + enum class DefaultSignalAction { Terminate, Ignore, @@ -542,22 +585,16 @@ void Thread::resume_from_stopped() ASSERT(is_stopped()); ASSERT(m_stop_state != State::Invalid); ASSERT(g_scheduler_lock.own_lock()); - set_state(m_stop_state); - m_stop_state = State::Invalid; - // make sure SemiPermanentBlocker is unblocked - if (m_state != Thread::Runnable && m_state != Thread::Running) { - ScopedSpinLock lock(m_lock); - if (m_blocker && m_blocker->is_reason_signal()) - unblock(); - } + set_state(m_stop_state != Blocked ? m_stop_state : Runnable); } -ShouldUnblockThread Thread::dispatch_signal(u8 signal) +DispatchSignalResult Thread::dispatch_signal(u8 signal) { ASSERT_INTERRUPTS_DISABLED(); ASSERT(g_scheduler_lock.own_lock()); ASSERT(signal > 0 && signal <= 32); ASSERT(process().is_user_process()); + ASSERT(this == Thread::current()); #ifdef SIGNAL_DEBUG klog() << "signal: dispatch signal " << signal << " to " << *this; @@ -568,7 +605,14 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) // at least in Runnable state and is_initialized() returns true, // which indicates that it is fully set up an we actually have // a register state on the stack that we can modify - return ShouldUnblockThread::No; + return DispatchSignalResult::Deferred; + } + + if (is_stopped() && signal != SIGCONT && signal != SIGKILL && signal != SIGTRAP) { +#ifdef SIGNAL_DEBUG + klog() << "signal: " << *this << " is stopped, will handle signal " << signal << " when resumed"; +#endif + return DispatchSignalResult::Deferred; } auto& action = m_signal_action_data[signal]; @@ -579,30 +623,35 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) m_pending_signals &= ~(1 << (signal - 1)); m_have_any_unmasked_pending_signals.store(m_pending_signals & ~m_signal_mask, AK::memory_order_release); - if (signal == SIGSTOP) { + auto* thread_tracer = tracer(); + if (signal == SIGSTOP || (thread_tracer && default_signal_action(signal) == DefaultSignalAction::DumpCore)) { if (!is_stopped()) { - m_stop_signal = SIGSTOP; +#ifdef SIGNAL_DEBUG + dbg() << "signal: signal " << signal << " stopping thread " << *this; +#endif + m_stop_signal = signal; set_state(State::Stopped); } - return ShouldUnblockThread::No; + return DispatchSignalResult::Yield; } if (signal == SIGCONT && is_stopped()) { +#ifdef SIGNAL_DEBUG + dbg() << "signal: SIGCONT resuming " << *this << " from stopped"; +#endif resume_from_stopped(); } else { - auto* thread_tracer = tracer(); if (thread_tracer != nullptr) { // when a thread is traced, it should be stopped whenever it receives a signal // the tracer is notified of this by using waitpid() // only "pending signals" from the tracer are sent to the tracee if (!thread_tracer->has_pending_signal(signal)) { m_stop_signal = signal; - // make sure SemiPermanentBlocker is unblocked - ScopedSpinLock lock(m_lock); - if (m_blocker && m_blocker->is_reason_signal()) - unblock(); +#ifdef SIGNAL_DEBUG + dbg() << "signal: " << signal << " stopping " << *this << " for tracer"; +#endif set_state(Stopped); - return ShouldUnblockThread::No; + return DispatchSignalResult::Yield; } thread_tracer->unset_signal(signal); } @@ -614,7 +663,7 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) case DefaultSignalAction::Stop: m_stop_signal = signal; set_state(Stopped); - return ShouldUnblockThread::No; + return DispatchSignalResult::Yield; case DefaultSignalAction::DumpCore: process().for_each_thread([](auto& thread) { thread.set_dump_backtrace_on_finalization(); @@ -623,11 +672,11 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) [[fallthrough]]; case DefaultSignalAction::Terminate: m_process->terminate_due_to_signal(signal); - return ShouldUnblockThread::No; + return DispatchSignalResult::Terminate; case DefaultSignalAction::Ignore: ASSERT_NOT_REACHED(); case DefaultSignalAction::Continue: - return ShouldUnblockThread::Yes; + return DispatchSignalResult::Continue; } ASSERT_NOT_REACHED(); } @@ -636,7 +685,7 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) #ifdef SIGNAL_DEBUG klog() << "signal: " << *this << " ignored signal " << signal; #endif - return ShouldUnblockThread::Yes; + return DispatchSignalResult::Continue; } ProcessPagingScope paging_scope(m_process); @@ -700,7 +749,7 @@ ShouldUnblockThread Thread::dispatch_signal(u8 signal) #ifdef SIGNAL_DEBUG klog() << "signal: Okay, " << *this << " {" << state_string() << "} has been primed with signal handler " << String::format("%w", m_tss.cs) << ":" << String::format("%x", m_tss.eip) << " to deliver " << signal; #endif - return ShouldUnblockThread::Yes; + return DispatchSignalResult::Continue; } void Thread::set_default_signal_dispositions() @@ -827,11 +876,6 @@ void Thread::set_state(State new_state) } } - if (new_state == Stopped) { - // We don't want to restore to Running state, only Runnable! - m_stop_state = m_state != Running ? m_state : Runnable; - } - m_state = new_state; #ifdef THREAD_DEBUG dbg() << "Set Thread " << *this << " state to " << state_string(); @@ -842,7 +886,16 @@ void Thread::set_state(State new_state) ASSERT(g_scheduler_data->has_thread(*this)); } - if (m_state == Dying) { + if (previous_state == Stopped) { + m_stop_state = State::Invalid; + process().unblock_waiters(*this, Thread::WaitBlocker::UnblockFlags::Continued); + } + + if (m_state == Stopped) { + // We don't want to restore to Running state, only Runnable! + m_stop_state = previous_state != Running ? m_state : Runnable; + process().unblock_waiters(*this, Thread::WaitBlocker::UnblockFlags::Stopped, m_stop_signal); + } else if (m_state == Dying) { ASSERT(previous_state != Queued); if (this != Thread::current() && is_finalizable()) { // Some other thread set this thread to Dying, notify the @@ -1027,7 +1080,6 @@ Thread::BlockResult Thread::wait_on(WaitQueue& queue, const char* reason, const } }); if (!timer) { - dbg() << "wait_on timed out before blocking"; // We timed out already, don't block // The API contract guarantees we return with interrupts enabled, // regardless of how we got called |