汇编相关的知识:

一些寄存器:
函数调用时:

  1. foo:
  2. push ebp # 将ebp存放在栈中
  3. mov ebp, esp # 当前ebp记录的是当前函数的栈底
  4. sub esp # 为一些变量预留空间

其中: ebp 的作用是:指向当前函数栈帧的栈底。(之前函数的 ebp 只是刚好在栈底而已。记成当前 ebp 指向主调函数的 ebp 会让逻辑混乱。)

协程的实现

  • setjmp(jmp_buf) ,第一次执行返回 0 ;由 longjmp 跳转过来会返回非 0
  • longjmp(jmp_buf, val) ,跳转到 setjmp 的地方,并且将 val 传递过去作为 setjpm 的返回值
    如果 val0 ,那么 setjmp 会返回 1
  • jmp_buf 里面的内容本来是寄存器的值,但是被 glib 加密了,所以需要解密
  • 在使用协程时,为了不出现意想不到的情况,协程函数应该使用单独的栈
  • 创建一个协程的时候,需要自定义 pc,esp,ebp 这三个值。其中,通过 esp 分配新的栈
  • 设计协程的一个问题是:如何才能够将参数传递给协程函数。要知道,协程函数使用的是新的栈
    并不知道函数调用方式,不太方便直接往栈里面写东西。可以利用全局变量来传递参数。
  • 参考文章

代码

  1. #include <setjmp.h>
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <memory.h>
  5. /**
  6. * 加密函数,glib对jmp_buf进行了加密
  7. */
  8. unsigned long PTR_MANGLE(unsigned long var)
  9. {
  10. asm("movq %1, %%rdx \n"
  11. "xor %%fs:0x30, %%rdx\n"
  12. "rol $0x11,%%rdx\n"
  13. "movq %%rdx, %0\t\n"
  14. : "=r"(var)
  15. : "0"(var));
  16. return var;
  17. }
  18. /**
  19. * 解密函数,glib对jmp_buf进行了加密
  20. */
  21. unsigned long PTR_DEMANGLE(unsigned long var)
  22. {
  23. asm("ror $0x11, %0\n"
  24. "xor %%fs:0x30, %0"
  25. : "=r"(var)
  26. : "0"(var));
  27. return var;
  28. }
  29. /**
  30. * jmp_buf里面的内容
  31. */
  32. typedef struct buffer
  33. {
  34. unsigned long RBX;
  35. unsigned long RBP; // 栈帧
  36. unsigned long R12;
  37. unsigned long R13;
  38. unsigned long R14;
  39. unsigned long R15;
  40. unsigned long RSP; // 栈基址
  41. unsigned long PC; // 接下来要从哪里运行
  42. } buffer;
  43. // struct task 的前向定义
  44. struct task;
  45. // 协程中运行的函数
  46. typedef void (*task_func)(struct task *, void *arg);
  47. // 代表一个协程任务
  48. struct task
  49. {
  50. unsigned char *stack; // 协程使用的栈,可以用作调试
  51. jmp_buf buf; // 协程当前运行的地方
  52. jmp_buf caller_buf; // 控制是从哪里跳转过来的
  53. task_func func;
  54. void *arg; // 协程里面函数运行所需的参数
  55. };
  56. // 栈的大小定义为4096
  57. #define STACK_SIZE 4096
  58. // 借助该全局变量来传递参数
  59. struct task *__task;
  60. /**
  61. * 该函数的主要作用是给task.func传递参数,该函数的栈应该是新分配的栈
  62. */
  63. void __start()
  64. {
  65. // 将参数保存在新分配的栈空间里面了
  66. struct task *t = __task;
  67. int res = setjmp(__task->buf);
  68. if (res == 0)
  69. {
  70. // 跳转回init函数里面
  71. longjmp(__task->caller_buf, 0);
  72. }
  73. // 从next函数跳转来
  74. // 不要再使用__task了,此时的cur_task已经不等于t了
  75. t->func(t, t->arg);
  76. // func执行结束了
  77. setjmp(t->buf);
  78. longjmp(t->caller_buf, 0);
  79. }
  80. void init(struct task *t, task_func func, void *arg)
  81. {
  82. // 为协程分配栈
  83. t->stack = (unsigned char *)malloc(STACK_SIZE);
  84. t->arg = arg;
  85. t->func = func;
  86. // 初始化jmp_buf
  87. int res = setjmp(t->buf);
  88. if (res == 0)
  89. {
  90. memcpy((void *)t->caller_buf, (void *)t->buf, sizeof(t->buf));
  91. buffer *buf = (buffer *)t->buf;
  92. // 设置入口地址
  93. buf->PC = PTR_MANGLE((unsigned long)__start);
  94. // 设置栈指针和frame指针
  95. buf->RBP = buf->RSP = PTR_MANGLE((unsigned long)(t->stack + STACK_SIZE - 16));
  96. // 将任务结构体设置为全局变量,__start马上会用到
  97. __task = t;
  98. // 跳到__start那里去
  99. longjmp(t->buf, 0);
  100. }
  101. // 从__start跳转回来,表示参数已经保存在新的栈里面了
  102. }
  103. /**
  104. * 在函数里面调用
  105. * val 是函数内向外产出的信息
  106. */
  107. int yield(struct task *t, int val)
  108. {
  109. int res;
  110. res = setjmp(t->buf);
  111. if (res == 0)
  112. {
  113. // 跳转回调度器
  114. longjmp(t->caller_buf, val);
  115. }
  116. // 从调度器跳来
  117. return res;
  118. }
  119. /**
  120. * 进入协程的方法
  121. * val是要传入的值
  122. */
  123. int next(struct task *t, int val)
  124. {
  125. int res;
  126. res = setjmp(t->caller_buf);
  127. if (res == 0)
  128. {
  129. // 跳转到协程里面
  130. longjmp(t->buf, val);
  131. }
  132. // 从协程跳转过来
  133. return res;
  134. }
  135. // 一个协程函数的例子
  136. // arg参数应该是一个数组:int arg[3]
  137. void foo(struct task *t, void *arg)
  138. {
  139. int out = 1;
  140. int i;
  141. for (i = 0; i < 3; ++i)
  142. {
  143. printf("%d-%d: hello", out, ((int *)arg)[i]);
  144. out = yield(t, 1);
  145. }
  146. }
  147. int main()
  148. {
  149. // 初始化一个任务对象
  150. struct task *t1 = (struct task *)malloc(sizeof(struct task));
  151. int arg[3] = {233, 122, 999};
  152. init(t1, foo, (void *)arg);
  153. // 使用协程的特性
  154. for (int i = 6; i < 15; ++i)
  155. {
  156. next(t1, i);
  157. printf(" world\n");
  158. }
  159. }

时序图:

C的协程-时序图.svg