BruceChen7 / gitblog

My blog
6 stars 1 forks source link

libtask解读 #5

Open BruceChen7 opened 4 years ago

BruceChen7 commented 4 years ago

参考资料

解析

在libtask中,虽说是一个库,但是其包含了一个main函数,这和常规的库有点不一样,看看使用libtask的特点:

 /*                                                
  * gcc count_test.c  -o count_test -L. -ltask -I./
  */                                               
 #include <stdio.h>                                
 #include <stdlib.h>                               
 #include <unistd.h>                               
 #include <task.h>                                 
 void                                              
 counttask1(void *arg)                             
 {                                                 
     int i;                                        
     for( i = 0; i < 5; i++) {                     
         printf("task1: %d\n", i);                 
         taskyield();                              
     }                                             
 }                                                 
 void                                              
 counttask2(void *arg)                             
 {                                                 
     int i;                                        
     for( i = 5; i < 10; i++) {                    
         printf("task2: %d\n", i);                 
         taskyield();                              
     }                                             
 }                                                 
 void                                              
 taskmain(int argc, char **argv)                   
 {                                                 
     taskcreate(counttask1, NULL, 32768);          
     taskcreate(counttask2, NULL, 32768);          
 }                                                 

在注释中,给出了编译的方式,也就是说我们在利用libtask来做自己的应用程序的时候,需要自己从taskmain函数开始。

libtask中的main

该main函数的作用,调用taskmain作为第一个执行的coroutine,调度整个coroutine链。

int
main(int argc, char **argv)
{
    struct sigaction sa, osa;

    // 信号处理函数
    memset(&sa, 0, sizeof sa);
    sa.sa_handler = taskinfo;
    sa.sa_flags = SA_RESTART;
    sigaction(SIGQUIT, &sa, &osa);

#ifdef SIGINFO
    sigaction(SIGINFO, &sa, &osa);
#endif

    argv0 = argv[0];
    // 都是全局变量
    taskargc = argc;
    taskargv = argv;

    // main coroutine
    if(mainstacksize == 0)
        mainstacksize = 256*1024;
    // 创建main任务
    // 25k的栈空间
    taskcreate(taskmainstart, nil, mainstacksize);
    // 开始执行调度
    taskscheduler();
    // 不可能出现
    fprint(2, "taskscheduler returned in main!\n");
    abort();
    return 0;
}

static void
taskmainstart(void *v)
{
    taskname("taskmain");
    taskmain(taskargc, taskargv);
}

main函数在执行完taskmain routine后,就开始执行taskscheduler,这个是整个libtask的核心,

taskscheduler


 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);
         // 切到新的context
         contextswitch(&taskschedcontext, &t->context);
         taskrunning = nil;
         // 进行资源释放
         if(t->exiting){
             // 减少当前任务列表
             if(!t->system)
                 // 如果不是系统任务,那么直接将任务数-1
                 taskcount--;
             i = t->alltaskslot;
             alltask[i] = alltask[--nalltask];
             alltask[i]->alltaskslot = i;
             free(t);
         }
     }
 }

在这里我们搜索下taskschedcontext,这个变量:

task.c|15| Context taskschedcontext;
task.c|190| contextswitch(&taskrunning->context, &taskschedcontext);
task.c|278| contextswitch(&taskschedcontext, &t->context);

跟踪一下该变量的使用,知道其是当前main coroutine的执行上下文,当从main coroutine中的调度器调度到别的coroutine上下文的时候,保存到taskschedcontext中(swapcontext中有有这个作用),这是个全局变量。

contextswitch

在linux中的uontext.h,提供的getcontext,setcontext,swapcontext和makecontext函数,具体的上下文有结构体ucontext_t指定,包括任务的栈的大小,栈顶指针以及cpu内寄存器信息

我们看下contextswitch的实现

static void
contextswitch(Context *from, Context *to)
{
    // 直接使用swapcontext来执行上下文切换
    // 保存当前上下文到from,并执行to上下文
    // 当执行完毕后,跳转到from上下文中
    if(swapcontext(&from->uc, &to->uc) < 0){
        fprint(2, "swapcontext failed: %r\n");
        assert(0);
    }
}