Skip to main content

Coroutine (coroutine)

A cooperative multitasking coroutine system based on assembly-level context switching, supporting multiple architectures and providing lightweight concurrent execution.

Data Structures

Coroutine

struct coroutine_t {
struct scheduler_t * sched; /* Owning scheduler */
struct list_head_t list; /* Ready list node */
void * fctx; /* Coroutine context (fcontext) */
void * stack; /* Coroutine stack space */
void (*func)(struct scheduler_t *, void *); /* Coroutine entry function */
void * data; /* User data */
};

Scheduler

struct scheduler_t {
struct list_head_t ready; /* Ready coroutine list */
struct coroutine_t * running; /* Currently running coroutine */
void * fctx; /* Scheduler context */
};

How It Works

The coroutine system uses cooperative scheduling. Each coroutine voluntarily calls coroutine_yield() to relinquish execution, and the scheduler selects the next coroutine from the ready list to run.

Context Switching

  • The underlying implementation uses the XOS abstraction layer's xos_coroutine_make() and xos_coroutine_jump()
  • xos_coroutine_make() creates a coroutine context on the specified stack space and sets the entry function
  • xos_coroutine_jump() performs context switching, passing a co_transfer_t structure to save the return context
  • Each architecture (ARM32/ARM64/RISC-V/x64) provides its own assembly implementation

Scheduling Flow

  1. scheduler_init() initializes the scheduler with an empty ready list
  2. coroutine_start() creates a coroutine, allocates stack space, and appends it to the ready list
  3. scheduler_loop() starts scheduling by jumping to the first ready coroutine
  4. During execution, a coroutine calls coroutine_yield() to relinquish the CPU; the scheduler switches to the next ready coroutine
  5. When a coroutine function returns, it is automatically removed from the list and its resources are freed, then the scheduler switches to the next coroutine or returns to the scheduler

API

FunctionDescription
scheduler_init(sched)Initialize the scheduler
scheduler_loop(sched)Start the scheduling loop, blocks until all coroutines finish
coroutine_start(sched, func, data, stksz)Create and start a coroutine, stksz is the stack size (default 4096 bytes)
coroutine_yield(sched)Relinquish execution, switch to the next ready coroutine
coroutine_msleep(sched, ms)Sleep for the specified milliseconds (based on yield polling)
coroutine_usleep(sched, us)Sleep for the specified microseconds
coroutine_nsleep(sched, ns)Sleep for the specified nanoseconds
coroutine_self(sched)Get the currently running coroutine pointer (inline function)

Usage Examples

Basic Coroutine

#include <kernel/core/coroutine.h>

static void worker(struct scheduler_t * sched, void * data)
{
const char * name = (const char *)data;
for(int i = 0; i < 3; i++)
{
LOG("%s: step %d\n", name, i);
coroutine_msleep(sched, 100); /* Sleep 100ms */
}
}

void demo(void)
{
struct scheduler_t sched;
scheduler_init(&sched);

coroutine_start(&sched, worker, "task-A", 4096);
coroutine_start(&sched, worker, "task-B", 4096);

scheduler_loop(&sched); /* Blocks until all coroutines finish */
}

Coroutine Cooperation

static void producer(struct scheduler_t * sched, void * data)
{
struct cochannel_t * ch = (struct cochannel_t *)data;
const char * msg = "hello";
cochannel_send(sched, ch, (unsigned char *)msg, 5);
}

static void consumer(struct scheduler_t * sched, void * data)
{
struct cochannel_t * ch = (struct cochannel_t *)data;
char buf[16] = {0};
cochannel_recv(sched, ch, (unsigned char *)buf, 5);
LOG("received: %s\n", buf);
}

Notes

  • Coroutines use cooperative scheduling with no preemption; coroutines must voluntarily call yield or sleep to relinquish execution
  • Sleep functions (msleep/usleep/nsleep) are implemented via ktime_get() and yield polling; precision depends on scheduling frequency
  • When a coroutine function returns, its stack space and structure are automatically freed; no manual cleanup is required
  • scheduler_loop() automatically returns when all coroutines have finished
  • Coroutines depend on platform assembly implementations; use xstar_feature_coroutine() to check if the current platform supports coroutines