From 2fa4b134cbc67b9f76006d6f8d3d73fd93568c40 Mon Sep 17 00:00:00 2001 From: Octavian Purdila Date: Tue, 23 Feb 2016 07:50:36 +0200 Subject: [PATCH] lkl: add support for multiple system call threads One of the current limitation of LKL is that all system calls are serialized which limits the usability of LKL with threading applications. As an example, lets take an application that waits for data on a network socket using a blocking call to read() and occasionally write()s data from another application thread. Once the read() syscall has been issued, no write()s can be performed until the socket receives some data and returns from the read(). This patch adds support for multiple system call threads so that the application can issue multiple system calls. Note that only one system call will be executed (i.e. LKL is still non SMP) at a time, but if one system call blocks, another one can be executed. In order to do so, the application must issue lkl_create_syscall_thread from the context of a host thread. A new kernel thread will be created and all subsequent system calls from the host thread will be queued to the newly created kernel thread. All the host threads that call lkl_create_syscall_thread must call lkl_stop_syscall_thread and this must happen before lkl_halt is called. Signed-off-by: Octavian Purdila --- arch/lkl/Makefile | 3 +- arch/lkl/include/asm/syscalls.h | 2 +- arch/lkl/include/asm/unistd.h | 4 + arch/lkl/include/uapi/asm/host_ops.h | 10 ++ arch/lkl/kernel/setup.c | 8 +- arch/lkl/kernel/syscalls.c | 233 ++++++++++++++++++++++----- tools/lkl/include/lkl.h | 18 +++ tools/lkl/lib/posix-host.c | 25 +++ tools/lkl/tests/boot.c | 64 ++++++++ 9 files changed, 321 insertions(+), 46 deletions(-) diff --git a/arch/lkl/Makefile b/arch/lkl/Makefile index cea544d46d408b..2fdf309e2a926b 100644 --- a/arch/lkl/Makefile +++ b/arch/lkl/Makefile @@ -22,7 +22,8 @@ endif LDFLAGS_vmlinux += -r LKL_ENTRY_POINTS := lkl_start_kernel lkl_sys_halt lkl_syscall lkl_trigger_irq \ - lkl_get_free_irq lkl_put_irq + lkl_get_free_irq lkl_put_irq lkl_create_syscall_thread \ + lkl_stop_syscall_thread core-y += arch/lkl/kernel/ diff --git a/arch/lkl/include/asm/syscalls.h b/arch/lkl/include/asm/syscalls.h index 54e4310bad1a5c..72779c285d4b10 100644 --- a/arch/lkl/include/asm/syscalls.h +++ b/arch/lkl/include/asm/syscalls.h @@ -1,7 +1,7 @@ #ifndef _ASM_LKL_SYSCALLS_H #define _ASM_LKL_SYSCALLS_H -int run_syscalls(void); +int initial_syscall_thread(void *); long lkl_syscall(long no, long *params); #define sys_mmap sys_ni_syscall diff --git a/arch/lkl/include/asm/unistd.h b/arch/lkl/include/asm/unistd.h index 234f93d8e8478c..22e435e5577580 100644 --- a/arch/lkl/include/asm/unistd.h +++ b/arch/lkl/include/asm/unistd.h @@ -1,5 +1,9 @@ #include +#define __NR_create_syscall_thread __NR_arch_specific_syscall + +__SYSCALL(__NR_create_syscall_thread, sys_create_syscall_thread) + #define __SC_ASCII(t, a) #t "," #a #define __ASCII_MAP0(m,...) diff --git a/arch/lkl/include/uapi/asm/host_ops.h b/arch/lkl/include/uapi/asm/host_ops.h index 1fe46589433ab4..2697af38527356 100644 --- a/arch/lkl/include/uapi/asm/host_ops.h +++ b/arch/lkl/include/uapi/asm/host_ops.h @@ -33,6 +33,11 @@ struct lkl_sem_t; * thread handle or NULL if the thread could not be created * @thread_exit - terminates the current thread * + * @tls_alloc - allocate a thread local storage key; returns 0 if succesful + * @tls_free - frees a thread local storage key; returns 0 if succesful + * @tls_set - associate data to the thread local storage key; returns 0 if succesful + * @tls_get - return data associated with the thread local storage key or NULL on error + * * @mem_alloc - allocate memory * @mem_free - free memory * @@ -70,6 +75,11 @@ struct lkl_host_operations { int (*thread_create)(void (*f)(void *), void *arg); void (*thread_exit)(void); + int (*tls_alloc)(unsigned int *key); + int (*tls_free)(unsigned int key); + int (*tls_set)(unsigned int key, void *data); + void *(*tls_get)(unsigned int key); + void* (*mem_alloc)(unsigned long); void (*mem_free)(void *); diff --git a/arch/lkl/kernel/setup.c b/arch/lkl/kernel/setup.c index 3bce11e0b97bf2..77f095283ed5a5 100644 --- a/arch/lkl/kernel/setup.c +++ b/arch/lkl/kernel/setup.c @@ -33,16 +33,10 @@ void __init setup_arch(char **cl) int run_init_process(const char *init_filename) { - lkl_ops->sem_up(init_sem); - - run_syscalls(); + initial_syscall_thread(init_sem); kernel_halt(); - /* We want to kill init without panic()ing */ - init_pid_ns.child_reaper = 0; - do_exit(0); - return 0; } diff --git a/arch/lkl/kernel/syscalls.c b/arch/lkl/kernel/syscalls.c index cc7decc5c3d18c..5d89a1361fd0db 100644 --- a/arch/lkl/kernel/syscalls.c +++ b/arch/lkl/kernel/syscalls.c @@ -8,9 +8,13 @@ #include #include #include +#include #include #include +struct syscall_thread_data; +static asmlinkage long sys_create_syscall_thread(struct syscall_thread_data *); + typedef long (*syscall_handler_t)(long arg1, ...); #undef __SYSCALL @@ -23,15 +27,15 @@ syscall_handler_t syscall_table[__NR_syscalls] = { struct syscall { long no, *params, ret; - void *sem; }; static struct syscall_thread_data { - wait_queue_head_t wqh; struct syscall *s; void *mutex, *completion; -} syscall_thread_data; - + int irq; + /* to be accessed from Linux context only */ + wait_queue_head_t wqh; +} default_syscall_thread_data; static struct syscall *dequeue_syscall(struct syscall_thread_data *data) { @@ -53,19 +57,52 @@ static long run_syscall(struct syscall *s) task_work_run(); - if (s->sem) - lkl_ops->sem_up(s->sem); return ret; } -int run_syscalls(void) +static irqreturn_t syscall_irq_handler(int irq, void *dev_id) +{ + struct syscall_thread_data *data = (struct syscall_thread_data *)dev_id; + + wake_up(&data->wqh); + + return IRQ_HANDLED; +} + +int syscall_thread(void *_data) { - struct syscall_thread_data *data = &syscall_thread_data; + struct syscall_thread_data *data; struct syscall *s; + int ret; + static int count; + + data = (struct syscall_thread_data *)_data; + init_waitqueue_head(&data->wqh); + + snprintf(current->comm, sizeof(current->comm), "ksyscalld%d", count++); - current->flags &= ~PF_KTHREAD; + data->irq = lkl_get_free_irq("syscall"); + if (data->irq < 0) { + pr_err("lkl: %s: failed to allocate irq: %d\n", __func__, + data->irq); + return data->irq; + } + + ret = request_irq(data->irq, syscall_irq_handler, 0, current->comm, + data); + if (ret) { + pr_err("lkl: %s: failed to request irq %d: %d\n", __func__, + data->irq, ret); + lkl_put_irq(data->irq, "syscall"); + data->irq = -1; + return ret; + } - snprintf(current->comm, sizeof(current->comm), "init"); + pr_info("lkl: syscall thread %s initialized (irq%d)\n", current->comm, + data->irq); + + /* system call thread is ready */ + lkl_ops->sem_up(data->completion); while (1) { wait_event(data->wqh, (s = dequeue_syscall(data)) != NULL); @@ -74,48 +111,146 @@ int run_syscalls(void) break; run_syscall(s); + + lkl_ops->sem_up(data->completion); } + free_irq(data->irq, data); + lkl_put_irq(data->irq, "syscall"); + s->ret = 0; - lkl_ops->sem_up(s->sem); + lkl_ops->sem_up(data->completion); return 0; } -static irqreturn_t syscall_irq_handler(int irq, void *dev_id) -{ - wake_up(&syscall_thread_data.wqh); +static unsigned int syscall_thread_data_key; - return IRQ_HANDLED; -} +static int syscall_thread_data_init(struct syscall_thread_data *data, + void *completion) +{ + data->mutex = lkl_ops->sem_alloc(1); + if (!data->mutex) + return -ENOMEM; -static struct irqaction syscall_irqaction = { - .handler = syscall_irq_handler, - .flags = IRQF_NOBALANCING, - .dev_id = &syscall_irqaction, - .name = "syscall" -}; + if (!completion) + data->completion = lkl_ops->sem_alloc(0); + else + data->completion = completion; + if (!data->completion) { + lkl_ops->sem_free(data->mutex); + return -ENOMEM; + } -static int syscall_irq; + return 0; +} long lkl_syscall(long no, long *params) { - struct syscall_thread_data *data = &syscall_thread_data; + struct syscall_thread_data *data = NULL; struct syscall s; + if (lkl_ops->tls_get) + data = lkl_ops->tls_get(syscall_thread_data_key); + if (!data) + data = &default_syscall_thread_data; + s.no = no; s.params = params; - s.sem = data->completion; lkl_ops->sem_down(data->mutex); data->s = &s; - lkl_trigger_irq(syscall_irq); + lkl_trigger_irq(data->irq); lkl_ops->sem_down(data->completion); lkl_ops->sem_up(data->mutex); + if (no == __NR_reboot) { + lkl_ops->sem_free(data->completion); + lkl_ops->sem_free(data->mutex); + if (data != &default_syscall_thread_data) + lkl_ops->mem_free(data); + } + return s.ret; } +static int syscall_threads; + +int lkl_create_syscall_thread(void) +{ + struct syscall_thread_data *data; + long params[6], ret; + + if (!lkl_ops->tls_set) + return -ENOTSUPP; + + data = lkl_ops->mem_alloc(sizeof(*data)); + if (!data) + return -ENOMEM; + + ret = syscall_thread_data_init(data, NULL); + if (ret < 0) { + lkl_ops->mem_free(data); + return ret; + } + + params[0] = (long)data; + ret = lkl_syscall(__NR_create_syscall_thread, params); + if (ret < 0) { + lkl_ops->sem_free(data->completion); + lkl_ops->sem_free(data->mutex); + lkl_put_irq(data->irq, "syscall"); + lkl_ops->mem_free(data); + return ret; + } + + lkl_ops->sem_down(data->completion); + + ret = lkl_ops->tls_set(syscall_thread_data_key, data); + if (ret < 0) { + lkl_ops->sem_free(data->completion); + lkl_ops->sem_free(data->mutex); + lkl_put_irq(data->irq, "syscall"); + lkl_ops->mem_free(data); + return ret; + } + + __sync_fetch_and_add(&syscall_threads, 1); + + return 0; +} + +int lkl_stop_syscall_thread(void) +{ + struct syscall_thread_data *data; + long params[6] = { 0, }; + int ret; + + if (!lkl_ops->tls_get || !lkl_ops->tls_set) + return -ENOTSUPP; + + data = lkl_ops->tls_get(syscall_thread_data_key); + if (!data || data == &default_syscall_thread_data) + return -EINVAL; + + ret = lkl_syscall(__NR_reboot, params); + if (ret < 0) + return ret; + + ret = lkl_ops->tls_set(syscall_thread_data_key, NULL); + if (ret) + return ret; + + params[0] = 0; + params[3] = WEXITED; + ret = lkl_syscall(__NR_waitid, params); + if (ret < 0) + return ret; + + __sync_fetch_and_sub(&syscall_threads, 1); + return 0; +} + asmlinkage ssize_t sys_lkl_pwrite64(unsigned int fd, const char *buf, size_t count, off_t pos_hi, off_t pos_lo) @@ -130,19 +265,43 @@ ssize_t sys_lkl_pread64(unsigned int fd, char *buf, size_t count, return sys_pread64(fd, buf, count, ((loff_t)pos_hi << 32) + pos_lo); } -int __init syscall_init(void) +static asmlinkage long +sys_create_syscall_thread(struct syscall_thread_data *data) { - struct syscall_thread_data *data = &syscall_thread_data; - - init_waitqueue_head(&data->wqh); - data->mutex = lkl_ops->sem_alloc(1); - data->completion = lkl_ops->sem_alloc(0); - BUG_ON(!data->mutex || !data->completion); + pid_t pid; - syscall_irq = lkl_get_free_irq("syscall"); - setup_irq(syscall_irq, &syscall_irqaction); + pid = kernel_thread(syscall_thread, data, CLONE_VM | CLONE_FS | + CLONE_FILES | SIGCHLD); + if (pid < 0) + return pid; - pr_info("lkl: syscall interface initialized (irq%d)\n", syscall_irq); return 0; } -late_initcall(syscall_init); + +int initial_syscall_thread(void *sem) +{ + int ret = 0; + + if (lkl_ops->tls_alloc) + ret = lkl_ops->tls_alloc(&syscall_thread_data_key); + if (ret) + return ret; + + init_pid_ns.child_reaper = 0; + + ret = syscall_thread_data_init(&default_syscall_thread_data, sem); + if (ret) { + lkl_ops->tls_free(syscall_thread_data_key); + return ret; + } + + ret = syscall_thread(&default_syscall_thread_data); + if (lkl_ops->tls_alloc) { + lkl_ops->tls_free(syscall_thread_data_key); + __sync_synchronize(); + BUG_ON(syscall_threads); + } + + return ret; +} + diff --git a/tools/lkl/include/lkl.h b/tools/lkl/include/lkl.h index 46131c230aaa53..f3097c4ac9ff66 100644 --- a/tools/lkl/include/lkl.h +++ b/tools/lkl/include/lkl.h @@ -231,6 +231,24 @@ int lkl_netdev_add(union lkl_netdev nd, void *mac); */ int lkl_netdev_get_ifindex(int id); +/** + * lkl_create_syscall_thread - create an additional system call thread + * + * Create a new system call thread. All subsequent system calls issued from this + * host thread are queued to the newly created system call thread. + * + * System call threads must be stopped up by calling @lkl_stop_syscall_thread + * before @lkl_halt is called. + */ +int lkl_create_syscall_thread(void); + +/** + * lkl_stop_syscall_thread - stop the associated system call thread + * + * Stop the system call thread associated with this host thread, if any. + */ +int lkl_stop_syscall_thread(); + #ifdef __cplusplus } #endif diff --git a/tools/lkl/lib/posix-host.c b/tools/lkl/lib/posix-host.c index 4e146c1a20f134..32b4bcbd047435 100644 --- a/tools/lkl/lib/posix-host.c +++ b/tools/lkl/lib/posix-host.c @@ -178,6 +178,27 @@ static void thread_exit(void) pthread_exit(NULL); } +static int tls_alloc(unsigned int *key) +{ + return pthread_key_create((pthread_key_t*)key, NULL); +} + +static int tls_free(unsigned int key) +{ + return pthread_key_delete(key); +} + +static int tls_set(unsigned int key, void *data) +{ + return pthread_setspecific(key, data); +} + +static void *tls_get(unsigned int key) +{ + return pthread_getspecific(key); +} + + static unsigned long long time_ns(void) { struct timeval tv; @@ -251,6 +272,10 @@ struct lkl_host_operations lkl_host_ops = { .mutex_free = mutex_free, .mutex_lock = mutex_lock, .mutex_unlock = mutex_unlock, + .tls_alloc = tls_alloc, + .tls_free = tls_free, + .tls_set = tls_set, + .tls_get = tls_get, .time = time_ns, .timer_alloc = timer_alloc, .timer_set_oneshot = timer_set_oneshot, diff --git a/tools/lkl/tests/boot.c b/tools/lkl/tests/boot.c index e68574c65bd313..71cb19dc0a82cd 100644 --- a/tools/lkl/tests/boot.c +++ b/tools/lkl/tests/boot.c @@ -666,6 +666,69 @@ static int test_gettid(char *str, int len) } } +static void test_thread(void *data) +{ + int *pipe_fds = (int*) data; + char tmp[LKL_PIPE_BUF+1]; + int ret; + + ret = lkl_create_syscall_thread(); + if (ret < 0) { + fprintf(stderr, "%s: %s\n", __func__, lkl_strerror(ret)); + } + + ret = lkl_sys_read(pipe_fds[0], tmp, sizeof(tmp)); + if (ret < 0) { + fprintf(stderr, "%s: %s\n", __func__, lkl_strerror(ret)); + } + + ret = lkl_stop_syscall_thread(); + if (ret < 0) { + fprintf(stderr, "%s: %s %d\n", __func__, lkl_strerror(ret), ret); + } + +} + +static int test_syscall_thread(char *str, int len) +{ + int pipe_fds[2]; + char tmp[LKL_PIPE_BUF+1]; + long ret; + + ret = lkl_sys_pipe2(pipe_fds, 0); + if (ret) { + snprintf(str, len, "pipe2: %s", lkl_strerror(ret)); + return TEST_FAILURE; + } + + ret = lkl_sys_fcntl(pipe_fds[0], LKL_F_SETPIPE_SZ, 1); + if (ret < 0) { + snprintf(str, len, "fcntl setpipe_sz: %s", lkl_strerror(ret)); + return TEST_FAILURE; + } + + ret = lkl_host_ops.thread_create(test_thread, pipe_fds); + if (ret) { + snprintf(str, len, "failed to create thread"); + return TEST_FAILURE; + } + + sleep(1); + + ret = lkl_sys_write(pipe_fds[1], tmp, sizeof(tmp)); + if (ret != sizeof(tmp)) { + if (ret < 0) + snprintf(str, len, "write: %s", lkl_strerror(ret)); + else + snprintf(str, len, "write: short write: %ld", ret); + return TEST_FAILURE; + } + + sleep(1); + + return TEST_SUCCESS; +} + static struct cl_option *find_short_opt(char name) { struct cl_option *opt; @@ -773,6 +836,7 @@ int main(int argc, char **argv) TEST(mutex); TEST(semaphore); TEST(gettid); + TEST(syscall_thread); lkl_sys_halt();