Skip to content

Commit

Permalink
Simplify scheduling and fix several bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
kurtjd committed Aug 22, 2023
1 parent dffeb8a commit 41a91cc
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 146 deletions.
5 changes: 2 additions & 3 deletions app.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ int main(void) {

os_init();
os_task_create(print_task, "I just print", task_stack, TASK_STACK_SZ, 1);
os_task_create(mutex_task, "MutA", task_stack, TASK_STACK_SZ, 2);
os_task_create(mutex_task, "MutB", task_stack, TASK_STACK_SZ, 2);
os_task_create(mutex_task, "MutC", task_stack, TASK_STACK_SZ, 2);
os_task_create(mutex_task, "Mutex A", task_stack, TASK_STACK_SZ, 2);
os_task_create(mutex_task, "Mutex B", task_stack, TASK_STACK_SZ, 2);

os_start();

Expand Down
213 changes: 74 additions & 139 deletions daedalus_os.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,7 @@
static struct os_tcb tasks[MAX_NUM_TASKS];
static int task_count = 0;
static struct os_tcb *running_task = NULL;

// Helps quickly track which priorities have ready tasks
static struct os_tcb *priority_list[MAX_PRIORITY_LEVEL];
static uint16_t ready_group;
static uint16_t ready_table[PRIORITY_TABLE_SZ];
static struct os_tcb *ready_list[MAX_PRIORITY_LEVEL];

// Related to the idle task
static os_task_stack idle_os_task_stack[IDLE_TASK_STACK_SZ];
Expand All @@ -27,131 +23,61 @@ static void os_idle_task_entry(void *data) {
printf("Idling\n");
}

// TODO: Merge these list functions
static void os_task_insert_priority_list(struct os_tcb *task) {
if (!priority_list[task->priority]) {
priority_list[task->priority] = task;
} else {
struct os_tcb *next = priority_list[task->priority]->next_task;
priority_list[task->priority]->next_task = task;
task->next_task = next;
}
}

static void os_task_insert_block_list(struct os_tcb *task, struct os_tcb **block_list) {
if (!*block_list) {
*block_list = task;
static void os_task_insert_into_list(struct os_tcb *task, struct os_tcb **list) {
if (!*list) {
*list = task;
} else {
/*struct os_tcb *next = (*block_list)->next_blocked;
(*block_list)->next_blocked = task;
task->next_blocked = next;*/
(*block_list)->prev_blocked = task;
task->next_blocked = *block_list;
*block_list = task;
(*list)->prev_task = task;
task->next_task = *list;
*list = task;
}
}

static void os_task_remove_block_list(struct os_tcb *task, struct os_tcb **block_list) {
if (task->next_blocked)
task->next_blocked->prev_blocked = task->prev_blocked;
static void os_task_remove_from_list(struct os_tcb *task, struct os_tcb **list) {
if (task->next_task)
task->next_task->prev_task = task->prev_task;

if (task->prev_blocked)
task->prev_blocked->next_blocked = task->next_blocked;
if (task->prev_task)
task->prev_task->next_task = task->next_task;
else
*block_list = task->next_blocked;
*list = task->next_task;

task->next_blocked = NULL;
task->prev_blocked = NULL;
}

static void os_ready_list_set(uint8_t priority) {
// Find which bit in the ready group the priority of the task sits
uint16_t ready_group_idx = priority >> 4;

// Set that bit to indicate a priority in this index is ready
ready_group |= (1 << ready_group_idx);

// Finally set the bit to indicate this specific priority is ready
ready_table[ready_group_idx] |= (1 << (priority & 0xF));
}

static void os_ready_list_clear(uint8_t priority) {
struct os_tcb *task = priority_list[priority];
assert(task != NULL); // We shouldn't ever call this on a priority that has no tasks assigned to it

// Have to be sure there are no tasks at this priority level ready to run
while (task) {
if (task->state == TASK_READY)
return;
task = task->next_task;
}

uint16_t ready_group_idx = priority >> 4;
ready_table[ready_group_idx] &= ~(1 << (priority & 0xF));

// No priorities in this group are ready
if (ready_table[ready_group_idx] == 0)
ready_group &= ~(1 << ready_group_idx);
task->next_task = NULL;
task->prev_task = NULL;
}

static void os_task_set_state(struct os_tcb *task, enum OS_TASK_STATE state) {
task->state = state;

switch (state) {
case TASK_READY:
os_ready_list_set(task->priority);
os_task_insert_into_list(task, &ready_list[task->priority]);
break;
case TASK_BLOCKED:
running_task = NULL;
os_ready_list_clear(task->priority);
break;
case TASK_RUNNING:
os_ready_list_clear(task->priority);
running_task = NULL; // Consider if need to do this
os_task_remove_from_list(task, &ready_list[task->priority]);
break;
}
}

static struct os_tcb *os_get_next_ready_task(void) {
// No task is ready to run
if (ready_group == 0)
return NULL;

// First find the highest priority group that has a priority ready
int priority = 0;
int tmp = ready_group;
while (tmp >>= 1)
priority++;

// Then find the highest priority that has a task ready
tmp = ready_table[priority];
assert(tmp != 0); // Shouldn't have a group ready but no priorities in that group ready
priority *= PRIORITY_GROUP_WIDTH;
while (tmp >>= 1)
priority++;

// If the running task is the highest priority task, abort
if (running_task && priority < running_task->priority)
return NULL;

// Now find task associated with that priority that is ready
struct os_tcb *task;
if (running_task && priority == running_task->priority && running_task->next_task)
task = running_task->next_task;
else
task = priority_list[priority];
assert(task); // Should have at least one task associated with that priority

while (task->state != TASK_READY) {
task = task->next_task;
static uint8_t os_get_highest_ready_pri(void) {
for (int i = MAX_PRIORITY_LEVEL; i >= 0; i--) {
if (ready_list[i])
return i;
}

// Should always find a task that is ready if priority is greater than running task priority
assert(task || (running_task && priority == running_task->priority));
// Shouldn't get here...
assert(false);
return 0;
}

if (!task)
task = priority_list[priority];
}
static struct os_tcb *os_get_next_ready_task(uint8_t priority) {
// Allows for round-robin
if (running_task && running_task->priority == priority && running_task->next_task)
return running_task->next_task;

return task;
// Return NULL if the next task is the same as running task so context-switch doesn't happen
return running_task != ready_list[priority] ? ready_list[priority] : NULL;
}

static void os_sw_context(struct os_tcb *next_task) {
Expand All @@ -165,15 +91,16 @@ static void os_sw_context(struct os_tcb *next_task) {
static void os_schedule(void) {
OS_ENTER_CRITICAL();

struct os_tcb *next_task = os_get_next_ready_task();
uint8_t highest_ready_pri = os_get_highest_ready_pri();
struct os_tcb *next_task = os_get_next_ready_task(highest_ready_pri);

// Make sure there is a higher priority task that's ready
if (next_task) {
os_task_set_state(next_task, TASK_RUNNING);
/*os_task_set_state(next_task, TASK_RUNNING);
// Running task may have previously been set to blocked if scheduler was called through a sync function
if (running_task)
os_task_set_state(running_task, TASK_READY);
os_task_set_state(running_task, TASK_READY);*/

running_task = next_task;
os_sw_context(next_task);
Expand All @@ -190,13 +117,7 @@ static void os_mutex_assign(struct os_mutex *mutex, struct os_tcb *task) {
mutex->holding_task_orig_pri = task->priority;
}

static void os_task_wake(struct os_tcb *task) {
task->waiting = false;
task->timeout = 0;
os_task_set_state(task, TASK_READY);
}

static void ClockTick(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
static void os_tick(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
(void)hwnd;
(void)uMsg;
(void)idEvent;
Expand All @@ -205,11 +126,12 @@ static void ClockTick(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
// Naive approach, optimize later (don't want to have to loop over every task, only tasks in a timeout list)
for (int i = 0; i < task_count; i++) {
struct os_tcb *task = &tasks[i];
if (task->timeout > 0)
if (task->timeout > 0) {
task->timeout--;

if (task->timeout == 0)
os_task_set_state(task, TASK_READY);
if (task->timeout == 0)
os_task_set_state(task, TASK_READY);
}
}

os_schedule();
Expand All @@ -221,12 +143,10 @@ void os_init(void) {
}

void os_start(void) {
//os_schedule();

// Enable SysTick interrupt

// But for now use hacky Windows timer
SetTimer(NULL, 0, 1000 / CLOCK_RATE_HZ, ClockTick);
SetTimer(NULL, 0, 1000 / CLOCK_RATE_HZ, os_tick);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
Expand All @@ -244,17 +164,14 @@ uint8_t os_task_create(os_task_entry entry, void *arg, os_task_stack *stack_base
.stack_sz = stack_sz,
.priority = priority,
.next_task = NULL,
.next_blocked = NULL,
.prev_blocked = NULL,
.prev_task = NULL,
.timeout = 0,
.waiting = false,
.id = task_count
};

tasks[task_count] = task;
os_task_insert_priority_list(&tasks[task_count]);
os_task_set_state(&tasks[task_count], TASK_READY);
task_count++;
os_task_set_state(&tasks[task_count++], TASK_READY);

// Push entry as PC onto task's stack
// Push stack_base+stack_sz as SP onto task's stack
Expand All @@ -272,13 +189,32 @@ void os_task_sleep(uint16_t ticks) {
os_schedule();
}

static void os_task_wake(struct os_tcb *task) {
OS_ENTER_CRITICAL();
task->waiting = false;
task->timeout = 0;
os_task_set_state(task, TASK_READY);
OS_EXIT_CRITICAL();

os_schedule();
}

void os_task_yield(void) {
// For now just immediately call scheduler
// Yielding has little use in preemptive RTOS as highest priority task is already running
// However useful if you have roud-robin tasks and want to immediately call the next one
os_schedule();
}

void os_task_change_priority(struct os_tcb *task, uint8_t new_priority) {
/*if (task->state == TASK_READY) {
os_task_remove_from_list(task, &ready_list[task->priority]);
os_task_insert_into_list(task, &ready_list[new_priority]);
}*/
assert(task->state != TASK_READY);
task->priority = new_priority;
}

const struct os_tcb *os_task_query(uint8_t task_id) {
return &tasks[task_id];
}
Expand All @@ -293,12 +229,13 @@ bool os_mutex_acquire(struct os_mutex *mutex, uint16_t timeout_ticks) {
os_mutex_assign(mutex, running_task);
return true;
} else {
// TODO: Handle priority inheritance (make holding task pri = running task pri)
// Priority inheritance: Set holding task to running task's priority
os_task_change_priority(mutex->holding_task, running_task->priority);
running_task->waiting = true;

os_task_insert_block_list(running_task, &mutex->blocked_list);
os_task_insert_into_list(running_task, &mutex->blocked_list);
os_task_sleep(timeout_ticks);
os_task_remove_block_list(running_task, &mutex->blocked_list);
os_task_remove_from_list(running_task, &mutex->blocked_list);

// Task either gets here via timeout or woken by mutex release
// Commented out until context switch implemented
Expand All @@ -313,24 +250,22 @@ bool os_mutex_acquire(struct os_mutex *mutex, uint16_t timeout_ticks) {
}

void os_mutex_release(struct os_mutex *mutex) {
// Set holding task back to original priority
os_task_change_priority(mutex->holding_task, mutex->holding_task_orig_pri);
mutex->holding_task = NULL;
// TODO: Handle resetting priority back to original

// Wanted to use tables like for readying tasks but tasks sharing priorities makes that difficult...
struct os_tcb *task = mutex->blocked_list;
if (task) {
uint8_t high_pri = 0;
struct os_tcb *high_task = NULL;
struct os_tcb *high_task = task;

// Find the highest priority task waiting on the mutex then wake it
while (task) {
if (task->priority >= high_pri) {
high_pri = task->priority;
if (task->priority >= high_task->priority) {
high_task = task;
task = task->next_blocked;
task = task->next_task;
}
}

os_task_wake(high_task);
os_schedule();
}
}
6 changes: 2 additions & 4 deletions daedalus_os.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ typedef uint32_t os_task_stack;
/* Enums */
enum OS_TASK_STATE {
TASK_BLOCKED,
TASK_READY,
TASK_RUNNING
TASK_READY
};

/* Structs */
Expand All @@ -43,8 +42,7 @@ struct os_tcb {
uint8_t priority;
enum OS_TASK_STATE state;
struct os_tcb *next_task;
struct os_tcb *next_blocked;
struct os_tcb *prev_blocked;
struct os_tcb *prev_task;
uint16_t timeout;
bool waiting;
uint8_t id;
Expand Down

0 comments on commit 41a91cc

Please sign in to comment.