commit e4ba02507b3f9246d57988ebc779dad495f93fa1
Author: sin <sin@2f30.org>
Date: Sun, 22 Sep 2013 18:30:45 +0100
Initial commit
Diffstat:
A | COPYING | | | 14 | ++++++++++++++ |
A | Makefile | | | 17 | +++++++++++++++++ |
A | README | | | 4 | ++++ |
A | TODO | | | 4 | ++++ |
A | list.h | | | 87 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | test.c | | | 56 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | thread.c | | | 298 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | 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