概述

最近在研究协程的实现原理,看了云风的coroutine和腾讯的开源库libco后,原来要实现一个协程库也没那么难。我先来讲讲云风的coroutine库。他使用的是 uncontext来保存程序运行上下文,进而实现协程库,这个库很值深入了解一番,吃透了这个库,协程的原理也就了解了。

头文件

#include <ucontext.h>

ucontext结构体

typedef struct ucontext
  {
    unsigned long int uc_flags;
    struct ucontext *uc_link;
    stack_t uc_stack;
    mcontext_t uc_mcontext;
    __sigset_t uc_sigmask;
    struct _libc_fpstate __fpregs_mem;
  } ucontext_t;
  • sigset_tstack_t 定义在标准头文件 <signal.h>
  • uc_link字段保存当前context结束后继续执行的context记录;
  • uc_sigmask 记录该context运行阶段需要屏蔽的信号;
  • uc_stack 是该context运行的栈信息,
  • uc_mcontext 则保存具体的程序执行上下文——如PC值、堆栈指针、寄存器值等信息

操作ucontext的四个函数

getcontext()  : 获取当前context
setcontext()  : 切换到指定context
makecontext() : 设置 函数指针 和 堆栈 到对应context保存的sp和pc寄存器中,调用之前要先调用 getcontext()
swapcontext() : 保存当前context,并且切换到指定context

通过getcontext和swapcontext实现流程控制

代码如下

int main()
{
	ucontext_t ctx, main;
	int done = 1;
	getcontext(&ctx); 
	if (done > 5)
	{<!-- -->
		printf("ctx done\n");
		swapcontext(&ctx, &main); //切换到指定的main
	}
	printf("done: %d\n", done++);
	swapcontext(&main, &ctx);  //保存当前上下文`main`,并且切换到指定`ctx`

	printf("return 1\n");
	return 1;
}

具体步骤如下:

  • 获取当前上下文: getcontext(&ctx); 判断 done>5,
  • 如果为假则保存当前上下文main,并且切换到指定ctx, swapcontext(&main, &ctx)
  • 如果为真则切换上下文: swapcontext(&ctx, &main);

输出如下:

done: 1
done: 2
done: 3
done: 4
done: 5
ctx done
return 1

使用uncontet写一个消费者和生产者的程序

步骤如下:

  • 生产者,产生一个值,保存当前上下文,切换到消费者上下文- 消费者,输出生产者传递过来的值。保存消费者的当前上下文,切换到生产者 上下文切换已经做了标记,代码如下:
//procus.c
#include <stdlib.h>
#include <stdio.h>
#include <ucontext.h>
#include <stddef.h>

#include <sys/types.h>

#define STACK_SIZE (1024*1024)

struct args {<!-- -->
	int n;
	int send;
	ucontext_t*  ctx;
	ucontext_t*  main_ctx;
};


void product(void *arg)
{<!-- -->
	struct args* a = (struct args*)arg;
	a->send = 1;
	while (a->n<5)
	{<!-- -->
		printf("a->n: %d\n", a->n++);
		swapcontext(a->ctx, a->main_ctx); //标记1, 保存当前上下文到a->ctx, 切换到a->main_ctx, 跳转到标记4
	}
	a->send = 0;
	printf("send is 0: %d\n", a->n++);
	swapcontext(a->ctx, a->main_ctx);     //标记2,保存当前上下文到a->ctx, 切换到a->main_ctx, 跳转到标记4
}

void customer(void *arg)
{<!-- -->

	struct args* a = (struct args*)arg;
	while (a->send==1)
	{<!-- -->
		printf("n: %d\n", a->n);
		swapcontext(a->main_ctx, a->ctx); //标记3,保存当前上下文到a->main_ctx, 切换到a->ctx, 跳转到标记1
	}
	
}


int main()
{<!-- -->
	ucontext_t ctx, main_ctx;
	getcontext(&ctx);
	struct args a;
	a.n  = 1;
	a.ctx = &ctx;
	a.main_ctx = &main_ctx;

	ctx.uc_stack.ss_sp = malloc(STACK_SIZE);  //分配一块空间
	ctx.uc_stack.ss_size = STACK_SIZE;
	ctx.uc_link = a.main_ctx;

	makecontext(&ctx, product, 1, &a); //指定上下文与对应的生产者函数
	swapcontext(a.main_ctx, &ctx);	   //标记4, 保存当前上下文到a.main_ctx, 切换到ctx,跳转到product函数
	customer(&a);					   //消费者函数

	printf("finally return\n");
	return 0;
}

编译,运行,输出如下:

$ gcc -g -Wall -o procus procus.c
$ ./procus
a->n: 1
n: 2
a->n: 2
n: 3
a->n: 3
n: 4
a->n: 4
n: 5
send is 0: 5
finally return

--完--