-
Notifications
You must be signed in to change notification settings - Fork 0
/
coroutine.cpp
284 lines (253 loc) · 9.09 KB
/
coroutine.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
//看了一个云风实现的coroutine c版本,注释了一下。(by kevinlin)
//==============================================
#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
//==============================================
#include "coroutine.h"
#include <stdio.h>
#include <stdlib.h>
#ifdef _ON_MAC_
#include <sys/ucontext.h>
#else
#include <ucontext.h>
#endif
//FIXME 在macosx下能编译过,但是运行时,co[1]->status不知何时被修改,导致191行assert()失败
#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <stdint.h>
#define STACK_SIZE (1024*1024)
#define DEFAULT_COROUTINE 16
struct coroutine;
struct schedule {
char stack[STACK_SIZE]; //1M的栈空间, 用来保存当前正在运行的coroutine的stack。
//主函数的栈不保存,它随着swapcontext带来带去
ucontext_t main; //main的上下文, 从coroutine切换回来就需要它
int nco; //已经使用的co
int cap; //总共co 的数量, 即将会malloc(cap*sizeof(coroutine*)))大小内存
int running; // schedule状态, 正在执行哪个coroutine.. = id
struct coroutine **co; //保存所有的coroutine信息
};
struct coroutine {
coroutine_func func; //协程所调用函数
//typedef void (*coroutine_func)(struct schedule *, void *ud);
void *ud; //user data
ucontext_t ctx; //此协程上下文
struct schedule * sch; //此coroutine从属的schedule
ptrdiff_t cap; //为coroutine私有栈分配的空间 cap>=size
ptrdiff_t size; //coroutine私有栈的实际大小
int status; //此coroutine的状态,是正在运行,还是挂起
//#define COROUTINE_DEAD 0
//#define COROUTINE_READY 1
//#define COROUTINE_RUNNING 2
//#define COROUTINE_SUSPEND 3
char *stack;
};
//创建和初始化一个coroutine, 内部使用
struct coroutine *
_co_new(struct schedule *S , coroutine_func func, void *ud) {
struct coroutine * co = malloc(sizeof(*co));
printf("%s:%s co:%p\n", __FILE__, __FUNCTION__, co);
co->func = func;
co->ud = ud;
co->sch = S;
co->cap = 0;
co->size = 0;
co->status = COROUTINE_READY; //创建完进入READY状态,等resume()
co->stack = NULL; //在需要切换 出去,要保存此coroutine的栈时才使用,动态分配内存
return co;
}
//删除一个coroutine
void
_co_delete(struct coroutine *co) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
free(co->stack); //因创建时已经置空,故可直接free
// co->ud 不用删除? 算是由外界传入,非吾所管理.并且你都不知道调用者是怎么分配的,如示例中的args
free(co);
}
//创建一个schedule, 感觉改名叫schedule_new或许更合适?想想不改也成,不能制造太多的概念给调用者
struct schedule *
coroutine_open(void) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
struct schedule *S = malloc(sizeof(*S));
S->nco = 0;
S->cap = DEFAULT_COROUTINE;
S->running = -1; //-1是为尚无任何coroutine在运行
S->co = malloc(sizeof(struct coroutine *) * S->cap); //分配空间存指针
memset(S->co, 0, sizeof(struct coroutine *) * S->cap);
return S;
}
//关闭schedule,释放所有的coroutine
void
coroutine_close(struct schedule *S) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
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);
}
//创建一个coroutine,并返回其id
int
coroutine_new(struct schedule *S, coroutine_func func, void *ud) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
//调用内部函数创建coroutine
struct coroutine *co = _co_new(S, func , ud);
//将创建的coroutine加入到schedule中管理
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;
printf("%s:%s:%d gen coroutine id:%d\n", __FILE__, __FUNCTION__,__LINE__, id);
return id;
} else {
int i;
//这里从前向后查找空闲的位置并使用,看来此id会很快被复用的
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;
printf("%s:%s:%d gen coroutine id:%d co:%p\n", __FILE__, __FUNCTION__,__LINE__, id, co);
return id;
}
}
}
assert(0);
return -1;
}
//从这里,调用coroutine的callback,当callback()返回后,此coroutine即结束
static void
mainfunc(uint32_t low32, uint32_t hi32) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
//为啥整了个这样的东东, ptr -- schedule*
uintptr_t ptr = (uintptr_t)low32 | ((uintptr_t)hi32 << 32);
struct schedule *S = (struct schedule *)ptr;
int id = S->running;//可能-1吗, 目前是调用此函数前就设置了此次croutine id
struct coroutine *C = S->co[id];
C->func(S,C->ud); //调用coroutine的callback(?)
//当func()调用结束后,此co即可宣告死亡
_co_delete(C);
S->co[id] = NULL;
--S->nco;
S->running = -1; //-1代表当前没有coroutine在执行, 在这个状态下,执行流(将)交给主线程(main)
}
//运行coroutine, resume是叫唤醒的意思吗,重新回到。。
void
coroutine_resume(struct schedule * S, int id) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
printf("coroutine:[%d]\n", id);
assert(S->running == -1);
assert(id >=0 && id < S->cap);
struct coroutine *C = S->co[id];
printf("coroutine:[%d] co:%p\n", id, C);
if (C == NULL)
return;
int status = C->status;
switch(status) {
case COROUTINE_READY:
printf("coroutine:[%d] COROUTINE_READY\n", id);
getcontext(&C->ctx);//这个调用 ,将会使ctx内的东西和当前的上下文关联上(ss,sp??)
C->ctx.uc_stack.ss_sp = S->stack; //要点: 对于context, stack需要自己分配给它,这里用S->stack传过去,相当于设置了堆栈的指针,如此使函数的调用过程中的栈信息放到了我们想要保存的位置 . 这S->stack相当于等下函数调用的栈顶
C->ctx.uc_stack.ss_size = STACK_SIZE; //提供栈大小,计算出顶底
C->ctx.uc_link = &S->main; //这个是当这个context返回时, resume的目标
S->running = id;//正在运行croutine id, 如此看来这块是非线程安全?
C->status = COROUTINE_RUNNING;
uintptr_t ptr = (uintptr_t)S;
//这里为何要将uintptr分为两个uint32_t传递? --> man makecontext
makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, (uint32_t)ptr, (uint32_t)(ptr>>32));
//将当前的上下文保存到S->main中,并且控制权交给C->ctx. 即去执行ctx中设置的函数等等
swapcontext(&S->main, &C->ctx);
printf("coroutine:[%d] COROUTINE_READY swapcontext return\n", id);
break;
case COROUTINE_SUSPEND://之前被yield过了,它已经有了自己的stack数据
printf("coroutine:[%d] COROUTINE_SUSPEND\n", id);
//将要唤醒的croutine的stack拷贝到S->stack末尾去,这是何意?为什么是末尾
//答复上面:因为stack是从下往上(从高地址往低?)放的,比如入栈一个int,它会放在S->stack+STACK_SIZE位置,然后栈顶往上移sizeof(int), 所以我们拷贝时,要把coroutine的私有栈拷贝到S->stack + STACK_SIZE - C->size的位置
memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);
S->running = id;
C->status = COROUTINE_RUNNING;
swapcontext(&S->main, &C->ctx);
printf("coroutine:[%d] COROUTINE_SUSPEND swapcontext\n", id);
break;
default:
printf("ERROR[coroutine status]:%d", status);
assert(0);
}
}
//保存croutine的stack内容
static void
_save_stack(struct coroutine *C, char *top) {
printf("%s:%s top:%p\n", __FILE__, __FUNCTION__, top);
//在栈上创建一个局部变量, 它就是栈顶了,往下的那一段都是函数调用的堆栈
char dummy = 0;
assert(top - &dummy <= STACK_SIZE);
if (C->cap < top - &dummy) {
free(C->stack);
C->cap = top-&dummy; //保存了此时coroutine的私有栈大小
C->stack = malloc(C->cap); //C->stack用来保存coroutine自身的栈内容
}
C->size = top - &dummy; //计算出需要保存的栈长度
printf("%s:%s C->stack:%p dummy:%p C->size:%ld\n",
__FILE__, __FUNCTION__, C->stack, &dummy, C->size);
memcpy(C->stack, &dummy, C->size);
}
void
coroutine_yield(struct schedule * S) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
int id = S->running;
//这个assert(id>=0) 即暗示了此函数只会在coroutine中调用。
assert(id >= 0);
//取出正在运行的croutine
struct coroutine * C = S->co[id];
//这个assert()是何意? 这里的S->stack为何是栈上的地址?不是从堆上分配的吗
//上面的疑问解答:因为swapcontext()时,指定了ss_sp这S->stack,这样当context执行时,它的栈是放在S->stack上的。
//所以C, S->stack 它们的地址都是在相近地
printf("&C:%p S->stack:%p\n", &C, S->stack);
assert((char *)&C > S->stack);
//保存这个coroutine的stack
_save_stack(C,S->stack + STACK_SIZE);
//使进入SUSPEND状态
C->status = COROUTINE_SUSPEND;
S->running = -1;
//切换回S->main, 就是当初从coroutine_resume()中过来的那里
swapcontext(&C->ctx , &S->main);
}
//获取某个coroutine的状态
int
coroutine_status(struct schedule * S, int id) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
assert(id>=0 && id < S->cap);
if (S->co[id] == NULL) {
return COROUTINE_DEAD;
}
return S->co[id]->status;
}
//获得正在运行的croutine id
int
coroutine_running(struct schedule * S) {
printf("%s:%s\n", __FILE__, __FUNCTION__);
return S->running;
}