J'essaie de construire une bibliothèque simple de multithreading coopératif en C. En gros, il est possible de créer des threads à l'aide des éléments suivants thread_create
et les ajouter à la file d'attente avec thread_queue
puis les exécuter jusqu'au bout en utilisant thread_exec
. Dans la fonction qui est attribuée à un thread, il est possible d'appeler thread_yield
pour placer le thread actuel à la fin de la runqueue et continuer avec le thread suivant.
Comme les threads peuvent être interrompus, leurs piles sont allouées au tas. J'utilise alors setjmp
en thread_yield
pour se souvenir de l'état actuel de l'exécution, et longjmp
dans le dispatch
-pour reprendre un fil.
Pour une raison quelconque, lors de l'exécution avec les optimisations du compilateur, j'obtiens l'erreur suivante :
*** longjmp causes uninitialized stack frame ***: terminated
Je n'obtiens pas cette erreur en compilant avec -D_FORTIFY_SOURCE=0
et obtenir le résultat attendu :
started at main
printing from 1, now yielding
printing from 2, now yielding
printing from 3, now yielding
printing from 1, now yielding
printing from 2, now yielding
printing from 3, now yielding
printing from 1, now yielding
printing from 2, now yielding
printing from 3, now yielding
returned from thread
returned from thread
returned from thread
finished at main
J'ai essayé de mettre en œuvre la sauvegarde et la restauration de la rsp
- s'enregistrer, mais en vain. J'aimerais que cela fonctionne sans désactiver les contrôles de sécurité insérés dans le compilateur et j'apprécierais toute suggestion.
Voici le code complet :
threads.c
#include "threads.h"
#include <stdlib.h>
#include <stdio.h>
struct thread* head_thread = NULL;
struct thread* tail_thread = NULL;
size_t initial_rbp = 0;
jmp_buf threading_start_ctx;
// create a thread executing f(arg)
struct thread* thread_create(void (*f)(void*), void* arg) {
// allocate stack of one megabyte
size_t stack_size = 1 << 20;
struct thread* thread = (struct thread*) malloc(sizeof(struct thread));
thread->next = NULL;
thread->f = f;
thread->arg = arg;
thread->stack_ptr = malloc(stack_size);
thread->stack_size = stack_size;
thread->has_run = false;
thread->rbp = 0;
return thread;
}
// add a thread to back queue
void thread_queue(struct thread* thread) {
if (!tail_thread) {
head_thread = thread;
} else {
tail_thread->next = thread;
}
tail_thread = thread;
thread->next = NULL;
}
// yield from thread, transfering execution to another thread
void thread_yield(void) {
// save rbp
asm("movq %%rbp, %[RBP]" : [RBP] "=rm" (head_thread->rbp) :);
if (!setjmp(head_thread->jmp_buf)) {
// if jmp was just set, schedule a different thread and execute it
schedule();
dispatch();
}
// restore rbp
asm("movq %[RBP], %%rbp" : : [RBP] "rm" (head_thread->rbp));
}
// reschedule threads
static void schedule(void) {
// only swap if more than one thread
if (head_thread != tail_thread) {
struct thread* current = head_thread;
head_thread = head_thread->next;
tail_thread->next = current;
tail_thread = current;
tail_thread->next = NULL;
}
}
// execute current head thread
static void dispatch(void) {
if (head_thread) {
// stack grows downwards, therefore add offset
size_t stack_top = (size_t) head_thread->stack_ptr + head_thread->stack_size;
if (!head_thread->has_run) {
head_thread->has_run = true;
// set up rbp and rsp to thread stack
asm(
"movq %[StackTop], %%rbp\n"
"movq %[StackTop], %%rsp"
: : [StackTop] "r" (stack_top)
);
// execute thread
head_thread->f(head_thread->arg);
printf("returned from thread\n");
// returned from thread, load starting rbp
asm("movq %[RBP], %%rbp" : : [RBP] "rm" (initial_rbp));
longjmp(threading_start_ctx, 1);
} else {
// load rbp of current head and continue where it yielded
asm("movq %[RBP], %%rbp": : [RBP] "rm" (head_thread->rbp));
longjmp(head_thread->jmp_buf, 1);
}
}
}
// start executing queued threads
void thread_exec(void) {
// save starting rbp
asm("movq %%rbp, %[RBP]" : [RBP] "=rm" (initial_rbp) :);
if (setjmp(threading_start_ctx)) {
// if arrive from longjmp, free current head
struct thread* next = head_thread->next;
free(head_thread->stack_ptr);
free(head_thread);
head_thread = next;
if (!head_thread) {
tail_thread = NULL;
}
}
if (head_thread) {
dispatch();
}
}
threads.h
#ifndef THREADS_H_
#define THREADS_H_
#include <stddef.h>
#include <setjmp.h>
#include <stdbool.h>
struct thread {
struct thread* next;
void (*f)(void*);
void* arg;
void* stack_ptr;
size_t stack_size;
bool has_run;
size_t rbp;
jmp_buf jmp_buf;
};
struct thread* thread_create(void (*f)(void*), void* arg);
void thread_queue(struct thread* t);
void thread_yield(void);
void thread_exec(void);
static void dispatch(void);
static void schedule(void);
#endif // THREADS_H_
main.c
#include <stdio.h>
#include <stdlib.h>
#include "threads.h"
static int a3 = 3;
void arg_printer(void* arg) {
int id = *(int*)arg;
if (id == 1) {
thread_queue(thread_create(arg_printer, &a3));
}
for (size_t i = 0; i < 3; ++i) {
printf("printing from %i, now yielding\n", id);
thread_yield();
}
}
int main() {
printf("started at main\n");
int a1 = 1;
int a2 = 2;
thread_queue(thread_create(arg_printer, &a1));
thread_queue(thread_create(arg_printer, &a2));
thread_exec();
printf("finished at main\n");
return 0;
}