Libtask is a simple coroutine library. It runs on Linux (ARM, MIPS, and x86),FreeBSD (x86), OS X (PowerPC x86, and x86-64), and SunOS Solaris (Sparc),and is easy to port to other systems.
Libtask gives the programmer the illusion of threads, but the operating system sees only a single kernel thread.For clarity, we refer to the coroutines as "tasks," not threads.
(一)中主要分析协程正常执行调度流程。
libtask里面有两个关键函数taskcreate和taskscheduler
1. taskceate
调用taskcreate初始化一个新的task结构体,并插入到任务队列,设置状态为ready,准备被调度。taskcreate中传递function函数指针和参数,真正被调度时该函数将被执行。
int
taskcreate(void (*fn)(void*), void *arg, uint stack)
{
int id;
Task *t;
t = taskalloc(fn, arg, stack);
taskcount++;
id = t->id;
if(nalltask%64 == 0){
alltask = realloc(alltask, (nalltask+64)*sizeof(alltask[0]));
if(alltask == nil){
fprint(2, "out of memory\n");
abort();
}
}
t->alltaskslot = nalltask;
alltask[nalltask++] = t;
taskready(t);
return id;
}
在taskalloc中调用一个getcontext 和 makecontext. makecontext修改通过getcontext取得的上下文ucp(这意味着调用makecontext前必须先调用getcontext),给该上下文指定一个栈空间ucp->stack,设置后继的上下文ucp->uc_link. (如果没有设置后继上下文的话,函数执行完毕将直接退出,libtask里面没有指定uc_link),除此之外,makecontext需传入func函数和所需参数。
当在taskscheduler函数中通过调用swapcontext or setcontext该上下文被激活时,makecontext传入的taskstart被执行。
if(getcontext(&t->context.uc) < 0){
fprint(2, "getcontext: %r\n");
abort();
}
..........
makecontext(&t->context.uc, (void(*)())taskstart, 2, y, x);
taskstart里面主要做了两件事,执行startfn(真正的任务,taskcreate时传入的handle function),调用taskexit将上下文切换到taskschedcontext
static void
taskstart(uint y, uint x)
{
Task *t;
ulong z;
z = x<<16; /* hide undefined 32-bit shift from 32-bit compilers */
z <<= 16;
z |= y;
t = (Task*)z;
t->startfn(t->startarg);
taskexit(0);
}
2.taskscheduler
该函数一直循环从任务队列中读取任务t 。调用contextswitch保存当前上下文到taskschedcontext,切换上下文到task context ,执行在任务创建时makecontext传入的taskstart函数。任务函数执行完毕后,主动切换回来,恢复当前上下文,进入下一轮循环。
static void
taskscheduler(void)
{
int i;
Task *t;
taskdebug("scheduler enter");
for(;;){
if(taskcount == 0)
exit(taskexitval);
t = taskrunqueue.head;
if(t == nil){
fprint(2, "no runnable tasks! %d tasks stalled\n", taskcount);
exit(1);
}
deltask(&taskrunqueue, t);
t->ready = 0;
taskrunning = t;
tasknswitch++;
taskdebug("run %d (%s)", t->id, t->name);
contextswitch(&taskschedcontext, &t->context);
taskrunning = nil;
if(t->exiting){
if(!t->system)
taskcount--;
i = t->alltaskslot;
alltask[i] = alltask[--nalltask];
alltask[i]->alltaskslot = i;
free(t);
}
}
}