zStack

zStack

马上订阅 zStack RSS 更新: https://blog.noicdi.com/atom.xml

如何用 C 实现协程

2022年7月22日 17:00

写在前面

本文是针对南大蒋炎岩老师 M2: 协程库 的实现总结。如果你也正在做这个 lab,请退出本文,仔细阅读实验文档,多写多调,虽然很难很痛苦,但是真的很有意思。如果你已经完成,欢迎邮件联系讨论相关内容,包括实现思路和相关代码交流。

关于这个实验

从开始阅读文档,到迄今为止完成 64bit 的测试用例(是的,32bit 测试用例还没过),耗费了大量的时间,一点一点摸索。揪着头发在脑子里运行协程,用 GDB 一行一行调试代码,逼急了甚至逐步过汇编代码,无时不让我感慨 coding 和 debug 真难,也真有意思。


2022-07-25 update:

使劲折腾终于用一种无语的方法把 32bit 测试用例通过了,此刻心情复杂。

协程

经过这段时间的 coding,让我大概对 协程 有了一些理解。

线程是 CPU 调度的最小单位,通过系统调用,陷入内核态来完成线程管理和调度;相比较进程而言,调度时上下文更少,因此开销更小一些。然而,抢占式调度终究还要看操作系统的脸色,陷入内核态还是会带来较大开销。

协程,我认为就是一种协作式调度的用户级线程。协作式,也就是由用户来决定调度时机,而不像线程用完时间片就被剥夺 CPU;用户级线程,无需陷入内核态,只要在用户态就可以完成逻辑流的切换。

除此之外,Linux 的线程栈大小默认为 8MB,一般来说用不到这么大;而协程可以自定义栈大小(可以通过修改系统文件来分配线程栈),相比较之下可以起更多的协程。

对于 I/O 密集型任务而言,CPU 用的少,需要更多的进程 or 线程来使用空闲的 CPU,提高利用率。协程相较于进程 or 线程而言,切换上下文开销更少,协程栈更小,就能开更多的协程来执行任务。

libco

libco 定义了相关的 API:

1
2
3
struct co *co_start(const char *name, void (*func)(void *), void *arg);
void co_yield();
void co_wait(struct co *co);
  • co_start()负责创建一个协程,并指定任务函数及参数,创建好的协程不会直接运行,而是返回其指针等待用户指令;
  • co_yield()负责在协程池中随机挑选一个待运行的协程,也就是『协作式调度』的切换函数;
  • co_wait()暂停当前协程,直到指定协程运行结束后再继续当前协程,会在指定协程运行结束后释放其资源,所以用户应当保证初始协程外的每个协程被此函数执行一次。

main 函数所在的协程即为初始协程,通过调用co_yield()co_wait()切换其他协程,并根据指定的任务函数和参数开始执行,直到函数返回 or 再次切换。

不论还有多少协程未执行结束,只要 mian 函数结束,整个进程即终止。

co 与协程池

定义协程,指定其协程栈大小为STACK_SIZE,64KB。

1
2
3
4
5
6
7
8
9
10...

剩余内容已隐藏

查看完整文章以阅读更多