Переглянути джерело

Kernel+LibPthread: pthread_create handles pthread_attr_t

Add an initial implementation of pthread attributes for:
  * detach state (joinable, detached)
  * schedule params (just priority)
  * guard page size (as skeleton) (requires kernel support maybe?)
  * stack size and user-provided stack location (4 or 8 MB only, must be aligned)

Add some tests too, to the thread test program.

Also, LibC: Move pthread declarations to sys/types.h, where they belong.
Andrew Kaster 5 роки тому
батько
коміт
618aebdd8a

+ 29 - 4
Kernel/Process.cpp

@@ -2851,18 +2851,42 @@ int Process::thread_count() const
     return count;
 }
 
-int Process::sys$create_thread(void* (*entry)(void*), void* argument, void* stack)
+int Process::sys$create_thread(void* (*entry)(void*), void* argument, const Syscall::SC_create_thread_params* params)
 {
     if (!validate_read((const void*)entry, sizeof(void*)))
         return -EFAULT;
-    if (!MM.validate_user_stack(*this, VirtualAddress(((u32)stack) - 4)))
+
+    if (!validate_read_typed(params))
+        return -EFAULT;
+
+    u32 user_stack_address = reinterpret_cast<u32>(params->m_stack_location) + params->m_stack_size;
+
+    if (!MM.validate_user_stack(*this, VirtualAddress(user_stack_address - 4)))
         return -EFAULT;
+
+    // FIXME: return EAGAIN if Thread::all_threads().size() is greater than PTHREAD_THREADS_MAX
+
+    ThreadPriority requested_thread_priority = static_cast<ThreadPriority>(params->m_schedule_priority);
+    if (requested_thread_priority < ThreadPriority::First || requested_thread_priority > ThreadPriority::Last)
+        return -EINVAL;
+
+    if (requested_thread_priority != ThreadPriority::Normal && !is_superuser())
+        return -EPERM;
+
+    bool is_thread_joinable = (0 == params->m_detach_state);
+
+    // FIXME: Do something with guard pages?
+
     auto* thread = new Thread(*this);
+
+    thread->set_priority(requested_thread_priority);
+    thread->set_joinable(is_thread_joinable);
+
     auto& tss = thread->tss();
     tss.eip = (u32)entry;
     tss.eflags = 0x0202;
     tss.cr3 = page_directory().cr3();
-    tss.esp = (u32)stack;
+    tss.esp = user_stack_address;
 
     // NOTE: The stack needs to be 16-byte aligned.
     thread->push_value_on_stack((u32)argument);
@@ -2915,7 +2939,8 @@ int Process::sys$join_thread(int tid, void** exit_value)
     if (thread->m_joiner)
         return -EINVAL;
 
-    // FIXME: EINVAL: 'thread' is not a joinable thread
+    if (!thread->is_joinable())
+        return -EINVAL;
 
     void* joinee_exit_value = nullptr;
 

+ 1 - 1
Kernel/Process.h

@@ -203,7 +203,7 @@ public:
     int sys$sched_setparam(pid_t pid, const struct sched_param* param);
     int sys$sched_getparam(pid_t pid, struct sched_param* param);
     int sys$restore_signal_mask(u32 mask);
-    int sys$create_thread(void* (*)(void*), void* argument, void* stack);
+    int sys$create_thread(void* (*)(void*), void* argument, const Syscall::SC_create_thread_params*);
     void sys$exit_thread(void*);
     int sys$join_thread(int tid, void** exit_value);
     int sys$rename(const char* oldpath, const char* newpath);

+ 14 - 0
Kernel/Syscall.h

@@ -248,6 +248,20 @@ struct SC_setsockopt_params {
     socklen_t value_size;
 };
 
+struct SC_create_thread_params {
+    unsigned int m_detach_state = 0; // JOINABLE or DETACHED
+    int m_schedule_priority = 2;     // ThreadPriority::Normal
+    // FIXME: Implment guard pages in create_thread (unreadable pages at "overflow" end of stack)
+    // "If an implementation rounds up the value of guardsize to a multiple of {PAGESIZE},
+    // a call to pthread_attr_getguardsize() specifying attr shall store in the guardsize
+    // parameter the guard size specified by the previous pthread_attr_setguardsize() function call"
+    // ... ok, if you say so posix. Guess we get to lie to people about guard page size
+    unsigned int m_guard_page_size = 0;          // Rounded up to PAGE_SIZE
+    unsigned int m_reported_guard_page_size = 0; // The lie we tell callers
+    unsigned int m_stack_size = 4 * MB;          // Default PTHREAD_STACK_MIN
+    void* m_stack_location = nullptr;            // nullptr means any, o.w. process virtual address
+};
+
 void initialize();
 int sync();
 

+ 4 - 0
Kernel/Thread.h

@@ -63,6 +63,9 @@ public:
     void set_priority(ThreadPriority p) { m_priority = p; }
     ThreadPriority priority() const { return m_priority; }
 
+    void set_joinable(bool j) { m_is_joinable = j; }
+    bool is_joinable() const { return m_is_joinable; }
+
     Process& process() { return m_process; }
     const Process& process() const { return m_process; }
 
@@ -368,6 +371,7 @@ private:
     SignalActionData m_signal_action_data[32];
     Blocker* m_blocker { nullptr };
 
+    bool m_is_joinable { true };
     Thread* m_joiner { nullptr };
     Thread* m_joinee { nullptr };
     void* m_exit_value { nullptr };

+ 2 - 0
Libraries/LibC/limits.h

@@ -33,3 +33,5 @@
 #define MB_LEN_MAX 16
 
 #define ARG_MAX 65536
+
+#define PTHREAD_STACK_MIN 65536

+ 13 - 1
Libraries/LibC/sys/types.h

@@ -1,7 +1,7 @@
 #pragma once
 
-#include <stddef.h>
 #include <bits/stdint.h>
+#include <stddef.h>
 #include <sys/cdefs.h>
 
 __BEGIN_DECLS
@@ -60,4 +60,16 @@ struct utimbuf {
     time_t modtime;
 };
 
+typedef int pthread_t;
+typedef void* pthread_key_t;
+typedef void* pthread_once_t;
+typedef uint32_t pthread_mutex_t;
+typedef void* pthread_attr_t;
+typedef void* pthread_mutexattr_t;
+typedef void* pthread_cond_t;
+typedef void* pthread_rwlock_t;
+typedef void* pthread_rwlockatrr_t;
+typedef void* pthread_spinlock_t;
+typedef void* pthread_condattr_t;
+
 __END_DECLS

+ 260 - 6
Libraries/LibPthread/pthread.cpp

@@ -2,11 +2,22 @@
 #include <AK/Atomic.h>
 #include <AK/StdLibExtras.h>
 #include <Kernel/Syscall.h>
+#include <limits.h>
 #include <pthread.h>
 #include <stdio.h>
 #include <sys/mman.h>
 #include <unistd.h>
 
+#define PTHREAD_DEBUG
+
+namespace {
+using PthreadAttrImpl = Syscall::SC_create_thread_params;
+} // end anonymous namespace
+
+constexpr size_t required_stack_alignment = 4 * MB;
+constexpr size_t highest_reasonable_guard_size = 32 * PAGE_SIZE;
+constexpr size_t highest_reasonable_stack_size = 8 * MB; // That's the default in Ubuntu?
+
 extern "C" {
 
 static int create_thread(void* (*entry)(void*), void* argument, void* stack)
@@ -25,12 +36,33 @@ int pthread_create(pthread_t* thread, pthread_attr_t* attributes, void* (*start_
 {
     if (!thread)
         return -EINVAL;
-    UNUSED_PARAM(attributes);
-    const size_t stack_size = 4 * MB;
-    auto* stack = (u8*)mmap_with_name(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, 0, 0, "Thread stack");
-    if (!stack)
-        return -1;
-    int rc = create_thread(start_routine, argument_to_start_routine, stack + stack_size);
+
+    PthreadAttrImpl default_attributes {};
+    PthreadAttrImpl** arg_attributes = reinterpret_cast<PthreadAttrImpl**>(attributes);
+
+    PthreadAttrImpl* used_attributes = arg_attributes ? *arg_attributes : &default_attributes;
+
+    if (!used_attributes->m_stack_location) {
+        // adjust stack size, user might have called setstacksize, which has no restrictions on size/alignment
+        if (0 != (used_attributes->m_stack_size % required_stack_alignment))
+            used_attributes->m_stack_size += required_stack_alignment - (used_attributes->m_stack_size % required_stack_alignment);
+
+        used_attributes->m_stack_location = mmap_with_name(nullptr, used_attributes->m_stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, 0, 0, "Thread stack");
+        if (!used_attributes->m_stack_location)
+            return -1;
+    }
+
+#ifdef PTHREAD_DEBUG
+    printf("pthread_create: Creating thread with attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+        used_attributes,
+        (PTHREAD_CREATE_JOINABLE == used_attributes->m_detach_state) ? "joinable" : "detached",
+        used_attributes->m_schedule_priority,
+        used_attributes->m_guard_page_size,
+        used_attributes->m_stack_size,
+        used_attributes->m_stack_location);
+#endif
+
+    int rc = create_thread(start_routine, argument_to_start_routine, used_attributes);
     if (rc < 0)
         return rc;
     *thread = rc;
@@ -73,4 +105,226 @@ int pthread_mutex_unlock(pthread_mutex_t* mutex)
     atomic->store(false, AK::memory_order_release);
     return 0;
 }
+
+int pthread_attr_init(pthread_attr_t* attributes)
+{
+    auto* impl = new PthreadAttrImpl {};
+    *attributes = impl;
+
+#ifdef PTHREAD_DEBUG
+    printf("pthread_attr_init: New thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+        impl,
+        (PTHREAD_CREATE_JOINABLE == impl->m_detach_state) ? "joinable" : "detached",
+        impl->m_schedule_priority,
+        impl->m_guard_page_size,
+        impl->m_stack_size,
+        impl->m_stack_location);
+#endif
+
+    return 0;
+}
+
+int pthread_attr_destroy(pthread_attr_t* attributes)
+{
+    auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+    delete attributes_impl;
+    return 0;
+}
+
+int pthread_attr_getdetachstate(const pthread_attr_t* attributes, int* p_detach_state)
+{
+    auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+    if (!attributes_impl || !p_detach_state)
+        return EINVAL;
+
+    *p_detach_state = attributes_impl->m_detach_state;
+    return 0;
+}
+
+int pthread_attr_setdetachstate(pthread_attr_t* attributes, int detach_state)
+{
+    auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+    if (!attributes_impl)
+        return EINVAL;
+
+    if ((PTHREAD_CREATE_JOINABLE != detach_state) || PTHREAD_CREATE_DETACHED != detach_state)
+        return EINVAL;
+
+    attributes_impl->m_detach_state = detach_state;
+
+#ifdef PTHREAD_DEBUG
+    printf("pthread_attr_setdetachstate: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+        attributes_impl,
+        (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+        attributes_impl->m_schedule_priority,
+        attributes_impl->m_guard_page_size,
+        attributes_impl->m_stack_size,
+        attributes_impl->m_stack_location);
+#endif
+
+    return 0;
+}
+
+int pthread_attr_getguardsize(const pthread_attr_t* attributes, size_t* p_guard_size)
+{
+    auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+    if (!attributes_impl || !p_guard_size)
+        return EINVAL;
+
+    *p_guard_size = attributes_impl->m_reported_guard_page_size;
+    return 0;
+}
+
+int pthread_attr_setguardsize(pthread_attr_t* attributes, size_t guard_size)
+{
+    auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+    if (!attributes_impl)
+        return EINVAL;
+
+    size_t actual_guard_size = guard_size;
+    // round up
+    if (0 != (guard_size % PAGE_SIZE))
+        actual_guard_size += PAGE_SIZE - (guard_size % PAGE_SIZE);
+
+    // what is the user even doing?
+    if (actual_guard_size > highest_reasonable_guard_size) {
+        return EINVAL;
+    }
+
+    attributes_impl->m_guard_page_size = actual_guard_size;
+    attributes_impl->m_reported_guard_page_size = guard_size; // POSIX, why?
+
+#ifdef PTHREAD_DEBUG
+    printf("pthread_attr_setguardsize: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+        attributes_impl,
+        (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+        attributes_impl->m_schedule_priority,
+        attributes_impl->m_guard_page_size,
+        attributes_impl->m_stack_size,
+        attributes_impl->m_stack_location);
+#endif
+
+    return 0;
+}
+
+int pthread_attr_getschedparam(const pthread_attr_t* attributes, struct sched_param* p_sched_param)
+{
+    auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+    if (!attributes_impl || !p_sched_param)
+        return EINVAL;
+
+    p_sched_param->sched_priority = attributes_impl->m_schedule_priority;
+    return 0;
+}
+
+int pthread_attr_setschedparam(pthread_attr_t* attributes, const struct sched_param* p_sched_param)
+{
+    auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+    if (!attributes_impl || !p_sched_param)
+        return EINVAL;
+
+    // NOTE: This must track sched_get_priority_[min,max] and ThreadPriority enum in Thread.h
+    if (p_sched_param->sched_priority < 0 || p_sched_param->sched_priority > 3)
+        return ENOTSUP;
+
+    attributes_impl->m_schedule_priority = p_sched_param->sched_priority;
+
+#ifdef PTHREAD_DEBUG
+    printf("pthread_attr_setschedparam: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+        attributes_impl,
+        (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+        attributes_impl->m_schedule_priority,
+        attributes_impl->m_guard_page_size,
+        attributes_impl->m_stack_size,
+        attributes_impl->m_stack_location);
+#endif
+
+    return 0;
+}
+
+int pthread_attr_getstack(const pthread_attr_t* attributes, void** p_stack_ptr, size_t* p_stack_size)
+{
+    auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+    if (!attributes_impl || !p_stack_ptr || !p_stack_size)
+        return EINVAL;
+
+    *p_stack_ptr = attributes_impl->m_stack_location;
+    *p_stack_size = attributes_impl->m_stack_size;
+
+    return 0;
+}
+
+int pthread_attr_setstack(pthread_attr_t* attributes, void* p_stack, size_t stack_size)
+{
+    auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+    if (!attributes_impl || !p_stack)
+        return EINVAL;
+
+    // Check for required alignment on size
+    if (0 != (stack_size % required_stack_alignment))
+        return EINVAL;
+
+    // FIXME: Check for required alignment on pointer?
+
+    // FIXME: "[EACCES] The stack page(s) described by stackaddr and stacksize are not both readable and writable by the thread."
+    // Have to check that the whole range is mapped to this process/thread? Can we defer this to create_thread?
+
+    attributes_impl->m_stack_size = stack_size;
+    attributes_impl->m_stack_location = p_stack;
+
+#ifdef PTHREAD_DEBUG
+    printf("pthread_attr_setstack: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+        attributes_impl,
+        (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+        attributes_impl->m_schedule_priority,
+        attributes_impl->m_guard_page_size,
+        attributes_impl->m_stack_size,
+        attributes_impl->m_stack_location);
+#endif
+
+    return 0;
+}
+
+int pthread_attr_getstacksize(const pthread_attr_t* attributes, size_t* p_stack_size)
+{
+    auto* attributes_impl = *(reinterpret_cast<const PthreadAttrImpl* const*>(attributes));
+
+    if (!attributes_impl || !p_stack_size)
+        return EINVAL;
+
+    *p_stack_size = attributes_impl->m_stack_size;
+    return 0;
+}
+
+int pthread_attr_setstacksize(pthread_attr_t* attributes, size_t stack_size)
+{
+    auto* attributes_impl = *(reinterpret_cast<PthreadAttrImpl**>(attributes));
+
+    if (!attributes_impl)
+        return EINVAL;
+
+    if ((stack_size < PTHREAD_STACK_MIN) || stack_size > highest_reasonable_stack_size)
+        return EINVAL;
+
+    attributes_impl->m_stack_size = stack_size;
+
+#ifdef PTHREAD_DEBUG
+    printf("pthread_attr_setstacksize: Thread attributes at %p, detach state %s, priority %d, guard page size %d, stack size %d, stack location %p\n",
+        attributes_impl,
+        (PTHREAD_CREATE_JOINABLE == attributes_impl->m_detach_state) ? "joinable" : "detached",
+        attributes_impl->m_schedule_priority,
+        attributes_impl->m_guard_page_size,
+        attributes_impl->m_stack_size,
+        attributes_impl->m_stack_location);
+#endif
+
+    return 0;
+}
 }

+ 20 - 10
Libraries/LibPthread/pthread.h

@@ -3,19 +3,10 @@
 #include <sched.h>
 #include <stdint.h>
 #include <sys/cdefs.h>
+#include <sys/types.h>
 
 __BEGIN_DECLS
 
-typedef int pthread_t;
-typedef void* pthread_key_t;
-typedef void* pthread_once_t;
-typedef uint32_t pthread_mutex_t;
-typedef void* pthread_attr_t;
-typedef void* pthread_mutexattr_t;
-typedef void* pthread_cond_t;
-typedef void* pthread_spinlock_t;
-typedef void* pthread_condattr_t;
-
 int pthread_create(pthread_t*, pthread_attr_t*, void* (*)(void*), void*);
 void pthread_exit(void*);
 int pthread_kill(pthread_t, int);
@@ -27,9 +18,28 @@ int pthread_mutex_trylock(pthread_mutex_t* mutex);
 int pthread_mutex_unlock(pthread_mutex_t*);
 int pthread_mutex_init(pthread_mutex_t*, const pthread_mutexattr_t*);
 int pthread_mutex_destroy(pthread_mutex_t*);
+
 int pthread_attr_init(pthread_attr_t*);
 int pthread_attr_destroy(pthread_attr_t*);
 
+#define PTHREAD_CREATE_JOINABLE 0
+#define PTHREAD_CREATE_DETACHED 1
+
+int pthread_attr_getdetachstate(const pthread_attr_t*, int*);
+int pthread_attr_setdetachstate(pthread_attr_t*, int);
+
+int pthread_attr_getguardsize(const pthread_attr_t*, size_t*);
+int pthread_attr_setguardsize(pthread_attr_t*, size_t);
+
+int pthread_attr_getschedparam(const pthread_attr_t*, struct sched_param*);
+int pthread_attr_setschedparam(pthread_attr_t*, const struct sched_param*);
+
+int pthread_attr_getstack(const pthread_attr_t*, void**, size_t*);
+int pthread_attr_setstack(pthread_attr_t* attr, void*, size_t);
+
+int pthread_attr_getstacksize(const pthread_attr_t*, size_t*);
+int pthread_attr_setstacksize(pthread_attr_t*, size_t);
+
 int pthread_once(pthread_once_t*, void (*)(void));
 #define PTHREAD_ONCE_INIT 0
 void* pthread_getspecific(pthread_key_t key);

+ 253 - 1
Userland/tt.cpp

@@ -1,14 +1,28 @@
+#include <mman.h>
 #include <pthread.h>
 #include <stdio.h>
-#include <unistd.h>
 #include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
 
 static int mutex_test();
+static int detached_test();
+static int priority_test();
+static int stack_size_test();
+static int set_stack_test();
 
 int main(int argc, char** argv)
 {
     if (argc == 2 && *argv[1] == 'm')
         return mutex_test();
+    if (argc == 2 && *argv[1] == 'd')
+        return detached_test();
+    if (argc == 2 && *argv[1] == 'p')
+        return priority_test();
+    if (argc == 2 && *argv[1] == 's')
+        return stack_size_test();
+    if (argc == 2 && *argv[1] == 'x')
+        return set_stack_test();
 
     printf("Hello from the first thread!\n");
     pthread_t thread_id;
@@ -70,3 +84,241 @@ int mutex_test()
     }
     return 0;
 }
+
+int detached_test()
+{
+    pthread_attr_t attributes;
+    int rc = pthread_attr_init(&attributes);
+    if (rc != 0) {
+        printf("pthread_attr_setdetachstate: %s\n", strerror(rc));
+        return 1;
+    }
+
+    int detach_state = 99; // clearly invalid
+    rc = pthread_attr_getdetachstate(&attributes, &detach_state);
+    if (rc != 0) {
+        printf("pthread_attr_setdetachstate: %s\n", strerror(rc));
+        return 2;
+    }
+    printf("Default detach state: %s\n", detach_state == PTHREAD_CREATE_JOINABLE ? "joinable" : "detached");
+
+    detach_state = PTHREAD_CREATE_DETACHED;
+    rc = pthread_attr_setdetachstate(&attributes, detach_state);
+    if (rc != 0) {
+        printf("pthread_attr_setdetachstate: %s\n", strerror(rc));
+        return 3;
+    }
+    printf("Set detach state on new thread to detached\n");
+
+    pthread_t thread_id;
+    rc = pthread_create(
+        &thread_id, &attributes, [](void*) -> void* {
+            printf("I'm the secondary thread :^)\n");
+            sleep(1);
+            pthread_exit((void*)0xDEADBEEF);
+            return nullptr;
+        },
+        nullptr);
+    if (rc < 0) {
+        perror("pthread_create");
+        return 4;
+    }
+
+    void* ret_val;
+    errno = 0;
+    rc = pthread_join(thread_id, &ret_val);
+    if (rc < 0 && errno != EINVAL) {
+        perror("pthread_join");
+        return 5;
+    }
+    if (errno != EINVAL) {
+        printf("Expected EINVAL! Thread was joinable?\n");
+        return 6;
+    }
+
+    sleep(2);
+    printf("Thread was created detached. I sure hope it exited on its own.\n");
+
+    rc = pthread_attr_destroy(&attributes);
+    if (rc != 0) {
+        printf("pthread_attr_setdetachstate: %s\n", strerror(rc));
+        return 7;
+    }
+
+    return 0;
+}
+
+int priority_test()
+{
+    pthread_attr_t attributes;
+    int rc = pthread_attr_init(&attributes);
+    if (rc != 0) {
+        printf("pthread_attr_init: %s\n", strerror(rc));
+        return 1;
+    }
+
+    struct sched_param sched_params;
+    rc = pthread_attr_getschedparam(&attributes, &sched_params);
+    if (rc != 0) {
+        printf("pthread_attr_getschedparam: %s\n", strerror(rc));
+        return 2;
+    }
+    printf("Default priority: %d\n", sched_params.sched_priority);
+
+    sched_params.sched_priority = 3;
+    rc = pthread_attr_setschedparam(&attributes, &sched_params);
+    if (rc != 0) {
+        printf("pthread_attr_setschedparam: %s\n", strerror(rc));
+        return 3;
+    }
+    printf("Set thread priority to 3\n");
+
+    pthread_t thread_id;
+    rc = pthread_create(
+        &thread_id, &attributes, [](void*) -> void* {
+            printf("I'm the secondary thread :^)\n");
+            sleep(1);
+            pthread_exit((void*)0xDEADBEEF);
+            return nullptr;
+        },
+        nullptr);
+    if (rc < 0) {
+        perror("pthread_create");
+        return 4;
+    }
+
+    rc = pthread_join(thread_id, nullptr);
+    if (rc < 0) {
+        perror("pthread_join");
+        return 5;
+    }
+
+    rc = pthread_attr_destroy(&attributes);
+    if (rc != 0) {
+        printf("pthread_attr_destroy: %s\n", strerror(rc));
+        return 6;
+    }
+
+    return 0;
+}
+
+int stack_size_test()
+{
+    pthread_attr_t attributes;
+    int rc = pthread_attr_init(&attributes);
+    if (rc != 0) {
+        printf("pthread_attr_init: %s\n", strerror(rc));
+        return 1;
+    }
+
+    size_t stack_size;
+    rc = pthread_attr_getstacksize(&attributes, &stack_size);
+    if (rc != 0) {
+        printf("pthread_attr_getstacksize: %s\n", strerror(rc));
+        return 2;
+    }
+    printf("Default stack size: %zu\n", stack_size);
+
+    stack_size = 8 * 1024 * 1024;
+    rc = pthread_attr_setstacksize(&attributes, stack_size);
+    if (rc != 0) {
+        printf("pthread_attr_setstacksize: %s\n", strerror(rc));
+        return 3;
+    }
+    printf("Set thread stack size to 8 MB\n");
+
+    pthread_t thread_id;
+    rc = pthread_create(
+        &thread_id, &attributes, [](void*) -> void* {
+            printf("I'm the secondary thread :^)\n");
+            sleep(1);
+            pthread_exit((void*)0xDEADBEEF);
+            return nullptr;
+        },
+        nullptr);
+    if (rc < 0) {
+        perror("pthread_create");
+        return 4;
+    }
+
+    rc = pthread_join(thread_id, nullptr);
+    if (rc < 0) {
+        perror("pthread_join");
+        return 5;
+    }
+
+    rc = pthread_attr_destroy(&attributes);
+    if (rc != 0) {
+        printf("pthread_attr_destroy: %s\n", strerror(rc));
+        return 6;
+    }
+
+    return 0;
+}
+
+int set_stack_test()
+{
+    pthread_attr_t attributes;
+    int rc = pthread_attr_init(&attributes);
+    if (rc < 0) {
+        printf("pthread_attr_init: %s\n", strerror(rc));
+        return 1;
+    }
+
+    size_t stack_size = 8 * 1024 * 1024;
+    void* stack_addr = mmap_with_name(nullptr, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, 0, 0, "Cool stack");
+
+    if (!stack_addr) {
+        perror("mmap_with_name");
+        return -1;
+    }
+
+    rc = pthread_attr_setstack(&attributes, stack_addr, stack_size);
+    if (rc != 0) {
+        printf("pthread_attr_setstack: %s\n", strerror(rc));
+        return 2;
+    }
+    printf("Set thread stack to %p, size %zu\n", stack_addr, stack_size);
+
+    size_t stack_size_verify;
+    void* stack_addr_verify;
+
+    rc = pthread_attr_getstack(&attributes, &stack_addr_verify, &stack_size_verify);
+    if (rc != 0) {
+        printf("pthread_attr_getstack: %s\n", strerror(rc));
+        return 3;
+    }
+
+    if (stack_addr != stack_addr_verify || stack_size != stack_size_verify) {
+        printf("Stack address and size don't match! addr: %p %p, size: %zu %zu\n", stack_addr, stack_addr_verify, stack_size, stack_size_verify);
+        return 4;
+    }
+
+    pthread_t thread_id;
+    rc = pthread_create(
+        &thread_id, &attributes, [](void*) -> void* {
+            printf("I'm the secondary thread :^)\n");
+            sleep(1);
+            pthread_exit((void*)0xDEADBEEF);
+            return nullptr;
+        },
+        nullptr);
+    if (rc < 0) {
+        perror("pthread_create");
+        return 5;
+    }
+
+    rc = pthread_join(thread_id, nullptr);
+    if (rc < 0) {
+        perror("pthread_join");
+        return 6;
+    }
+
+    rc = pthread_attr_destroy(&attributes);
+    if (rc != 0) {
+        printf("pthread_attr_destroy: %s\n", strerror(rc));
+        return 7;
+    }
+
+    return 0;
+}