Skip to content

Instantly share code, notes, and snippets.

@kana-sama
Last active March 22, 2023 21:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kana-sama/73383e5f08268f6827f9d8db44460ad5 to your computer and use it in GitHub Desktop.
Save kana-sama/73383e5f08268f6827f9d8db44460ad5 to your computer and use it in GitHub Desktop.
# include <stdio.h>
# include <stdlib.h>
# include <stdbool.h>
# include <setjmp.h>
# undef _FORTIFY_SOURCE
# define _FORTIFY_SOURCE 0
typedef int64_t PID_t;
typedef void (*action_t)();
// Process
typedef struct process_t {
PID_t pid;
jmp_buf* context;
char* stack;
} process_t;
process_t* process_new(PID_t pid, jmp_buf* context, char* stack) {
process_t* process = malloc(sizeof(process_t));
process->pid = pid;
process->context = context;
process->stack = stack;
return process;
}
void process_free(process_t* process) {
free(process->context);
free(process->stack);
free(process);
}
// Tasks queue
typedef struct tasks_queue_node_t {
process_t* head;
struct tasks_queue_node_t* tail;
} tasks_queue_node_t;
typedef struct tasks_queue_t {
tasks_queue_node_t* beg;
tasks_queue_node_t* end;
} tasks_queue_t;
tasks_queue_t* tasks_queue_new() {
tasks_queue_t* queue = malloc(sizeof(tasks_queue_t));
queue->beg = NULL;
queue->end = NULL;
return queue;
}
bool tasks_queue_is_empty(tasks_queue_t* queue) {
return queue->beg == NULL;
}
void tasks_queue_enqueue(tasks_queue_t* queue, process_t* task) {
tasks_queue_node_t* node = malloc(sizeof(tasks_queue_node_t));
node->head = task;
node->tail = NULL;
if (tasks_queue_is_empty(queue)) {
queue->beg = node;
queue->end = node;
} else {
queue->end->tail = node;
queue->end = node;
}
}
process_t* tasks_queue_dequeue(tasks_queue_t* queue) {
if (tasks_queue_is_empty(queue)) return NULL;
tasks_queue_node_t* node = queue->beg;
process_t* process = node->head;
queue->beg = node->tail;
if (node->tail == NULL)
queue->end = NULL;
free(node);
return process;
}
// Scheduler
struct scheduler_t {
tasks_queue_t* queue;
tasks_queue_t* to_free;
process_t* current;
jmp_buf* exit;
int64_t fuel;
int64_t gen;
int64_t available_fuel;
}* scheduler;
void scheduler_init(int64_t available_fuel) {
scheduler = malloc(sizeof(struct scheduler_t));
scheduler->queue = tasks_queue_new();
scheduler->to_free = tasks_queue_new();
scheduler->current = NULL;
scheduler->exit = malloc(sizeof(jmp_buf));
scheduler->fuel = 0;
scheduler->gen = 0;
scheduler->available_fuel = available_fuel;
}
static inline PID_t self() {
return scheduler->current->pid;
}
void collect_free_processes() {
process_t* process;
while ((process = tasks_queue_dequeue(scheduler->to_free))) {
process_free(process);
}
}
_Noreturn void switch_context() {
scheduler->current = tasks_queue_dequeue(scheduler->queue);
scheduler->fuel = scheduler->available_fuel;
if (scheduler->current) {
longjmp(*scheduler->current->context, 1);
} else {
longjmp(*scheduler->exit, 1);
}
}
_Noreturn void action_wrapper(action_t action, PID_t pid, jmp_buf* spawner, char* stack) {
process_t* process = process_new(pid, malloc(sizeof(jmp_buf)), stack);
tasks_queue_enqueue(scheduler->queue, process);
if (setjmp(*process->context) == 0)
longjmp(*spawner, 1);
action();
tasks_queue_enqueue(scheduler->to_free, process);
switch_context();
}
# define STACK_SIZE (8 * 1024 * 1024)
PID_t spawn(action_t action) {
jmp_buf* spawner = malloc(sizeof(jmp_buf));
PID_t pid = scheduler->gen++;
char* stack = aligned_alloc(16, STACK_SIZE);
char* stack_head = stack + STACK_SIZE - 8;
if (setjmp(*spawner) == 0) asm(
// use new stack
"movq %[stack_head], %%rsp \n"
// jump to wrapper to create context
"movq %[action] , %%rdi \n"
"movq %[pid] , %%rsi \n"
"movq %[spawner] , %%rdx \n"
"movq %[stack] , %%rcx \n"
"jmpq *%[action_wrapper] \n"
:
: [stack_head] "r" (stack_head)
, [action] "r" (action)
, [pid] "r" (pid)
, [spawner] "r" (spawner)
, [stack] "r" (stack)
, [action_wrapper] "r" (action_wrapper)
: "rdi", "rsi", "rdx", "rcx", "memory"
);
return pid;
}
void yield() {
scheduler->fuel -= 1;
if (scheduler->fuel <= 0) {
tasks_queue_enqueue(scheduler->queue, scheduler->current);
if (setjmp(*scheduler->current->context) == 0) {
switch_context();
}
}
collect_free_processes();
}
void run(action_t action) {
if (setjmp(*scheduler->exit) == 0) {
spawn(action);
switch_context();
}
collect_free_processes();
}
// Example
void printer() {
printf("<%lld> start\n", self());
for (int i = 0; i < 5; i++) {
printf("<%lld> %d\n", self(), i);
yield();
}
printf("<%lld> end\n", self());
}
void example() {
printf("<%lld> main start\n", self());
for (int i = 0; i < 5; i++) {
spawn(printer);
yield();
}
printf("<%lld> main end\n", self());
}
int main() {
scheduler_init(2);
run(example);
puts("done");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment