Monday, January 29, 2007

Stack-less multi-task system (2) -- implementation

The basic idea behind implementation:
  1. Replace implicit C stack with explict link stack.
  2. Use label to record PC.
  3. Store task state in heap instead of stack.
  4. Only functions will sleep directly or indirectly need to deal with stack explictly, other functions aren't affected.
Performance comparison:
  1. Task stack space is saved, only one stack is needed.
  2. Task switching time may be a little longer because of task swithing executing path will go through all functions invoked before sleep. But, if the task is small enough and function invoking depth is short, the task switching time will be affordable.
Sample Code:

#include <stdlib.h>
#include <assert.h>
#include <string.h>

struct stack
{
short stk_label;
void *stk_data;
struct stack *stk_prev;
struct stack *stk_next;
};

#define TASK_STATE_RUN 0
#define TASK_STATE_SLEEP 1

struct task
{
short tsk_state;
void (*tsk_entry)(void);
struct stack tsk_stk;
struct stack *tsk_stk_curr;
};

extern struct task tasks[];
struct task *task_curr;

void sleep(void)
{
task_curr->tsk_state = TASK_STATE_SLEEP;
}

void wakeup(int task_id)
{
tasks[task_id].tsk_state = TASK_STATE_RUN;
}

/* return code */
#define RC_SUCCESS 0
#define RC_FAILED 1
#define RC_PENDING 2

void push_stack()
{
if (!task_curr->tsk_stk_curr->stk_next) {
struct stack *stk;

stk = malloc(sizeof(struct stack));
assert(stk);
memset(stk, 0, sizeof(*stk));
stk->stk_prev = task_curr->tsk_stk_curr;
task_curr->tsk_stk_curr->stk_next = stk;
}
task_curr->tsk_stk_curr = task_curr->tsk_stk_curr->stk_next;
}

void pop_stack()
{
struct stack *stk;

stk = task_curr->tsk_stk_curr->stk_prev;
assert(stk->stk_next->stk_next == NULL);
free(stk->stk_next);
stk->stk_next = NULL;
task_curr->tsk_stk_curr = stk;
}

#define TEST_TASK_LABEL1 0
#define TEST_TASK_LABEL2 1
#define TEST_TASK_LABEL3 2

#define TEST_TASK_SUB_LABEL1 0
#define TEST_TASK_SUB_LABEL2 1

void test_task_label1(void)
{
/* everything as normal, without sleep */
}

int test_task_label2(void)
{
int rc = RC_SUCCESS;

push_stack();

switch (task_curr->tsk_stk_curr->stk_label) {
case TEST_TASK_SUB_LABEL1:
task_curr->tsk_stk_curr->stk_label = TEST_TASK_SUB_LABEL2;
rc = RC_PENDING;
sleep();
break;
case TEST_TASK_SUB_LABEL2:
break;
}
if (rc != RC_PENDING)
pop_stack();
return rc;
}

void test_task(void)
{
switch (task_curr->tsk_stk.stk_label) {
case TEST_TASK_LABEL1:
test_task_label1();
task_curr->tsk_stk.stk_label = TEST_TASK_LABEL2;
task_curr->tsk_stk.stk_data = malloc(10);
sleep();
break;
case TEST_TASK_LABEL2:
if (test_task_label2() == RC_PENDING) {
break;
}
task_curr->tsk_stk.stk_label = TEST_TASK_LABEL3;
case TEST_TASK_LABEL3:
break;
default:
assert(0);
break;
}
}

void test_task2(void)
{
wakeup(0);
}

struct task tasks[2] =
{
{
TASK_STATE_RUN,
test_task,
{ 0, NULL, NULL, NULL },
NULL,
},
{
TASK_STATE_RUN,
test_task2,
{ 0, NULL, NULL, NULL},
NULL,
},
};

void schedule(void)
{
int i;

for (i = 0; i < 2; i++) {
if (tasks[i].tsk_state == TASK_STATE_RUN) {
task_curr = &tasks[i];
task_curr->tsk_stk_curr = &task_curr->tsk_stk;
tasks[i].tsk_entry();
}
}
}

int main(int argc, char *argv[])
{
for (;;)
schedule();
return 0;
}

No comments: