Qemu 里的协程


最近在看qemu源码,跟之前看spice-gtk一样,困扰我的不是语言本身,而是语言里的机制,跟spice-gtk里用了大量回调不同(其实也有协程),在qemu里充斥着协程(coroutine)。这篇文章不是分析qemu的,主要记录下我对协程的一些理解,以便日后回忆……


先来说说进程/线程/协程这几个概念的区别和联系,从需求出发分析 long long ago,计算机处理问题方式还很单一,就是顺序执行,效果可想而知,很多时效性比较强的任务被时间长的任务耽误了…… 于是出现了进程,一个进程就是一个逻辑处理单元的上下文,在单处理器时代,进程其实也是依次顺序执行,只是可以再多个任务间快速切换了,让人看起来像是一起跑的而已

但是,要明白这里的切换同时会伴随者上下文的切换,这是个很费时费力的事,特别是要处理的任务多起来以后。于是,出现了线程,线程跟进程很像,只是多个线程之间的上下文很多是共享的,切换的代价就会小了很多 从操作系统来看,其实不管是进程还是线程的切换,都是遇到阻塞或者时间片用尽了之类的情况,切换的时候,首先陷入内核,由内核安排好后把执行资源交给另一个进程,这样看来,似乎省了不少事,操作系统帮干了很多事……但这样在某些情况下就不能充分利用cpu了,太多时间花在了等待及切换上面了 那有没有种方式可以更充分利用资源?有,那就是任务切换的时候不经过内核,就直接在用户态干了,而且是由用户自己安排调度,这种机制就演化成了协程。注意:协程之间的切换都是明确的

协程切换也会有上下文信息的保护,但是不是通过像进程线程那样的虚地址,而是在用户空间申请的一个栈空间临时保存,因为协程的切换一直在用户空间,所以这样也能达到进程线程的效果

那么,协程有什么问题吗? a)协程在smp下无法充分发挥性能,因为操作系统压根意思不到协程的存在,它就是个普通的进程而已! b)协程需要用户自己处理调度问题,需要程序员素质高点


C语言中没有原生的协程支持,不过可以通过ucontext组件模拟,下面是个简单的例子

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <ucontext.h>

ucontext_t main_ctx, producer_ctx, consumer_ctx;

char producer_stack[1024];
char consumer_stack[1024];

void producer()
{
        printf("init producer\n");
        for(;;){
                printf("producer\n");
                sleep(1);
                swapcontext(&producer_ctx, &main_ctx);
        }
}
void consumer()
{
        printf("init consumer\n");
        for(;;){
                printf("consumer\n");
                sleep(1);
                swapcontext(&consumer_ctx, &main_ctx);
        }
}

int main()
{
        getcontext(&producer_ctx);
        producer_ctx.uc_stack.ss_sp = producer_stack;
        producer_ctx.uc_stack.ss_size = sizeof(producer_stack);
        producer_ctx.uc_link = &main_ctx;
        makecontext(&producer_ctx, producer, 0);

        getcontext(&consumer_ctx);
        consumer_ctx.uc_stack.ss_sp = consumer_stack;
        consumer_ctx.uc_stack.ss_size = sizeof(consumer_stack);
        consumer_ctx.uc_link = &main_ctx;
        makecontext(&consumer_ctx, consumer, 0);

        int i;
        for(i = 0; i < 100000; ++i){
                printf("main\n");
                if(i%2)
                        swapcontext(&main_ctx, &producer_ctx);
                else
                        swapcontext(&main_ctx, &consumer_ctx);
        }
        return 0;
}

其中printf("init producer\n");printf("init consumer\n");只执行一次,因为上下文切换后是接着执行!


在qemu里,协程的实现主要通过sigsetjmp和siglongjmp实现,协程相关声明在include\block\coroutine,比较重要的几个接口(其它见文件注释): typedef void coroutine_fn CoroutineEntry(void *opaque); opaque指向协程临时数据保存区? Coroutine *qemu_coroutine_create(CoroutineEntry *entry); 创建协程,类似pthread_create之类的

void qemu_coroutine_enter(Coroutine *coroutine, void *opaque);

开始执行协程程序 void coroutine_fn qemu_coroutine_yield(void); 转移执行权

暂时这样,qemu里详细调用情况后面补充……