tinythread

tiny threading library for linux
git clone git://git.2f30.org/tinythread.git
Log | Files | Refs | README | LICENSE

commit e4ba02507b3f9246d57988ebc779dad495f93fa1
Author: sin <sin@2f30.org>
Date:   Sun Sep 22 18:30:45 +0100

Initial commit

Diffstat:
COPYING | 14++++++++++++++
Makefile | 17+++++++++++++++++
README | 4++++
TODO | 4++++
list.h | 87+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
test.c | 56++++++++++++++++++++++++++++++++++++++++++++++++++++++++
thread.c | 298+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
thread.h | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 532 insertions(+), 0 deletions(-)
diff --git a/COPYING b/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + 14 rue de Plaisance, 75014 Paris, France + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/Makefile b/Makefile @@ -0,0 +1,17 @@ +bin = thread +ver = 0.1 +src = thread.c test.c + +CC = gcc +CFLAGS += -Wall -Wextra -Wformat-security -Wshadow \ + -Wpointer-arith -ggdb + +all: $(bin) + +%: %.c + $(CC) -o $(bin) $(src) $(CFLAGS) + +clean: + @rm -rf $(bin) + +.PHONY: all clean diff --git a/README b/README @@ -0,0 +1,4 @@ +What is it? +=========== + +A tiny threading library for Linux. diff --git a/TODO b/TODO @@ -0,0 +1,4 @@ +* Make spinlock_t a struct. +* Don't export thread_config in the header, use functions instead (e.g. getstacksize()/setstacksize()). +* Ensure we can create threads from inside threads (this might work now as is). +* Use TLS for threads. diff --git a/list.h b/list.h @@ -0,0 +1,87 @@ +#ifndef _LIST_H +#define _LIST_H + +#define offsetof(TYPE, MEMBER) ((size_t)__builtin_offsetof(TYPE, MEMBER)) +#define container_of(ptr, type, member) ({ const typeof( ((type *)0)->member ) *__mptr = (ptr); (type *)( (char *)__mptr - offsetof(type,member) ); }) + +/** + * list_entry - get the struct for this entry + * @ptr: the &struct list_head pointer. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + */ +#define list_entry(ptr, type, member) \ + container_of(ptr, type, member) + +/** + * list_for_each - iterate over a list + * @pos: the &struct list_head to use as a loop cursor. + * @head: the head for your list. + */ +#define list_for_each(pos, head) \ + for (pos = (head)->next; pos != (head); \ + pos = pos->next) +/** + * list_for_each_safe - iterate over a list safe against removal of list entry + * @pos: the &struct list_head to use as a loop cursor. + * @n: another &struct list_head to use as temporary storage + * @head: the head for your list. + */ +#define list_for_each_safe(pos, n, head) \ + for (pos = (head)->next, n = pos->next; pos != (head); \ + pos = n, n = pos->next) + +#define LIST_HEAD(name) \ + struct list_head name = LIST_HEAD_INIT(name) + +#define LIST_HEAD_INIT(name) { &(name), &(name) } /* assign both next and prev to point to &name */ + +struct list_head { + struct list_head *next; + struct list_head *prev; +}; + +static inline void +INIT_LIST_HEAD(struct list_head *list) +{ + list->next = list; + list->prev = list; +} + +static inline void +list_add(struct list_head *new, struct list_head *head) +{ + head->next->prev = new; + new->next = head->next; + new->prev = head; + head->next = new; +} + +static inline void +list_add_tail(struct list_head *new, struct list_head *head) +{ + head->prev->next = new; + new->next = head; + new->prev = head->prev; + head->prev = new; +} + +static inline void +list_del(struct list_head *entry) +{ + entry->prev->next = entry->next; + entry->next->prev = entry->prev; + entry->next = NULL; + entry->prev = NULL; +} + +/** + * list_empty - tests whether a list is empty + * @head: the list to test. + */ +static inline int list_empty(const struct list_head *head) +{ + return head->next == head; +} + +#endif diff --git a/test.c b/test.c @@ -0,0 +1,56 @@ +#include <unistd.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "thread.h" + +enum { + THREADS_SPAWN_NR = 64 +}; + +/* This gets incremented once by each thread */ +static int counter; +/* Lock that protects `counter' */ +static spinlock_t lock; +/* Per thread arguments */ +static struct thread_args { + int id; +} thread_args[THREADS_SPAWN_NR]; + +static int +foo(void *arg) +{ + struct thread_args *args = arg; + + acquire(&lock); + printf("Entered thread with id %d\n", args->id); + counter++; + release(&lock); + + return 0; +} + +int +main(void) +{ + int i; + struct thread_ctx *tctx; + + setbuf(stdout, NULL); + + tctx = thread_init(NULL, NULL); + spinlock_init(&lock); + + for (i = 0; i < THREADS_SPAWN_NR; i++) { + thread_args[i].id = i; + thread_register(tctx, foo, &thread_args[i]); + } + + thread_wait_all_blocking(tctx); + + thread_exit(tctx); + + return EXIT_SUCCESS; +} diff --git a/thread.c b/thread.c @@ -0,0 +1,298 @@ +#define _GNU_SOURCE + +#include <sys/wait.h> +#include <sys/mman.h> +#include <sched.h> +#include <unistd.h> +#include <errno.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "list.h" +#include "thread.h" + +struct thread { + struct list_head list; + void *stack; + pid_t pid; + int (*fn)(void *); + void *args; +}; + +struct thread_ctx { + /* Configuration parameters for the library */ + struct thread_config thread_config; + /* Lock used internally to ensure the core is protected */ + spinlock_t lock; + /* List of all registered threads */ + struct list_head thread_list; +}; + +struct thread_ctx * +thread_init(const struct thread_config *tc, int *rval) { + struct thread_ctx *tctx; + long page_size; + size_t stack_size; + size_t guard_size; + + tctx = calloc(1, sizeof *tctx); + if (!tctx) { + if (rval) + *rval = -ENOMEM; + return NULL; + } + + spinlock_init(&tctx->lock); + INIT_LIST_HEAD(&tctx->thread_list); + + page_size = sysconf(_SC_PAGESIZE); + if (page_size < 0) + page_size = 4096; + + if (tc) { + /* Ensure stack size and guard size are + * page aligned */ + stack_size = (tc->stack_size + + (page_size - 1)) & ~(page_size - 1); + guard_size = (tc->guard_size + + (page_size - 1)) & ~(page_size - 1); + tctx->thread_config.stack_size = stack_size; + tctx->thread_config.guard_size = guard_size; + } else { + tctx->thread_config.stack_size = page_size * 16; + tctx->thread_config.guard_size = page_size; + } + + return tctx; +} + +pid_t +thread_register(struct thread_ctx *tctx, int (*fn)(void *), void *arg) +{ + struct thread *t; + pid_t ret; + + if (!tctx) + return -EINVAL; + + acquire(&tctx->lock); + t = malloc(sizeof *t); + if (!t) { + release(&tctx->lock); + return -ENOMEM; + } + + t->stack = mmap(0, tctx->thread_config.stack_size, PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (t->stack == MAP_FAILED) { + ret = -errno; + goto free_thread; + } + + /* Set the guard page at the end of the stack */ + ret = mprotect(t->stack, tctx->thread_config.guard_size, PROT_NONE); + if (ret < 0) { + ret = -errno; + goto free_stack; + } + + t->pid = clone(fn, (char *)t->stack + tctx->thread_config.stack_size, + SIGCHLD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | + CLONE_VM | CLONE_SYSVSEM, arg, NULL, NULL, NULL); + if (t->pid < 0) { + ret = -errno; + goto free_stack; + } + + t->fn = fn; + t->args = arg; + + list_add_tail(&t->list, &tctx->thread_list); + release(&tctx->lock); + + return t->pid; + +free_stack: + munmap(t->stack, tctx->thread_config.stack_size); +free_thread: + free(t); + release(&tctx->lock); + return ret; +} + +/* Called with lock acquired */ +static void +thread_remove(struct thread_ctx *tctx, pid_t pid) +{ + struct list_head *iter, *q; + struct thread *tmp; + + /* A hash table here would be better for many threads */ + list_for_each_safe(iter, q, &tctx->thread_list) { + tmp = list_entry(iter, struct thread, list); + if (tmp->pid == pid) { + list_del(&tmp->list); + munmap(tmp->stack, tctx->thread_config.stack_size); + free(tmp); + break; + } + } +} + +int +thread_wait(struct thread_ctx *tctx, pid_t pid) +{ + pid_t p; + int status; + + if (!tctx) + return -EINVAL; + + acquire(&tctx->lock); + p = wait(&status); + if (p < 0) { + release(&tctx->lock); + return -errno; + } + if (p == pid) { + thread_remove(tctx, p); + release(&tctx->lock); + return WEXITSTATUS(status); + } + release(&tctx->lock); + + return -EAGAIN; +} + +int +thread_wait_blocking(struct thread_ctx *tctx, pid_t pid) +{ + pid_t p; + int status; + + if (!tctx) + return -EINVAL; + + acquire(&tctx->lock); + while (1) { + p = wait(&status); + if (p < 0) { + release(&tctx->lock); + return -errno; + } + if (p == pid) { + thread_remove(tctx, p); + break; + } + } + release(&tctx->lock); + + return WEXITSTATUS(status); +} + +int +thread_wait_all_blocking(struct thread_ctx *tctx) +{ + pid_t pid; + + if (!tctx) + return -EINVAL; + + acquire(&tctx->lock); + while (!list_empty(&tctx->thread_list)) { + pid = wait(NULL); + if (pid < 0) { + release(&tctx->lock); + return -errno; + } + thread_remove(tctx, pid); + } + release(&tctx->lock); + + return 0; +} + +void +thread_exit(struct thread_ctx *tctx) +{ + struct list_head *iter, *q; + struct thread *tmp; + + if (!tctx) + return; + + acquire(&tctx->lock); + while (!list_empty(&tctx->thread_list)) { + /* A hash table here would be better for many threads */ + list_for_each_safe(iter, q, &tctx->thread_list) { + tmp = list_entry(iter, struct thread, list); + list_del(&tmp->list); + munmap(tmp->stack, tctx->thread_config.stack_size); + free(tmp); + } + } + release(&tctx->lock); + free(tctx); +} + +pid_t +thread_id(void) +{ + return getpid(); +} + +int +thread_get_sched_param(struct thread_ctx *tctx, pid_t pid, int *policy, + struct sched_param *param) +{ + struct list_head *iter; + struct thread *tmp; + int ret = -EINVAL; + + if (!tctx) + return -EINVAL; + + acquire(&tctx->lock); + list_for_each(iter, &tctx->thread_list) { + tmp = list_entry(iter, struct thread, list); + if (tmp->pid == pid) { + ret = sched_getparam(tmp->pid, param); + if (ret < 0) + ret = -errno; + else + *policy = sched_getscheduler(tmp->pid); + break; + } + } + release(&tctx->lock); + + return ret; +} + +int +thread_set_sched_param(struct thread_ctx *tctx, pid_t pid, int policy, + const struct sched_param *param) +{ + struct list_head *iter; + struct thread *tmp; + int ret = -EINVAL; + + if (!tctx) + return -EINVAL; + + acquire(&tctx->lock); + list_for_each(iter, &tctx->thread_list) { + tmp = list_entry(iter, struct thread, list); + if (tmp->pid == pid) { + ret = sched_setscheduler(tmp->pid, policy, param); + if (ret < 0) + ret = -errno; + break; + } + } + release(&tctx->lock); + + return ret; +} diff --git a/thread.h b/thread.h @@ -0,0 +1,52 @@ +#ifndef THREADLIB_H +#define THREADLIB_H + +#include <sched.h> +#include <stddef.h> + +struct thread_config { + size_t stack_size; + size_t guard_size; +}; + +typedef int spinlock_t; + +struct thread_ctx; +extern struct thread_ctx *thread_init(const struct thread_config *tc, int *rval); +extern pid_t thread_register(struct thread_ctx *tctx, int (*fn)(void *), void *arg); +extern int thread_wait(struct thread_ctx *tctx, pid_t pid); +extern int thread_wait_blocking(struct thread_ctx *tctx, pid_t pid); +extern int thread_wait_all_blocking(struct thread_ctx *tctx); +extern void thread_exit(struct thread_ctx *tctx); +extern pid_t thread_id(void); +extern int thread_get_sched_param(struct thread_ctx *tctx, pid_t pid, int *policy, + struct sched_param *param); +extern int thread_set_sched_param(struct thread_ctx *tctx, pid_t pid, int policy, + const struct sched_param *param); + +static inline void +spinlock_init(spinlock_t *spinlock) +{ + __sync_lock_release(spinlock); +} + +static inline void +acquire(spinlock_t *spinlock) +{ + while (__sync_lock_test_and_set(spinlock, 1) == 1) + ; +} + +static inline int +try_acquire(spinlock_t *spinlock) +{ + return __sync_lock_test_and_set(spinlock, 1); +} + +static inline void +release(spinlock_t *spinlock) +{ + __sync_lock_release(spinlock); +} + +#endif