kospthread: Initial implementation

Details are described in README.md

Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
This commit is contained in:
2026-01-07 04:13:10 +03:00
committed by Max Logaev
parent c5874360c9
commit 6123269c29
7 changed files with 519 additions and 4 deletions

View File

@@ -8,7 +8,7 @@ add_custom_target(newlib-headers
${CMAKE_COMMAND} -E copy_directory
"${NEWLIB_SRC_DIR}/newlib/libc/include"
"${KOS_SDK_DIR}/i586-kolibrios/include"
COMMENT "Copying all Newlib headers to ${KOS_SDK_DIR}}/include"
COMMENT "Copying all Newlib headers to ${KOS_SDK_DIR}/include"
)
# Newlib
@@ -25,3 +25,14 @@ ExternalProject_Add(
BUILD_ALWAYS TRUE
DEPENDS gcc
)
# kospthread
ExternalProject_Add(
kospthread
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/kospthread
CMAKE_ARGS
-DCMAKE_TOOLCHAIN_FILE=${KOS_TOOLCHAIN_FILE}
-DCMAKE_INSTALL_PREFIX=${KOS_SDK_DIR}/i586-kolibrios
BUILD_ALWAYS TRUE
DEPENDS gcc
)

View File

@@ -0,0 +1,11 @@
cmake_minimum_required(VERSION 3.31)
project(kospthread LANGUAGES C ASM)
# libpthread
add_library(pthread
src/threads.c
src/exit.S
)
install(TARGETS pthread DESTINATION lib)

View File

@@ -0,0 +1,57 @@
# kospthread
This is a minimal implementation of POSIX threads [IEEE Std 1003.1-2024](https://pubs.opengroup.org/onlinepubs/9799919799/) for KolibriOS.
## Main goal
Implement the minimum set of functions for `libgcc` and `libstdc++` to work:
## Status
Due to the way threads work in **KolibriOS**, the current **pthread** only implements the ability to spawn threads from the main thread.
An attempt to create a child thread will result in an `EAGAIN` error.
Fully implemented:
- `pthread_equal()`
- `pthread_self()`
Implemented with restrictions:
- `pthread_create()`: attributes are not supported; only main thread.
- `pthread_join()`: only main thread;
Not implemented:
- `pthread_once()`
- `pthread_detach()`
- `sched_yield()`
- `pthread_mutex_lock()`
- `pthread_mutex_trylock()`
- `pthread_mutex_unlock()`
- `pthread_mutex_init()`
- `pthread_mutex_destroy()`
- `pthread_mutexattr_init()`
- `pthread_mutexattr_settype()`
- `pthread_mutexattr_destroy()`
- `pthread_cond_init()`
- `pthread_cond_broadcast()`
- `pthread_cond_signal()`
- `pthread_cond_wait()`
- `pthread_cond_timedwait()`
- `pthread_cond_destroy()`
- `pthread_key_create()`
- `pthread_key_delete()`
- `pthread_getspecific()`
- `pthread_setspecific()`
## Build
```sh
cmake -B build -DCMAKE_TOOLCHAIN_FILE=${KOS_TOOLCHAIN_FILE}
cmake --build build
```

View File

@@ -0,0 +1,22 @@
/*
* SPDX-License-Identifier: GPL-2.0-only
* Copyright (C) 2026 KolibriOS team
* Author: Maxim Logaev <maxlogaev@proton.me>
*/
#include <kolibrios/tls.h>
#include <kolibrios/syscall.h>
.section .text
.global ___pthread_child_free_stk_and_exit
___pthread_child_free_stk_and_exit:
movl %fs:KOS_TLS_OFF_PTHREAD_SELF, %eax
movl (%eax), %ecx
movl $SF_SYS_MISC, %eax
movl $SSF_MEM_FREE, %ebx
int $0x40
orl $SF_TERMINATE_PROCESS, %eax
int $0x40

View File

@@ -0,0 +1,293 @@
/*
* SPDX-License-Identifier: GPL-2.0-only
*
* Basic POSIX thread wrappers for KolibriOS.
*
* Copyright (C) 2026 KolibriOS team
* Author: Maxim Logaev <maxlogaev@proton.me>
*/
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
#include <sys/errno.h>
#include <kolibrios/app.h>
#include <kolibrios/limits.h>
#include <kolibrios/syscall.h>
#include <kolibrios/tls.h>
extern __dead2 void __pthread_child_free_stk_and_exit (void);
#define CHILDREN_MAX (KOS_THREADS_MAX - 1) // -1 self
#define PUSH_PTR(sp, ptr) \
do \
{ \
sp -= sizeof (void *); \
*(void **)sp = ptr; \
} \
while (0)
#define IS_VALID_CHILD_ID(id) (id < CHILDREN_MAX)
#define MAIN_THREAD_ID -1
#define THREAD_STATUS_NORMAL_EXIT 1
struct pthread_self
{
void *stack_lo; // Do not change the order! (see exit.S)
unsigned long status;
pthread_t id;
void *ret;
};
struct pthread_child
{
unsigned long kos_tid;
struct pthread_self self;
};
static bool inited;
static pthread_t next_child_id;
static struct pthread_child children[CHILDREN_MAX];
/* --------------------------- Helper functions ---------------------------- */
static bool
free_stack_if_dead (pthread_t id)
{
/*
* The child must free his stack upon correct exit.
* If there is a crash in the child, then the main thread will have to do it.
*/
if (children[id].self.stack_lo != (void *)-1
&& children[id].self.status != THREAD_STATUS_NORMAL_EXIT)
{
_ksys (SF_SYS_MISC, SSF_MEM_FREE,
(unsigned long)children[id].self.stack_lo);
children[id].self.stack_lo = (void *)-1;
return true;
}
return false;
}
static pthread_t
alloc_child (void)
{
// First pass: start with the next one
for (pthread_t id = next_child_id; id < CHILDREN_MAX; id++)
{
if (children[id].kos_tid == -1)
{
next_child_id = id + 1;
return id;
}
}
// Second pass: start from the beginning to the last
for (pthread_t id = 0; id < next_child_id; id++)
{
if (children[id].kos_tid == -1)
{
next_child_id = id + 1;
return id;
}
}
// Third pass: search for the dead
for (pthread_t id = 0; id < CHILDREN_MAX; id++)
{
if (!_ksys (SF_SYSTEM, SSF_GET_THREAD_SLOT, children[id].kos_tid))
{
free_stack_if_dead (id);
next_child_id = id + 1;
return id;
}
}
return (pthread_t)-1;
}
static inline void
clean_child (pthread_t id)
{
__builtin_memset (&children[id], -1, sizeof (children[id]));
}
static inline bool
is_child (void)
{
return __kos_tls_get (KOS_TLS_OFF_PTHREAD_SELF) != NULL;
}
static inline void
__pthread_main_init_once (void)
{
if (!inited)
{
next_child_id = 0;
__builtin_memset (children, -1, sizeof (children));
inited = true;
}
}
static __dead2 void
__pthread_main_exit (void)
{
if (inited)
{
for (pthread_t id = 0; id < CHILDREN_MAX; id++)
{
if (children[id].kos_tid != -1)
{
_ksys (SF_SYSTEM, SSF_TERMINATE_THREAD_ID, children[id].kos_tid);
}
}
}
/* Freeing the children stack is pointless when the main is terminated */
_ksys (SF_TERMINATE_PROCESS);
__unreachable ();
}
static __dead2 void
__pthread_child_exit (struct pthread_self *self, void *ret)
{
self->status = THREAD_STATUS_NORMAL_EXIT;
self->ret = ret;
__pthread_child_free_stk_and_exit ();
__unreachable ();
}
static __dead2 void
__thread_func (void *self, void *(*pthread_fn) (void *), void *arg)
{
__kos_tls_set (KOS_TLS_OFF_PTHREAD_SELF, self);
void *ret = pthread_fn (arg);
__pthread_child_exit (self, ret);
__unreachable ();
}
/* ----------------------------- Main functions ---------------------------- */
void __dead2
pthread_exit (void *ret)
{
void *self = __kos_tls_get (KOS_TLS_OFF_PTHREAD_SELF);
if (self)
{
__pthread_child_exit (__kos_tls_get (KOS_TLS_OFF_PTHREAD_SELF), ret);
__unreachable ();
}
__pthread_main_exit ();
__unreachable ();
}
int
pthread_create (pthread_t *id, const pthread_attr_t *attr,
void *(*func) (void *), void *arg)
{
if (attr)
return EINVAL;
__pthread_main_init_once ();
if (is_child ())
return EAGAIN;
pthread_t child_id = alloc_child ();
if (!IS_VALID_CHILD_ID (child_id))
return EAGAIN;
// Allocate memory for new stack. Is freed by the new thread itself.
char *stack_lo
= (char *)_ksys (SF_SYS_MISC, SSF_MEM_ALLOC, __kosapp_stack_size);
if (!stack_lo)
return EAGAIN;
// Prepare a new stack:
char *stack_hi = stack_lo + __kosapp_stack_size;
children[child_id].self.id = child_id;
children[child_id].self.stack_lo = stack_lo;
// Push arguments
PUSH_PTR (stack_hi, arg);
PUSH_PTR (stack_hi, func);
PUSH_PTR (stack_hi, &children[child_id].self);
// Push unreachable return address
PUSH_PTR (stack_hi, (void *)-1);
// Create thread:
unsigned long tid = _ksys (SF_THREAD_CTRL, SSF_CREATE_THREAD,
(intptr_t)__thread_func, (intptr_t)stack_hi);
if (tid == -1)
{
_ksys (SF_SYS_MISC, SSF_MEM_FREE, (intptr_t)stack_lo);
return EAGAIN;
}
children[child_id].kos_tid = tid;
*id = child_id;
return 0;
}
int
pthread_join (pthread_t id, void **ret)
{
if (is_child () || id == MAIN_THREAD_ID)
return EDEADLK;
if (!IS_VALID_CHILD_ID (id))
return ESRCH;
while (_ksys (SF_SYSTEM, SSF_GET_THREAD_SLOT, children[id].kos_tid))
{
// Sleep 1s
_ksys (SF_SLEEP, 100);
}
void *save_ret = children[id].self.ret;
bool is_dead = free_stack_if_dead (id);
clean_child (id);
// If the thread terminated unexpectedly
if (is_dead)
return ESRCH;
if (ret)
*ret = save_ret;
return 0;
}
pthread_t
pthread_self (void)
{
const struct pthread_self *self = __kos_tls_get (KOS_TLS_OFF_PTHREAD_SELF);
if (self)
{
return self->id;
}
return MAIN_THREAD_ID;
}
int
pthread_equal (pthread_t __t1, pthread_t __t2)
{
return __t1 == __t2;
}

View File

@@ -1,9 +1,11 @@
cmake_minimum_required(VERSION 3.31)
project(tests)
project(tests C)
add_executable(hello hello.c)
add_executable(malloc malloc.c)
install(TARGETS hello DESTINATION tests)
install(TARGETS malloc DESTINATION tests)
add_executable(pthread_create pthread.c)
target_link_libraries(pthread_create PRIVATE pthread)
install(TARGETS hello malloc pthread_create DESTINATION tests)

119
tests/pthread.c Normal file
View File

@@ -0,0 +1,119 @@
#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <kolibrios/syscall.h>
void *
thread (void *arg)
{
if (arg == (void *)0xDEADBEEF)
{
_ksys_dbg_print ("I: Thread1: Valid argument\n");
}
_ksys (SF_SLEEP, 1000);
char *str = "I: Thread1: Ok!\n";
return (void *)str;
}
void *
thread_exit (void *arg)
{
if (arg == (void *)0xBEEFDEAD)
{
_ksys_dbg_print ("I: Thread2: Valid argument\n");
}
_ksys (SF_SLEEP, 1000);
char *str = "I: Thread2: Ok!\n";
pthread_exit (str);
}
void *
thread_crash (void *arg)
{
if (arg == (void *)0xDEADCAFE)
{
_ksys_dbg_print ("I: Thread3: Valid argument\n");
}
_ksys (SF_SLEEP, 1000);
/* Crash: General protection fault! */
__asm__ ("int3");
char *str = "E: Joinded 3: Fail!\n";
return str;
}
int
main ()
{
int rc = 0;
pthread_t id1, id2, id3;
void *ret = NULL;
rc = pthread_create (&id1, NULL, thread, (void *)0xDEADBEEF);
if (rc)
{
fprintf (stderr, "E: pthread_create() failed: %s\n", strerror (rc));
return 1;
}
_ksys_dbg_print ("I: Created 1\n");
rc = pthread_create (&id2, NULL, thread_exit, (void *)0xBEEFDEAD);
if (rc)
{
fprintf (stderr, "E: pthread_create() failed: %s\n", strerror (rc));
return 1;
}
_ksys_dbg_print ("I: Created 2\n");
rc = pthread_create (&id3, NULL, thread_crash, (void *)0xDEADCAFE);
if (rc)
{
fprintf (stderr, "E: pthread_create() failed: %s\n", strerror (rc));
return 1;
}
_ksys_dbg_print ("I: Created 3\n");
rc = pthread_join (id1, &ret);
if (rc)
{
fprintf (stderr, "E: pthread_join() failed: %s\n", strerror (rc));
return 1;
}
_ksys_dbg_print ("I: Joinded 1\n");
_ksys_dbg_print (ret);
rc = pthread_join (id2, &ret);
if (rc)
{
fprintf (stderr, "E: pthread_join() failed: %s\n", strerror (rc));
return 1;
}
_ksys_dbg_print ("I: Joinded 2\n");
_ksys_dbg_print (ret);
rc = pthread_join (id3, &ret);
if (rc == ESRCH)
{
_ksys_dbg_print ("I: Not joined 3 (negative tests)\n");
_ksys_dbg_print ("I: Thread3: Ok!\n");
return 0;
}
_ksys_dbg_print ("E: Joinded 3\n");
_ksys_dbg_print (ret);
return 1;
}