kospthread: Initial implementation
Details are described in README.md Signed-off-by: Maxim Logaev <maxlogaev@proton.me>
This commit is contained in:
@@ -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
|
||||
)
|
||||
|
||||
11
libraries/kospthread/CMakeLists.txt
Normal file
11
libraries/kospthread/CMakeLists.txt
Normal 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)
|
||||
57
libraries/kospthread/README.md
Normal file
57
libraries/kospthread/README.md
Normal 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
|
||||
```
|
||||
22
libraries/kospthread/src/exit.S
Normal file
22
libraries/kospthread/src/exit.S
Normal 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
|
||||
293
libraries/kospthread/src/threads.c
Normal file
293
libraries/kospthread/src/threads.c
Normal 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;
|
||||
}
|
||||
@@ -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
119
tests/pthread.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user