diff --git a/Makefile b/Makefile index 729fb68..91379d0 100644 --- a/Makefile +++ b/Makefile @@ -6,11 +6,16 @@ MKDIR = mkdir -p SRC_DIR = src INC_DIR = includes -SOURCES = $(wildcard $(SRC_DIR)/*.c) -OBJETS = $(patsubst %.c,%.o,$(notdir $(SOURCES))) + +SOURCES = $(wildcard $(SRC_DIR)/*.c) +SOURCES_NOSCHED = $(filter-out $(wildcard $(SRC_DIR)/sched-*.c), $(SOURCES)) + +ALL_OBJECTS = $(patsubst %.c,%.o,$(notdir $(SOURCES))) +OBJECTS = $(patsubst %.c,%.o,$(notdir $(SOURCES_NOSCHED))) CFLAGS = -std=gnu11 -pedantic LDFLAGS = +SCHED = sched-ws.o EXE = ordonnanceur EXE_EXT = .elf @@ -22,7 +27,7 @@ PDF_NEWNAME = Rapport de projet %.o: src/%.c $(CC) -c $< -o $@ $(CFLAGS) -release: CFLAGS += -O2 +release: CFLAGS += -O2 release: compilation debug: CFLAGS += -Wall -Wextra -Wshadow -Wcast-align -Wstrict-prototypes @@ -30,12 +35,25 @@ debug: CFLAGS += -fanalyzer -fsanitize=undefined -g -Og debug: LDFLAGS += -fsanitize=undefined debug: compilation -compilation: $(OBJETS) - $(CC) -o $(EXE)$(EXE_EXT) $(OBJETS) $(LDFLAGS) +compilation: $(ALL_OBJECTS) + $(CC) -o $(EXE)$(EXE_EXT) $(OBJECTS) $(SCHED) $(LDFLAGS) all: release +# Specific schedulers +threads: SCHED = sched-threads.o +threads: release + +stack: SCHED = sched-stack.o +stack: release + +random: SCHED = sched-random.o +random: release + +ws: SCHED = sched-ws.o +ws: release + pdf-make: cd report && \ $(MAKE) @@ -45,7 +63,7 @@ pdf-clean: $(MAKE) clean clean: pdf-clean - $(RM) $(OBJETS) "$(EXE)$(EXE_EXT)" "$(ARCHIVE_NAME).tar" + $(RM) *.o "$(EXE)$(EXE_EXT)" "$(ARCHIVE_NAME).tar" archive: pdf-make $(MKDIR) "$(ARCHIVE_NAME)" diff --git a/README b/README index 1faf0c7..cea26cd 100644 --- a/README +++ b/README @@ -1,20 +1,34 @@ Projet de programmation système avancée ======================================= -Compilation optimisée ---------------------- +Compilation optimisée avec ordonnanceur *work-stealing* +------------------------------------------------------- make Ce qui créer l'exécutable `ordonnanceur.elf`. + Lancement utilisant tous les cœurs disponibles : ------------------------------------------------ ./ordonnanceur.elf -t 0 -Info ----- + +Autres options +-------------- + +Il est possible d'utiliser d'autres implémentations d'ordonnanceur en changeant +la cible du Makefile. + +- `make threads` : lance juste des threads +- `make stack` : utilisation d'une pile +- `make random` : idem que stack mais en prenant une tâche aléatoire +- `make ws` : work-stealing + + +Infos +----- Le rapport se trouve dans le dossier courant. diff --git a/src/sched-random.c b/src/sched-random.c new file mode 100644 index 0000000..47f7e9f --- /dev/null +++ b/src/sched-random.c @@ -0,0 +1,179 @@ +#include "../includes/sched.h" + +#include +#include +#include +#include + +struct task_info { + void *closure; + taskfunc f; +}; + +struct scheduler { + /* Indicateur de changement d'état */ + pthread_cond_t cond; + + /* Taille de la pile */ + int qlen; + + /* Mutex qui protège la structure */ + pthread_mutex_t mutex; + + /* Nombre de threads instanciés */ + int nthreads; + + /* Nombre de threads en attente */ + int nthsleep; + + /* Pile de tâches */ + struct task_info *tasks; + + /* Position actuelle dans la pile */ + int top; +}; + +/* Ordonnanceur partagé */ +static struct scheduler sched; + +/* Lance une tâche de la pile */ +void *sched_worker(void *); + +int +sched_init(int nthreads, int qlen, taskfunc f, void *closure) +{ + if(qlen <= 0) { + fprintf(stderr, "qlen must be greater than 0\n"); + return -1; + } + sched.qlen = qlen; + + if(nthreads < 0) { + fprintf(stderr, "nthreads must be greater than 0\n"); + return -1; + } else if(nthreads == 0) { + nthreads = sched_default_threads(); + } + sched.nthreads = nthreads; + + if(pthread_mutex_init(&sched.mutex, NULL) != 0) { + fprintf(stderr, "Can't init mutex\n"); + return -1; + } + + if(pthread_cond_init(&sched.cond, NULL) != 0) { + fprintf(stderr, "Can't init condition variable\n"); + return -1; + } + + sched.top = -1; + if((sched.tasks = malloc(qlen * sizeof(struct task_info))) == NULL) { + fprintf(stderr, "Can't allocate memory for stack\n"); + return -1; + } + + // Initialise l'aléatoire + srand(time(NULL)); + + pthread_t threads[nthreads]; + for(int i = 0; i < nthreads; ++i) { + if(pthread_create(&threads[i], NULL, sched_worker, &sched) != 0) { + fprintf(stderr, "Can't create the thread %d\n", i); + + if(i > 0) { + fprintf(stderr, ", cancelling already created threads...\n"); + for(int j = 0; j < i; ++j) { + if(pthread_cancel(threads[j]) != 0) { + fprintf(stderr, "Can't cancel the thread %d\n", j); + } + } + } else { + fprintf(stderr, "\n"); + } + + free(sched.tasks); + return -1; + } + } + + if(sched_spawn(f, closure, &sched) < 0) { + fprintf(stderr, "Can't create the initial task\n"); + return -1; + } + + for(int i = 0; i < nthreads; ++i) { + if((pthread_join(threads[i], NULL) != 0)) { + fprintf(stderr, "Can't wait the thread %d\n", i); + return -1; + } + } + + free(sched.tasks); + + return 1; +} + +int +sched_spawn(taskfunc f, void *closure, struct scheduler *s) +{ + pthread_mutex_lock(&s->mutex); + + if(s->top + 1 >= s->qlen) { + pthread_mutex_unlock(&s->mutex); + errno = EAGAIN; + fprintf(stderr, "Stack is full\n"); + return -1; + } + + s->tasks[++s->top] = (struct task_info){closure, f}; + + pthread_cond_signal(&s->cond); + pthread_mutex_unlock(&s->mutex); + + return 0; +} + +void * +sched_worker(void *arg) +{ + struct scheduler *s = (struct scheduler *)arg; + + while(1) { + pthread_mutex_lock(&s->mutex); + + // S'il on a rien à faire + if(s->top == -1) { + s->nthsleep++; + if(s->nthsleep == s->nthreads) { + // Signal a tout les threads que il n'y a plus rien à faire + // si un thread attend une tâche + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->mutex); + + break; + } + + pthread_cond_wait(&s->cond, &s->mutex); + s->nthsleep--; + pthread_mutex_unlock(&s->mutex); + continue; + } + + // Extrait une tâche aléatoire de la liste + int random_index = rand() % (s->top + 1); + + struct task_info echange = s->tasks[random_index]; + s->tasks[random_index] = s->tasks[s->top]; + s->tasks[s->top] = echange; + + taskfunc f = s->tasks[s->top].f; + void *closure = s->tasks[s->top].closure; + s->top--; + pthread_mutex_unlock(&s->mutex); + + // Exécute la tâche + f(closure, s); + } + + return NULL; +} diff --git a/src/sched-stack.c b/src/sched-stack.c new file mode 100644 index 0000000..1ce3319 --- /dev/null +++ b/src/sched-stack.c @@ -0,0 +1,170 @@ +#include "../includes/sched.h" + +#include +#include +#include +#include + +struct task_info { + void *closure; + taskfunc f; +}; + +struct scheduler { + /* Indicateur de changement d'état */ + pthread_cond_t cond; + + /* Taille de la pile */ + int qlen; + + /* Mutex qui protège la structure */ + pthread_mutex_t mutex; + + /* Nombre de threads instanciés */ + int nthreads; + + /* Nombre de threads en attente */ + int nthsleep; + + /* Pile de tâches */ + struct task_info *tasks; + + /* Position actuelle dans la pile */ + int top; +}; + +/* Ordonnanceur partagé */ +static struct scheduler sched; + +/* Lance une tâche de la pile */ +void *sched_worker(void *); + +int +sched_init(int nthreads, int qlen, taskfunc f, void *closure) +{ + if(qlen <= 0) { + fprintf(stderr, "qlen must be greater than 0\n"); + return -1; + } + sched.qlen = qlen; + + if(nthreads < 0) { + fprintf(stderr, "nthreads must be greater than 0\n"); + return -1; + } else if(nthreads == 0) { + nthreads = sched_default_threads(); + } + sched.nthreads = nthreads; + + if(pthread_mutex_init(&sched.mutex, NULL) != 0) { + fprintf(stderr, "Can't init mutex\n"); + return -1; + } + + if(pthread_cond_init(&sched.cond, NULL) != 0) { + fprintf(stderr, "Can't init condition variable\n"); + return -1; + } + + sched.top = -1; + if((sched.tasks = malloc(qlen * sizeof(struct task_info))) == NULL) { + fprintf(stderr, "Can't allocate memory for stack\n"); + return -1; + } + + pthread_t threads[nthreads]; + for(int i = 0; i < nthreads; ++i) { + if(pthread_create(&threads[i], NULL, sched_worker, &sched) != 0) { + fprintf(stderr, "Can't create the thread %d\n", i); + + if(i > 0) { + fprintf(stderr, ", cancelling already created threads...\n"); + for(int j = 0; j < i; ++j) { + if(pthread_cancel(threads[j]) != 0) { + fprintf(stderr, "Can't cancel the thread %d\n", j); + } + } + } else { + fprintf(stderr, "\n"); + } + + free(sched.tasks); + return -1; + } + } + + if(sched_spawn(f, closure, &sched) < 0) { + fprintf(stderr, "Can't create the initial task\n"); + return -1; + } + + for(int i = 0; i < nthreads; ++i) { + if((pthread_join(threads[i], NULL) != 0)) { + fprintf(stderr, "Can't wait the thread %d\n", i); + return -1; + } + } + + free(sched.tasks); + + return 1; +} + +int +sched_spawn(taskfunc f, void *closure, struct scheduler *s) +{ + pthread_mutex_lock(&s->mutex); + + if(s->top + 1 >= s->qlen) { + pthread_mutex_unlock(&s->mutex); + errno = EAGAIN; + fprintf(stderr, "Stack is full\n"); + return -1; + } + + s->tasks[++s->top] = (struct task_info){closure, f}; + + pthread_cond_signal(&s->cond); + pthread_mutex_unlock(&s->mutex); + + return 0; +} + +void * +sched_worker(void *arg) +{ + struct scheduler *s = (struct scheduler *)arg; + + while(1) { + pthread_mutex_lock(&s->mutex); + + // S'il on a rien à faire + if(s->top == -1) { + s->nthsleep++; + if(s->nthsleep == s->nthreads) { + // Signal a tout les threads que il n'y a plus rien à faire + // si un thread attend une tâche + pthread_cond_broadcast(&s->cond); + pthread_mutex_unlock(&s->mutex); + + break; + } + + pthread_cond_wait(&s->cond, &s->mutex); + s->nthsleep--; + pthread_mutex_unlock(&s->mutex); + continue; + } + + // Extrait la tâche de la pile + taskfunc f = s->tasks[s->top].f; + void *closure = s->tasks[s->top].closure; + s->top--; + pthread_mutex_unlock(&s->mutex); + + // Exécute la tâche + f(closure, s); + } + + return NULL; +} diff --git a/src/sched-threads.c b/src/sched-threads.c new file mode 100644 index 0000000..55ccb23 --- /dev/null +++ b/src/sched-threads.c @@ -0,0 +1,34 @@ +#include "../includes/sched.h" + +#include +#include +#include + +int +sched_init(int nthreads, int qlen, taskfunc f, void *closure) +{ + sched_spawn(f, closure, NULL); + return 0; +} + +int +sched_spawn(taskfunc f, void *closure, struct scheduler *s) +{ + pthread_t thread; + int err; + + // Création d'un thread pour la tâche + if((err = pthread_create(&thread, NULL, (void *(*)(void *))f, closure)) != + 0) { + fprintf(stderr, "pthread_create error %d\n", errno); + return -1; + } + + // Attend la fin du thread + if((err = pthread_join(thread, NULL)) != 0) { + fprintf(stderr, "pthread_join error %d\n", errno); + return -1; + } + + return 0; +} diff --git a/src/sched.c b/src/sched-ws.c similarity index 100% rename from src/sched.c rename to src/sched-ws.c