汇编相关的知识:
一些寄存器:
函数调用时:
foo:push ebp # 将ebp存放在栈中mov ebp, esp # 当前ebp记录的是当前函数的栈底sub esp # 为一些变量预留空间
其中: ebp 的作用是:指向当前函数栈帧的栈底。(之前函数的 ebp 只是刚好在栈底而已。记成当前 ebp 指向主调函数的 ebp 会让逻辑混乱。)
协程的实现
setjmp(jmp_buf),第一次执行返回0;由longjmp跳转过来会返回非0longjmp(jmp_buf, val),跳转到setjmp的地方,并且将val传递过去作为setjpm的返回值
如果val是0,那么setjmp会返回1jmp_buf里面的内容本来是寄存器的值,但是被glib加密了,所以需要解密- 在使用协程时,为了不出现意想不到的情况,协程函数应该使用单独的栈
- 创建一个协程的时候,需要自定义
pc,esp,ebp这三个值。其中,通过esp分配新的栈 - 设计协程的一个问题是:如何才能够将参数传递给协程函数。要知道,协程函数使用的是新的栈
并不知道函数调用方式,不太方便直接往栈里面写东西。可以利用全局变量来传递参数。 - 参考文章
代码
#include <setjmp.h>#include <stdlib.h>#include <stdio.h>#include <memory.h>/*** 加密函数,glib对jmp_buf进行了加密*/unsigned long PTR_MANGLE(unsigned long var){asm("movq %1, %%rdx \n""xor %%fs:0x30, %%rdx\n""rol $0x11,%%rdx\n""movq %%rdx, %0\t\n": "=r"(var): "0"(var));return var;}/*** 解密函数,glib对jmp_buf进行了加密*/unsigned long PTR_DEMANGLE(unsigned long var){asm("ror $0x11, %0\n""xor %%fs:0x30, %0": "=r"(var): "0"(var));return var;}/*** jmp_buf里面的内容*/typedef struct buffer{unsigned long RBX;unsigned long RBP; // 栈帧unsigned long R12;unsigned long R13;unsigned long R14;unsigned long R15;unsigned long RSP; // 栈基址unsigned long PC; // 接下来要从哪里运行} buffer;// struct task 的前向定义struct task;// 协程中运行的函数typedef void (*task_func)(struct task *, void *arg);// 代表一个协程任务struct task{unsigned char *stack; // 协程使用的栈,可以用作调试jmp_buf buf; // 协程当前运行的地方jmp_buf caller_buf; // 控制是从哪里跳转过来的task_func func;void *arg; // 协程里面函数运行所需的参数};// 栈的大小定义为4096#define STACK_SIZE 4096// 借助该全局变量来传递参数struct task *__task;/*** 该函数的主要作用是给task.func传递参数,该函数的栈应该是新分配的栈*/void __start(){// 将参数保存在新分配的栈空间里面了struct task *t = __task;int res = setjmp(__task->buf);if (res == 0){// 跳转回init函数里面longjmp(__task->caller_buf, 0);}// 从next函数跳转来// 不要再使用__task了,此时的cur_task已经不等于t了t->func(t, t->arg);// func执行结束了setjmp(t->buf);longjmp(t->caller_buf, 0);}void init(struct task *t, task_func func, void *arg){// 为协程分配栈t->stack = (unsigned char *)malloc(STACK_SIZE);t->arg = arg;t->func = func;// 初始化jmp_bufint res = setjmp(t->buf);if (res == 0){memcpy((void *)t->caller_buf, (void *)t->buf, sizeof(t->buf));buffer *buf = (buffer *)t->buf;// 设置入口地址buf->PC = PTR_MANGLE((unsigned long)__start);// 设置栈指针和frame指针buf->RBP = buf->RSP = PTR_MANGLE((unsigned long)(t->stack + STACK_SIZE - 16));// 将任务结构体设置为全局变量,__start马上会用到__task = t;// 跳到__start那里去longjmp(t->buf, 0);}// 从__start跳转回来,表示参数已经保存在新的栈里面了}/*** 在函数里面调用* val 是函数内向外产出的信息*/int yield(struct task *t, int val){int res;res = setjmp(t->buf);if (res == 0){// 跳转回调度器longjmp(t->caller_buf, val);}// 从调度器跳来return res;}/*** 进入协程的方法* val是要传入的值*/int next(struct task *t, int val){int res;res = setjmp(t->caller_buf);if (res == 0){// 跳转到协程里面longjmp(t->buf, val);}// 从协程跳转过来return res;}// 一个协程函数的例子// arg参数应该是一个数组:int arg[3]void foo(struct task *t, void *arg){int out = 1;int i;for (i = 0; i < 3; ++i){printf("%d-%d: hello", out, ((int *)arg)[i]);out = yield(t, 1);}}int main(){// 初始化一个任务对象struct task *t1 = (struct task *)malloc(sizeof(struct task));int arg[3] = {233, 122, 999};init(t1, foo, (void *)arg);// 使用协程的特性for (int i = 6; i < 15; ++i){next(t1, i);printf(" world\n");}}
