mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-11-22 23:50:19 +00:00
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:
parent
c6a8b95643
commit
69efa3f630
Notes:
sideshowbarker
2024-07-19 11:13:22 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/69efa3f6301
9 changed files with 117 additions and 5 deletions
|
@ -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 (¤t->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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{}
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
25
Userland/tt.cpp
Normal 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;
|
||||
}
|
Loading…
Reference in a new issue