最近在看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里详细调用情况后面补充……