云风协程库保存和恢复协程运行栈原理讲解

   首先, linux下的进程地址空间布局是这样子的:

在这里插入图片描述

可以看到 整个进程地址空间从上到下地址变化是从高地址到低地址的! 32 位系统有 4G 的地址空间,其中0x08048000-> 0xbfffffff 是用户空间,0xc0000000~0xffffffff 是内核空间,包括内核代码和数据、与进程相关的数据结构(如页表、内核栈)等。其实就是大体上就是下面这样:

在这里插入图片描述

另外, %esp 执行栈顶,往低地址方向变化; brk/sbrk 函数控制堆顶往高地址方向变化。也就是说栈是向下生长的.(堆向上生长,这样可以充分利用空间)

知道了这些,我们就可以开心的来看代码了^-^

保存现场:

协程在从 RUNNINGSUSPEND 状态时需要保存运行栈,即调用 coroutine_yield 之后挂起协程,让出CPU的过程。

原理:就是将该协程运行时候的所有数据转而存储到每个协程所对应的结构中,为了在下次回到这个协程时能回到之前的执行点继续执行,那么你会想问,那这个协程运行的时候的栈在哪?我们来看代码:

C->ctx.uc_stack.ss_sp = S->stack; 
C->ctx.uc_stack.ss_size = STACK_SIZE; 
makecontext(&C->ctx, (void (*)(void)) mainfunc, 2, 
(uint32_t)ptr, (uint32_t)(ptr>>32)); 

这是协程初始化时进行的设置.我们可以看到schedule 的 stack[STACK_SIZE] 是子协程运行的公共栈空间!该协程栈的栈顶设置为 S->stackmainfunc 的运行使用 S->stack 作为栈顶,大小为 STACK_SIZE.ok,一切搞定,那么转储的时候其实就是将这个栈中该协程所用到的数据copy到它对应的结构中就行了

static void
_save_stack(struct coroutine *C, char *top)
{
	printf("top:%p \n", top);

	char dummy = 0;
	printf("dummy:%p \n", &dummy);
	// top-&dummy 即该协程的运行时所占的空间大小
	assert(top - &dummy <= STACK_SIZE);

	printf("STACK_ZISE:%d \n", STACK_SIZE);
	printf("top - &dummy:%ld \n", top - &dummy);
	//如果协程私有栈空间大小(C->cap)不够放下运行时的栈空间,则要重新扩容
	if (C->cap < top - &dummy)
	{
		free(C->stack);
		C->cap = top - &dummy;
		C->stack = malloc(C->cap);
	}
	C->size = top - &dummy; /*c->stack 是在堆上的*/
	memcpy(C->stack, &dummy, C->size);
	//将(dummy->top) 的数据copy到该协程对应的结构中就行了
}

void coroutine_yield(struct schedule *S)
{
	int id = S->running;
	assert(id >= 0);
	struct coroutine *C = S->co[id];
	assert((char *)&C > S->stack);

	printf("S->stack ==%p\n", S->stack);
	printf("S->stack+STACK_SIZE ==%p\n", S->stack + STACK_SIZE);
   //为了在下次回到这个协程时能回到之前的执行点继续执行,必须要保存其执行的上下文

	_save_stack(C, S->stack + STACK_SIZE);

	C->status = COROUTINE_SUSPEND;
	S->running = -1;
	swapcontext(&C->ctx, &S->main);
}

关于_save_stack()函数,我画了这样的一幅图,相信你一看就能懂

在这里插入图片描述

程序执行结果:
在这里插入图片描述
1. 可以看到S->stack+STACK_SIZE地址值大于S->stack,所以他在高地址.
2. dummy介于两者之间,> S->stack ,< S->stack+STACK_SIZE.因为我们没有用到什么数据,所以dummy的地址很靠近< S->stack+STACK_SIZE
3. 证明上面画得图是正确的.

恢复现场:

协程从 SUSPENDRUNNING 状态时,是要恢复当时的运行栈,回到执行点再执行的.

原理:将各自私有的C->stack 空间中的数据恢复到S->stack中

// C->size 表示协程似有栈的实际大小
memcpy(S->stack + STACK_SIZE - C->size, C->stack, C->size);

至此,该原理就讲解到这儿了,你理解了吗??

<think>我们正在讨论协程的实现方式,用户询问共享协程的区别。从提供的引用来看,我们有三个关键点:1.无协程通过状态机管理执行流,不分配独立空间,利用系统保存状态局部变量[^1]。2.共享是多个协程共享同一个空间,通过动态调整大小来适应需求[^2]。用户之前的问题是关于独立共享是否都属于有协程,我确认了二者都是有协程。现在用户转向询问共享协程的区别。因此,我需要从实现原理、内存管理性能特点三方面对比:###核心区别1.**空间的存在性**:-**共享协程**:仍然需要空间(多个协程共享一个物理),属于有协程的子类-**无协程**:没有独立空间,完全依赖状态机+系统2.**上下文保存方式**:-共享协程:切换时需要完整保存/恢复整个内容(虽然是共享的)-无协程:仅需保存状态机位置少量局部变量###实现模型对比用伪代码说明:**无协程**(基于生成器模型):```pythondefasync_func():state=0whileTrue:ifstate==0:do_step1()state=1returnintermediate_resultelifstate==1:do_step2()state=2returnfinal_result```**共享协程**(简化版上下文切换):```cppcharshared_stack[8*1024];//共享内存voidcoroutine_switch(Coroutine*from,Coroutine*to){//1.保存当前协程数据到其私有缓冲区memcpy(from->private_buffer,shared_stack,current_stack_size);//2.将目标协程数据恢复到共享memcpy(shared_stack,to->private_buffer,to->saved_stack_size);//3.寄存器切换swap_registers(&from->regs,&to->regs);}```###数学表述差异定义协程切换代价函数:-无协程:$$C_{\text{stackless}}=O(1)\text{(仅状态转移)}$$-共享协程:$$C_{\text{shared}}=O(S)\text{(S为数据大小)}$$###性能对比表|特性|无协程|共享协程||--------------|---------------------|-------------------||切换速度|$O(1)$[^1]|$O(\text{大小})$[^2]||内存开销|恒定|空间+私有缓冲区||编程复杂度|高(需拆解状态机)|低(接近线程模型)||中断点支持|有限(仅yield点)|任意位置|###总结差异共享协程本质上**仍是有协程的优化形态**,通过牺牲部分切换速度换取内存节省;而无协程是**完全不同的实现范式**,其设计哲学适用场景均有本质区别[^1][^2]。---相关问题:1.什么场景下适合使用无协程?2.共享协程数据如何安全恢复?3.C++20协程是有还是无实现?参考文献:[^1]:无协程的实现原理[^2]:协程的演进与共享实现</think> ### 共享协程与无协程的区别及实现特点 #### 1. **基础定义** - **共享协程**:多个协程共享**同一块物理内存空间**,通过动态调整实现协程切换,本质上仍属于**有协程**的优化形态[^2]。 - **无协程**:**不分配独立空间**,通过状态机维护执行上下文,完全依赖系统管理局部变量[^1]。 #### 2. **核心区别对比表** | **特性** | **无协程** | **共享协程** | |-------------------|---------------------------------------|-------------------------------------| | **空间分配** | 无独立(仅用系统) | 共享物理 + 私有缓冲区 | | **上下文切换代价** | $O(1)$(状态跳转)[^1] | $O(S)$($S$=数据大小)[^2] | | **内存占用** | 恒定(仅存储状态机) | $M = S_{\text{shared}} + N \times B$($B$=缓冲区) | | **中断位置限制** | 仅限`yield`显式中断点 | 任意函数调用深度均可中断 | | **典型代表** | Python生成器、C#异步方法 | Libco、云风协程 | #### 3. **实现原理详解** **无协程(状态机模型)**: 将函数拆解为离散状态,用`switch-case`或`goto`实现跳转: ```c // 伪代码示例:无协程状态机 void async_func() { static int state = 0; while (1) { switch (state) { case 0: do_step1(); state = 1; return; case 1: do_step2(); state = 2; return; } } } ``` **数学表达**: 函数执行流被编码为状态转移序列: $$\text{State} \rightarrow \left\{ S_0, S_1, \cdots, S_n \right\} \quad \text{迁移开销} \Delta t = O(1)$$ **共享协程(上下文切换)**: 物理共享 + 私有数据缓冲区: ```c // 伪代码:共享切换逻辑 void coroutine_switch(Coroutine* from, Coroutine* to) { // 1. 保存当前数据到私有缓冲区 memcpy(from->private_buf, shared_stack, current_stack_size); // 2. 从目标缓冲区恢复数据 memcpy(shared_stack, to->private_buf, to->saved_size); // 3. 切换寄存器上下文 swap_registers(&from->regs, &to->regs); } ``` **数学表达**: 切换代价与大小$S$线性相关: $$C_{\text{switch}} = k \cdot S \quad (k \text{为内存拷贝系数})$$ #### 4. **适用场景对比** - **无协程适用**: - 高并发I/O密集型任务(如网络服务) - 内存受限环境(嵌入式系统) - 需极低延迟切换的场景 - **共享协程适用**: - 传统线程模型改造(如游戏服务器) - 深度递归调用场景 - 需要兼容同步编程习惯的异步逻辑 --- ### 相关问题 1. 无协程的状态机如何自动生成? 2. 共享协程如何避免不同协程数据冲突? 3. 为什么Rust语言同时支持无(async/await)共享(生成器)协程
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值