Kernel+LibPthread: Implement pthread_join()

It's now possible to block until another thread in the same process has
exited. We can also retrieve its exit value, which is whatever value it
passed to pthread_exit(). :^)
This commit is contained in:
Andreas Kling 2019-11-14 20:58:23 +01:00
parent c6a8b95643
commit 69efa3f630
Notes: sideshowbarker 2024-07-19 11:13:22 +09:00
9 changed files with 117 additions and 5 deletions

View file

@ -2862,9 +2862,9 @@ int Process::sys$create_thread(void* (*entry)(void*), void* argument)
return thread->tid();
}
void Process::sys$exit_thread(void* code)
void Process::sys$exit_thread(void* exit_value)
{
UNUSED_PARAM(code);
current->m_exit_value = exit_value;
cli();
if (&current->process().main_thread() == current) {
// FIXME: For POSIXy reasons, we should only sys$exit once *all* threads have exited.
@ -2877,6 +2877,47 @@ void Process::sys$exit_thread(void* code)
ASSERT_NOT_REACHED();
}
int Process::sys$join_thread(int tid, void** exit_value)
{
if (exit_value && !validate_write_typed(exit_value))
return -EFAULT;
Thread* thread = nullptr;
for_each_thread([&](auto& child_thread) {
if (child_thread.tid() == tid) {
thread = &child_thread;
return IterationDecision::Break;
}
return IterationDecision::Continue;
});
if (!thread)
return -ESRCH;
if (thread == current)
return -EDEADLK;
if (thread->m_joinee == current)
return -EDEADLK;
ASSERT(thread->m_joiner != current);
if (thread->m_joiner)
return -EINVAL;
// FIXME: EINVAL: 'thread' is not a joinable thread
// FIXME: pthread_join() should not be interruptable. Enforce this somehow?
auto result = current->block<Thread::JoinBlocker>(*thread);
(void)result;
// NOTE: 'thread' is very possibly deleted at this point. Clear it just to be safe.
thread = nullptr;
if (exit_value)
*exit_value = current->m_joinee_exit_value;
return 0;
}
int Process::sys$gettid()
{
return current->tid();

View file

@ -206,6 +206,7 @@ public:
int sys$restore_signal_mask(u32 mask);
int sys$create_thread(void* (*)(void*), void*);
void sys$exit_thread(void*);
int sys$join_thread(int tid, void** exit_value);
int sys$rename(const char* oldpath, const char* newpath);
int sys$systrace(pid_t);
int sys$mknod(const char* pathname, mode_t, dev_t);

View file

@ -68,6 +68,19 @@ void Scheduler::beep()
s_beep_timeout = g_uptime + 100;
}
Thread::JoinBlocker::JoinBlocker(Thread& joinee)
: m_joinee(joinee)
{
ASSERT(m_joinee.m_joiner == nullptr);
m_joinee.m_joiner = current;
current->m_joinee = &joinee;
}
bool Thread::JoinBlocker::should_unblock(Thread& joiner, time_t, long)
{
return !joiner.m_joinee;
}
Thread::FileDescriptionBlocker::FileDescriptionBlocker(const FileDescription& description)
: m_blocked_description(description)
{}

View file

@ -137,7 +137,8 @@ typedef u32 socklen_t;
__ENUMERATE_SYSCALL(getrandom) \
__ENUMERATE_SYSCALL(clock_gettime) \
__ENUMERATE_SYSCALL(clock_nanosleep) \
__ENUMERATE_SYSCALL(openat)
__ENUMERATE_SYSCALL(openat) \
__ENUMERATE_SYSCALL(join_thread)
namespace Syscall {

View file

@ -238,6 +238,14 @@ void Thread::finalize()
dbgprintf("Finalizing Thread %u in %s(%u)\n", tid(), m_process.name().characters(), pid());
set_state(Thread::State::Dead);
if (m_joiner) {
ASSERT(m_joiner->m_joinee == this);
m_joiner->m_joinee_exit_value = m_exit_value;
m_joiner->m_joinee = nullptr;
// NOTE: We clear the joiner pointer here as well, to be tidy.
m_joiner = nullptr;
}
if (m_dump_backtrace_on_finalization)
dbg() << backtrace_impl();

View file

@ -95,6 +95,16 @@ public:
friend class Thread;
};
class JoinBlocker final : public Blocker {
public:
explicit JoinBlocker(Thread& joinee);
virtual bool should_unblock(Thread&, time_t now_s, long us) override;
virtual const char* state_string() const override { return "Joining"; }
private:
Thread& m_joinee;
};
class FileDescriptionBlocker : public Blocker {
public:
const FileDescription& blocked_description() const;
@ -356,6 +366,13 @@ private:
VirtualAddress m_thread_specific_data;
SignalActionData m_signal_action_data[32];
Blocker* m_blocker { nullptr };
// FIXME: Some of these could probably live in the JoinBlocker object instead.
Thread* m_joiner { nullptr };
Thread* m_joinee { nullptr };
void* m_joinee_exit_value { nullptr };
void* m_exit_value { nullptr };
FPUState* m_fpu_state { nullptr };
State m_state { Invalid };
ThreadPriority m_priority { ThreadPriority::Normal };

View file

@ -1,10 +1,11 @@
#include <AK/StdLibExtras.h>
#include <Kernel/Syscall.h>
#include <pthread.h>
#include <unistd.h>
extern "C" {
int pthread_create(pthread_t* thread, pthread_attr_t* attributes, void *(*start_routine)(void*), void* argument_to_start_routine)
int pthread_create(pthread_t* thread, pthread_attr_t* attributes, void* (*start_routine)(void*), void* argument_to_start_routine)
{
if (!thread)
return -EINVAL;
@ -21,4 +22,9 @@ void pthread_exit(void* value_ptr)
exit_thread(value_ptr);
}
int pthread_join(pthread_t thread, void** exit_value_ptr)
{
int rc = syscall(SC_join_thread, thread, exit_value_ptr);
__RETURN_WITH_ERRNO(rc, rc, -1);
}
}

View file

@ -19,7 +19,7 @@ clean:
$(APPS) : % : %.o $(OBJS)
@echo "LD $@"
@$(LD) -o $@ $(LDFLAGS) $< -lc -lhtml -lgui -ldraw -laudio -lipc -lthread -lcore -lpcidb -lmarkdown
@$(LD) -o $@ $(LDFLAGS) $< -lc -lhtml -lgui -ldraw -laudio -lipc -lthread -lcore -lpcidb -lmarkdown -lpthread
%.o: %.cpp
@echo "CXX $<"

25
Userland/tt.cpp Normal file
View file

@ -0,0 +1,25 @@
#include <pthread.h>
#include <stdio.h>
int main(int, char**)
{
printf("Hello from the first thread!\n");
pthread_t thread_id;
int rc = pthread_create(&thread_id, nullptr, [](void*) -> void* {
printf("Hi there, from the second thread!\n");
pthread_exit((void*)0xDEADBEEF);
return nullptr;
}, nullptr);
if (rc < 0) {
perror("pthread_create");
return 1;
}
void* retval;
rc = pthread_join(thread_id, &retval);
if (rc < 0) {
perror("pthread_join");
return 1;
}
printf("Okay, joined and got retval=%p\n", retval);
return 0;
}