Linux POSIX coroutine初步

2014-12-31 wcdj


Linux POSIXcoroutine初步

目录

1 背景介绍...1

2 代码实践...4

 

 

摘要:开发同学在设计高性能后台服务时会优先考虑异步的执行方式,而目前异步执行方式主要有基于事件驱动的状态机模型、coroutine协程模型、Future/Promise模型。很多现代编程语言都提供了原生的coroutine特性(同步代码,异步执行)。本文总结下Linux使用POSIXsetcontext系列调用以实现coroutine语义的方法。

1 背景介绍

在代码里使用context需要包含ucontext.h(/usr/include/ucontext.h)系统头文件。

Linux POSIX coroutine初步_第1张图片

此头文件列出了POSIX下关于context operation的interface。并且可以看到此头文件还会引用另一个头文件/usr/include/sys/ucontext.h。

补充说明:

(1) setcontext function transfers controlto the context in ucp. Execution continues from the point at which the contextwas stored in ucp. setcontext does not return.

(2) getcontext saves current context intoucp. This function returns in two possible cases: after the initial call, orwhen a thread switches to the context in ucp via setcontext or swapcontext. Thegetcontext function does not provide a return value to distinguish the cases(its return value is used solely to signal error), so the programmer must usean explicit flag variable, which must not be a register variable and must bedeclared volatile to avoid constant propagation or other compiler optimizations.

(3) makecontext function sets up analternate thread of control in ucp, which has previously been initialized usinggetcontext. The ucp.uc_stack member should be pointed to an appropriately sizedstack; the constant SIGSTKSZ is commonly used. When ucp is jumped to usingsetcontext or swapcontext, execution will begin at the entry point to thefunction pointed to bye func, with argc arguments as specified. When functerminates, control is returned to ucp.uc_link.

(4) swapcontext transfers control to ucpand saves the current execution state into oucp.

Linux POSIX coroutine初步_第2张图片

头文件/usr/include/sys/ucontext.h主要是定义了相关的Register和DS,用户需要关心的是Userlevel context的ucontext_t类型。

Linux POSIX coroutine初步_第3张图片

补充说明:

(1) uc_link points to the context whichwill be resumed when the current context exits, if the context was created withmakecontext (a secondary context).

(2) uc_stack is the stack used by thecontext.

(3) uc_mcontext stores execution state,including all registers and CPU flags, the instruction pointer, and the stackpointer. mcontext_t is an opaque type.

(4) uc_sigmask is used to store the set ofsignals blocked in the context.

2 代码实践

在了解了基础的概念后,下面就可以学习下某开源coroutine的具体实现。代码中添加了一些调试信息和备注。

coroutine的接口定义:

#ifndef C_COROUTINE_H
#define C_COROUTINE_H
#define COROUTINE_DEAD 0
#define COROUTINE_READY 1
#define COROUTINE_RUNNING 2
#define COROUTINE_SUSPEND 3
struct schedule;
typedef void (*coroutine_func)(struct schedule *, void *ud);
struct schedule * coroutine_open(void);
void coroutine_close(struct schedule *);
int coroutine_new(struct schedule *, coroutine_func, void *ud);
void coroutine_resume(struct schedule *, int id);
int coroutine_status(struct schedule *, int id);
int coroutine_running(struct schedule *);
void coroutine_yield(struct schedule *);
#endif

 coroutine的实现:

#include "coroutine.h"
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define STACK_SIZE (1024 * 1024)
#define DEFAULT_COROUTINE 16

struct coroutine;

// 调度器
struct schedule {
	char stack[STACK_SIZE];   // 所有协程的public stack
	ucontext_t main;          // 主线程的context
	int nco;                  // 当前启用的协程数量
	int cap;                  // 支持的协程数量
	int running;              // 协程id
	struct coroutine **co;    // 协程对象集
};

// 协程对象
struct coroutine {
	coroutine_func func;      // 每个协程的回调函数
	void *ud;                 // 每个协程的用户数据
	ucontext_t ctx;           // 每个协程的context
	struct schedule *sch;     // 每个协程从属的调度器
	ptrdiff_t cap;            // 每个协程private stack的最大分配空间
	ptrdiff_t size;           // 每个协程private stack的实际分配空间
	int status;               // 每个协程的当前运行状态
	char *stack;              // 协程的private stack
};

struct coroutine * 
_co_new(struct schedule *S , coroutine_func func, void *ud) {
	struct coroutine * co = malloc(sizeof(*co));
	co->func = func;
	co->ud = ud;
	co->sch = S;
	co->cap = 0;
	co->size = 0;
	co->status = COROUTINE_READY;
	co->stack = NULL;
	return co;
}

void
_co_delete(struct coroutine *co) {

	// 只释放协程自己动态分配的空间, 并注意释放顺序
	free(co->stack);
	free(co);
}

struct schedule * 
coroutine_open(void) {
	struct schedule *S = malloc(sizeof(*S));
	S->nco = 0;
	S->cap = DEFAULT_COROUTINE;
	S->running = -1;
	S->co = malloc(sizeof(struct coroutine *) * S->cap);
	memset(S->co, 0, sizeof(struct coroutine *) * S->cap);
	return S;
}

void 
coroutine_close(struct schedule *S) {
	int i;
	for (i = 0; i < S->cap; i++) {
		struct coroutine * co = S->co[i];
		if (co) {
			_co_delete(co);
		}
	}

	// 最后释放调度器
	free(S->co);
	S->co = NULL;
	free(S);
}

int 
coroutine_new(struct schedule *S, coroutine_func func, void *ud) {
	struct coroutine *co = _co_new(S, func , ud);
	if (S->nco >= S->cap) {
		int id = S->cap;
		S->co = realloc(S->co, S->cap * 2 * sizeof(struct coroutine *));
		memset(S->co + S->cap , 0 , sizeof(struct coroutine *) * S->cap);
		S->co[S->cap] = co;
		S->cap *= 2;
		++S->nco;
		return id;
	} else {
		int i;
		for (i = 0; i < S->cap; i++) {
			int id = (i+S->nco) % S->cap;
			if (S->co[id] == NULL) {
				S->co[id] = co;
				++S->nco;

				// 返回创建好的协程id
				return id;
			}
		}
	}
	assert(0);
	return -1;
}

static void
mainfunc(uint32_t low32, uint32_t hi32) {
	// resume param
	uintptr_t ptr = (uintptr_t)low32 | ((uintptr_t)hi32 << 32);
	struct schedule *S = (struct schedule *)ptr;
	int id = S->running;

	// debug
	printf("mainfunc: coroutine id[%d]\n", S->running);

	struct coroutine *C = S->co[id];
	C->func(S,C->ud);

	_co_delete(C);

	S->co[id] = NULL;
	--S->nco;
	S->running = -1;
}

void 
coroutine_resume(struct schedule * S, int id) {

	assert(S->running == -1);
	assert(id >= 0 && id < S->cap);

	struct coroutine *C = S->co[id];
	if (C == NULL)
		return;

	int status = C->status;
	switch(status) {

	case COROUTINE_READY:
		getcontext(&C->ctx);
		C->ctx.uc_stack.ss_sp = S->stack;       // stack top起始位置(SP)
		C->ctx.uc_stack.ss_size = STACK_SIZE;   // 用于计算stack bottom(数据从stack bottom开始存放)
		C->ctx.uc_link = &S->main;              // 协程执行完切回的context
		S->running = id;                        // 调度器记录当前准备调度的协程id
		C->status = COROUTINE_RUNNING;          // 将准备调度的协程状态置为"待运行状态"

		uintptr_t ptr = (uintptr_t)S;
		// 需要考虑跨平台指针大小不同的问题
		makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));
		// 开始执行mainfunc回调, 执行完继续fall through, 即执行下一个协程
		swapcontext(&S->main, &C->ctx);
		// debug
		printf("COROUTINE_READY: coroutine id[%d] return\n", S->running);
		break;

	case COROUTINE_SUSPEND:
		// stack从高地址向低地址生长, 即从stack bottom向stack top存储数据
		memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);
		S->running = id;
		C->status = COROUTINE_RUNNING;
		swapcontext(&S->main, &C->ctx);
		// debug
		sleep(2);
		printf("COROUTINE_SUSPEND: coroutine id[%d] return\n", S->running);
		break;

	default:
		assert(0);
	}

	printf("break switch: coroutine id[%d] return\n", S->running);
}

static void
_save_stack(struct coroutine *C, char *top) {

	// 在stack上创建一个局部变量, 标识当前栈顶(SP)的位置(低地址)
	char dummy  = 0;
	printf("_save_stack: &C[%p] top[%p] &dummy[%p] top - &dummy[%d] STACK_SIZE[%d]\n", &C, top, &dummy, top - &dummy, STACK_SIZE);

	// 检查stack是否有溢出
	assert(top - &dummy <= STACK_SIZE);

	// 按需保存当前协程的stack (begin)
	// 判断协程栈的空间是否足够, 若不够则重新分配
	if (C->cap < top - &dummy) {
		free(C->stack);
		C->cap = top - &dummy;
		C->stack = malloc(C->cap);
	}
	C->size = top - &dummy;
	memcpy(C->stack, &dummy, C->size);
	// 按需保存当前协程的stack (end)
}

void
coroutine_yield(struct schedule * S) {

	int id = S->running;
	// debug
	printf("coroutine_yield: coroutine id[%d] into\n", S->running);
	assert(id >= 0);

    // 注意makecontext()时指定ss_sp为S->stack, 当C->ctx执行时, 协程的栈是存储在S->stack上的, 即把堆上分配的一块空间虚拟成栈来使用
	struct coroutine * C = S->co[id];

	// 与栈顶S->stack的位置进行比较
    printf("coroutine_yield: &C[%p] S->stack[%p]\n", &C, S->stack);
	assert((char *)&C > S->stack);

	// 按需动态保存协程的private stack
	_save_stack(C, S->stack + STACK_SIZE);

	C->status = COROUTINE_SUSPEND;
	S->running = -1;

	swapcontext(&C->ctx, &S->main);
}

int 
coroutine_status(struct schedule * S, int id) {
	assert(id >= 0 && id < S->cap);
	if (S->co[id] == NULL) {
		return COROUTINE_DEAD;
	}
	return S->co[id]->status;
}

int 
coroutine_running(struct schedule * S) {
	return S->running;
}

结果输出:

LINUX SUSE 32: 
main start
mainfunc: coroutine id[0]
coroutine 0 : 0
coroutine_yield: coroutine id[0] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_READY: coroutine id[-1] return
break switch: coroutine id[-1] return
mainfunc: coroutine id[1]
coroutine 1 : 100
coroutine_yield: coroutine id[1] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_READY: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 0 : 1
coroutine_yield: coroutine id[0] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 1 : 101
coroutine_yield: coroutine id[1] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 0 : 2
coroutine_yield: coroutine id[0] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 1 : 102
coroutine_yield: coroutine id[1] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 0 : 3
coroutine_yield: coroutine id[0] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 1 : 103
coroutine_yield: coroutine id[1] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 0 : 4
coroutine_yield: coroutine id[0] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
coroutine 1 : 104
coroutine_yield: coroutine id[1] into
coroutine_yield: &C[0xb7df4f9c] S->stack[0xb7cf5008]
_save_stack: &C[0xb7df4f7c] top[0xb7df5008] &dummy[0xb7df4f67] top -&dummy[161] STACK_SIZE[1048576]
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
COROUTINE_SUSPEND: coroutine id[-1] return
break switch: coroutine id[-1] return
main end 

 

参考

[1] http://en.wikipedia.org/wiki/Setcontext

[2] https://github.com/cloudwu/coroutine/

 

 


你可能感兴趣的:(Coroutine,GNU/Linux,C/C++)