探究有栈协程的实现以及ucontenxt函数族的使用

协程分类

对称协程与非对称协程

协程按概念分为对称协程、非对称协程,对称协程指的是协程a可任意跳转到协程b/c/d,所有的协程都是相同的,可任意跳转,称为对称协程。
非对称协程则是有类似函数调用栈的概念,如协程a调用了协程b,那么协程b只能跳转到协程a,返回到上一级,这种称为非对称协程。

有栈线程与无栈协程

协程按实现可分为有栈协程和无栈协程,顾名思义,有栈协程利用栈实现的,无栈协程则不需要利用协程栈,而是采用状态机等方法实现。
其中有栈协程还可分为共享栈(即所有协程用同一个栈)和独立栈(每个协程一个栈)。
这里讨论有栈协程的独立栈实现。

实现原理简述

我们知道,在函数执行过程中,其实就是在一块栈空间内运行罢了。这个栈空间由rsp、rbp指针来指定两端范围。函数中的局部变量的都会存放在栈中某一块内存中。函数调用无非是从一个函数栈跳转到相邻另一个函数栈罢了,只是由于调用返回后还需恢复原函数栈的状态,因此必须在调用时通过寄存器和栈空间的配合来存储一些数据,方便调用完成后恢复。这一点需要明确下,具有调用关系的两个函数栈空间一定是相邻的,也就是说主调方的函数栈空间与被调方的函数栈空间一点是相邻的。
协程实在函数间的一种跳转,这意味着:

如果我们保存了当前函数func_a的上下文,即当前函数的栈寄存器(rsp、rip),参数寄存器(rax rbx… rdi rsi)等寄存器,以及程序计数器(rip),那么假设我们现在跳转到了func_b执行,在func_b的执行中,我们将之前保存的寄存器内容写到原寄存器中,那么我们就跳转到了原func_a的执行上下文。
这是不是很类似与协程的跳转,没错,其实有栈协程就是这么实现的,这里有两个问题需要解决:
1:当func_b跳转到func_a后,func_a继续调用其他函数,继续使用栈空间,不就把原来的func_b的栈空间冲掉了吗,无法再跳转到func_b了。
2:如何获取这些寄存器值,如何写入呢。

其实linux以及为我们做好了,使用ucontenxt函数族即可。

ucontext函数族

简介

ucontext.h是GNU C库的一个头文件,主要用于用户态下的上下文切换。

结构

ucontext.h有两个比较重要的结构体,分别是mcontext_t和ucontext_t,其中mcontext_t中主要保存了上下文的各种寄存器信息,因此一般情况下不会修改mcontext_t的信息。

ucontext_t主要需要关注的字段如下:

typedef struct ucontext_t {struct ucontext_t *uc_link;stack_t uc_stack;mcontext_t uc_mcontext;sigset_t uc_sigmask;
} ucontext_t;
//其中mcontext_t 定义如下
typedef struct{gregset_t __ctx(gregs);//所装载寄存器fpregset_t __ctx(fpregs);//寄存器的类型
} mcontext_t;//其中gregset_t 定义如下
typedef greg_t gregset_t[NGREG];//包括了所有的寄存器的信息typedef struct {void *ss_sp;int ss_flags;size_t ss_size;
} stack_t;

uc_link指向一个上下文,当当前上下文结束时,将返回执行该上下文。sigset_t当上下文被激活时,被屏蔽的信号集合。stack_t栈消息,具体结构如下所示。uc_mcontext保存了上下文的各种寄存器信息。

getcontext(ucontext_t* ucp)

功能:将当前运行到的寄存器的信息保存在参数ucp中, 即我们之前所提到的将各个寄存器写入ucp的对应字段中,
在这里插入图片描述
可认为调用过后,uc_context就保存了当前上下文的所需寄存器值。
那么如何实现跳转呢,使用setcontext函数

setcontext

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>int main()
{int i = 0;ucontext_t ctx;//定义上下文结构体变量getcontext(&ctx);//获取当前上下文printf("i = %d\n", i++);sleep(1);setcontext(&ctx);//回复ucp上下文return 0;
}

在getcontext(&ctx);中,我们会将下一条执行的指令环境保存到结构体ctx中,也就是printf(“i = %d\n”, i++)指令。然后运行到setcontext(&ctx)时就会将ctx中的指令回复到cpu中,所以该代码就是让cpu去运行ctx所保存的上下文环境,所以又回到了打印的那一行代码中,所以运行是一个死循环,而i值不变是因为i是存在内存栈中的,不是存在寄存器中的,所以切换并不影响i的值
在这里插入图片描述
这已经初步有了协程的感觉,但是协程需要的是跳转的其他函数,这该如何做到(其实就是修改rip,创建栈)

makecontext

函数签名:void makecontext(ucontext_t *ucp, void (*func)(), int argc, …)
功能:修改上下文信息,参数ucp就是我们要修改的上下文信息结构体;func是上下文的入口函数;argc是入口函数的参数个数,后面的…是具体的入口函数参数,该参数必须为整形值
示例:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>void fun()
{printf("fun()\n");
}int main()
{int i = 0;//定义用户的栈char* stack = (char*)malloc(sizeof(char)*8192);//定义两个上下文//一个是主函数的上下文,一个是fun函数的上下文ucontext_t ctx_main, ctx_fun;getcontext(&ctx_main);getcontext(&ctx_fun);printf("i = %d\n", i++);sleep(1);//设置fun函数的上下文//使用getcontext是先将大部分信息初始化,我们到时候只需要修改我们所使用的部分信息即可ctx_fun.uc_stack.ss_sp = stack;//用户自定义的栈ctx_fun.uc_stack.ss_size = 8192;//栈的大小ctx_fun.uc_stack.ss_flags = 0;//信号屏蔽字掩码,一般设为0ctx_fun.uc_link = &ctx_main;//该上下文执行完后要执行的下一个上下文makecontext(&ctx_fun, fun, 0);//将fun函数作为ctx_fun上下文的下一条执行指令setcontext(&ctx_fun);printf("main exit\n");return 0;
}

在代码中,我们为ctx_fun指定了跳转指定时使用的栈(即malloc的空间,即设置了rsp的地址),这样就不会影响到原来的栈,使用makecontext函数,指定了跳转到地址(即设置了rip的值)
当执行到setcontext(&ctx_fun)代码时会去运行我们之前makecontext时设置的上下文入口函数所以在打印i完后会打印fun(),然后我们设置ctx_fun上下文执行完后要执行的下一个上下文是ctx_main,所以执行完后会执行到getcontext(&ctx_fun),所以最后也是一个死循环。
在这里插入图片描述

运行流程如上图,运行结果:
在这里插入图片描述
这里已经很接近协程了,同时也解决了在协程章节的问题:

Q1:当func_b跳转到func_a后,func_a继续调用其他函数,继续使用栈空间,不就把原来的func_b的栈空间冲掉了吗,无法再跳转到func_b了。
A:为rsp指定一个新空间,避免使用原来的栈空间就可以避免破坏其他栈数据。
Q2:如何获取这些寄存器值,如何写入呢。
A: 使用getcontext函数获取,在其变量中指定新的栈地址(rsp),新的执行地址(rip), 使用setcontext()写入完成跳转。

再看复杂一点的例子

swapcontext

int swapcontext(ucontext_t *oucp, ucontext_t *ucp), 将当前cpu中的上下文信息保存带oucp结构体变量中,然后将ucp中的结构体的上下文信息恢复到cpu中
这里可以理解为调用了两个函数,第一次是调用了getcontext(oucp)然后再调用setcontext(ucp)

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <ucontext.h>ucontext_t ctx_main, ctx_f1, ctx_f2;void fun1()
{printf("fun1() start\n");swapcontext(&ctx_f1, &ctx_f2);printf("fun1() end\n");
}void fun2()
{printf("fun2() start\n");swapcontext(&ctx_f2, &ctx_f1);printf("fun2 end\n");
}int main()
{char stack1[8192];char stack2[8192];getcontext(&ctx_f1);//初始化ctx_f1getcontext(&ctx_f2);//初始化ctx_f2ctx_f1.uc_stack.ss_sp = stack1;ctx_f1.uc_stack.ss_size = 8192;ctx_f1.uc_stack.ss_flags = 0;ctx_f1.uc_link = &ctx_f2;makecontext(&ctx_f1, fun1, 0);//设置上下文变量ctx_f2.uc_stack.ss_sp = stack2;ctx_f2.uc_stack.ss_size = 8192;ctx_f2.uc_stack.ss_flags = 0;ctx_f2.uc_link = &ctx_main;makecontext(&ctx_f2, fun2, 0);//保存ctx_main的上下文信息,并执行ctx_f1所设置的上下文入口函数swapcontext(&ctx_main, &ctx_f1);printf("main exit\n");return 0;
}

运行结果:定义三个上下文变量,ctx_main、ctx_f1、ctx_f2。
当执行到swapcontext(&ctx_main, &ctx_f1)时会执行fun1函数,然后打印fun1() start。再执行swapcontext(&ctx_f1, &ctx_f2),也就是保存ctx_f1的上下文,然后去执行ctx_f2的上下文信息,也就是fun2函数,所以会打印fun2() start。
执行到swapcontext(&ctx_f2, &ctx_f1);是会切换到fun1当时切换时的上下文环境,此时会打印fun1() end,ctx_f1上下文执行完后会执行之前设置的后继上下文,也就是ctx_f2,所以会打印fun2 end。fun2函数执行完会执行ctx_f2的后继上下文,其后继上下文为ctx_main,而此时的ctx_main的下一条指令就是printf(“main exit\n”),所以会打印main exit
在这里插入图片描述
在这里插入图片描述
这里其实就很接近协程的调用了,一些协程库也的确以此封装而成。

总结

可见有栈协程的原理并不算高深,就是保存寄存器,单独弄个栈空间赋值进去,然后调用系统调用,将新的栈地址、程序计数器写入CPU。这里每个协程都有单独的栈空间,这样使用倒是很方便,就是有点浪费,由此引入了共享栈,共享栈其实也不神秘,即所有协程公用一个栈空间,但是切换时,会将当前栈空间的内容全部memcpy的协程的数据结构中,恢复时再从其中拷贝到共享栈中。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/488227.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

容器化:Containerd组件

一 什么是Containerd? Containerd 最早出现在 Docker Engine 中&#xff0c;后来为了将 Docker Engine 做得更加轻量、快速和健壮&#xff0c;在 2016 年 Docker 将 containerd 从 daemon&#xff08;dockerd&#xff09; 中独立出来&#xff0c;并完成了与 daemon 的集成 独…

VBA API 概述 | 宏编程

注&#xff1a;本文为 “VBA API 概述 | 宏编程 | 执行速度慢” 相关文章合辑。 VBA API 详解 Office 二次开发于 2020-12-17 22:27:10 发布 Office 版本变动 在 Office 2010 之前&#xff0c;微软仅提供 32-bit 版本的 Office。而自 Office 2010 起&#xff0c;出现了 32-b…

LLM - 开源视觉多模态 LLaVA-CoT(o1) 深度推理模型 测试与源码 教程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/144304351 免责声明&#xff1a;本文来源于个人知识与公开资料&#xff0c;仅用于学术交流&#xff0c;欢迎讨论&#xff0c;不支持转载。 LLaVA-…

设计模式之工厂模式:从汽车工厂到代码工厂

~犬&#x1f4f0;余~ “我欲贱而贵&#xff0c;愚而智&#xff0c;贫而富&#xff0c;可乎&#xff1f; 曰&#xff1a;其唯学乎” 工厂模式概述 想象一下你走进一家4S店准备买车。作为顾客&#xff0c;你不需要知道汽车是如何被制造出来的&#xff0c;你只需要告诉销售顾问&a…

Apache APISIX快速入门

本文将介绍Apache APISIX&#xff0c;这是一个开源API网关&#xff0c;可以处理速率限制选项&#xff0c;并且可以轻松地完全控制外部流量对内部后端API服务的访问。我们将看看是什么使它从其他网关服务中脱颖而出。我们还将详细讨论如何开始使用Apache APISIX网关。 在深入讨…

2024年12月11日Github流行趋势

项目名称&#xff1a;maigret 项目维护者&#xff1a;soxoj, kustermariocoding, dependabot, fen0s, cyb3rk0tik项目介绍&#xff1a;通过用户名从数千个站点收集个人档案信息的工具。项目star数&#xff1a;12,055项目fork数&#xff1a;870 项目名称&#xff1a;uv 项目维护…

使用pyinstaller打包pyqt的程序,运行后提示ModuleNotFoundError: No module named ‘Ui_main‘

环境&#xff1a;windowpython3.9pyqt6 使用pyqt UI编辑器生成了main.ui &#xff0c;main.ui编译成了Ui_main.py main.py 使用当前目录下的Ui_main.py。 打包过程没报错&#xff0c;运行报错。 错误如下: 解决方法&#xff1a;pyinstaller -Fw main.py --paths. 使…

Linux-音频应用编程

ALPHA I.MX6U 开发板支持音频&#xff0c;板上搭载了音频编解码芯片 WM8960&#xff0c;支持播放以及录音功能&#xff01;本章我们来学习 Linux 下的音频应用编程&#xff0c;音频应用编程相比于前面几个章节所介绍的内容、其难度有所上升&#xff0c;但是笔者仅向大家介绍 Li…

网络应用技术 实验八:防火墙实现访问控制(华为ensp)

目录 一、实验简介 二、实验目的 三、实验需求 四、实验拓扑 五、实验步骤 1、设计全网 IP 地址 2、设计防火墙安全策略 3、在 eNSP 中部署园区网 4、配置用户主机地址 5、配置网络设备 配置交换机SW-1~SW-5 配置路由交换机RS-1~RS-5 配置路由器R-1~R-3 6、配置仿…

低代码云组态支持draw.io导入导出

支持draw.io 官网&#xff1a;draw.io 绘图 进入官网绘制模型&#xff0c;完成后导出 导出 选择“文件“ > “导出“ > “SVG“,完成后即可进行导入 新建 在低代码平台新建一个“网络拓扑”模型&#xff0c;如下图所示&#xff1a; 设计 新建的“网络拓扑”模型进行…

SpringMVC全局异常处理

一、Java中的异常 定义&#xff1a;异常是程序在运行过程中出现的一些错误&#xff0c;使用面向对象思想把这些错误用类来描述&#xff0c;那么一旦产生一个错误&#xff0c;即创建某一个错误的对象&#xff0c;这个对象就是异常对象。 类型&#xff1a; 声明异常&#xff1…

QT自定义控件实践--滑动组件

概述 本篇文章,会逐步带您了解,如何自定义一个QT的滑动组件 操作步骤 选择合适的基类继承: 我们命名这个自定义控件为MySlipButton,继承自QWidget 添加成员变量: 根据滑动组件的特性,添加合适的成员变量,如当前值、最小值、最大值、滑块的位置等。 定义必要的方…

【零成本抽象】基本概念与在C++中的实现

零成本抽象概念是由 Bjarne Stroustrup 提出的&#xff0c;他在 1994 年的著作中就有相关设想&#xff0c;2016 年其在 C 大会登台演讲时&#xff0c;明确阐述了 C 中的 “零成本抽象” 这一理念。 一、零成本抽象概念 Bjarne Stroustrup提出的零成本抽象概念&#xff0c;是指…

基于遗传优化算法的带时间窗多车辆路线规划matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于遗传优化算法的带时间窗多车辆路线规划matlab仿真&#xff0c;通过输入各个节点坐标&#xff0c;以及出发点到节点的时间窗&#xff0c;来进行优化&#xff0…

HTML前端开发-- Iconfont 矢量图库使用简介

一、SVG 简介及基础语法 1. SVG 简介 SVG&#xff08;Scalable Vector Graphics&#xff09;是一种基于 XML 的矢量图形格式&#xff0c;用于在网页上显示二维图形。SVG 图形可以无限缩放而不会失真&#xff0c;非常适合用于图标、图表和复杂图形。SVG 文件是文本文件&#x…

网络安全——防火墙

基本概念 防火墙是一个系统&#xff0c;通过过滤传输数据达到防止未经授权的网络传输侵入私有网络&#xff0c;阻止不必要流量的同时允许必要流量进入。防火墙旨在私有和共有网络间建立一道安全屏障&#xff0c;因为网上总有黑客和恶意攻击入侵私有网络来破坏&#xff0c;防火…

高质量阅读微信小程序ssm+论文源码调试讲解

第2章 开发环境与技术 高质量阅读微信小程序的编码实现需要搭建一定的环境和使用相应的技术&#xff0c;接下来的内容就是对高质量阅读微信小程序用到的技术和工具进行介绍。 2.1 MYSQL数据库 本课题所开发的应用程序在数据操作方面是不可预知的&#xff0c;是经常变动的&…

Linux部署oceanbase

一、源码部署 1. 下载官网安装包 https://www.oceanbase.com/softwarecenter 2. 上传安装包并解压缩 #在/home目录下创建oceanbase文件夹 mkdir oceanbase cd oceanbase/ tar -xzf oceanbase-all-in-one-4.2.1_bp10_20241122.el7.x86_64.tar.gz 3. 安装 cd oceanbase-all-in…

【论文阅读】Fifty Years of the ISCA: A Data-Driven Retrospective

学习体会&#xff1a; ISCA会议近五十年文章分析, 了解论文热点方向, 处理器依旧是热点! AI和并行是大趋势, 做XPU相关目前来说还是热点~ 摘录自原文 摘录: 数据来源和分析方法&#xff1a; 作者收集了 ACM 数字图书馆中所有 ISCA 论文&#xff0c;并使用 DBLP、Google Schol…

设置docker镜像加速器

阿里云镜像中心 https://cr.console.aliyun.com/cn-hangzhou/instances/mirrors 登陆阿里云账号后&#xff0c;可以看到镜像加速器的配置&#xff0c;如下图所示 参考文章地址 Docker 镜像库国内加速的几种方法_docker 加速-CSDN博客