协程5 --- 栈切换

文章目录

  • ucontext
    • 相关函数
    • 例子
    • ucontext_t结构
  • setjump、longjump
    • 相关函数
    • 例子
    • jmp_buf结构
  • 汇编实现
    • 解析
    • 图示

ucontext

相关函数

#include <ucontext.h>
int getcontext(ucontext_t *ucp);初始化ucp结构体,将当前上下文保存在ucp中。
int setcontext(const ucontext_t *ucp);设置当前上下文为ucp所指向的上下文。
void makecontext(ucontext_t *ucp, void (*func)(), int argc, ...);设置执行的函数指针,调用之前要先调用getcontext()
int swapcontext(ucontext_t *oucp, const ucontext_t *ucp);保存当前上下文到oucp,并且切换上下文到ucp

例子

#include <stdio.h>
#include <stdlib.h>
#include <ucontext.h>// 栈大小
#define STACK_SIZE 1024ucontext_t *coroutineA, *coroutineB;// 栈运行函数
void coroutineStart1()
{printf("B");// 切换运行协程swapcontext(coroutineB, coroutineA);printf("D");// 切换运行协程swapcontext(coroutineB, coroutineA);
}int main()
{printf("A");// 保存主线程coroutineA = (ucontext_t*)malloc(sizeof(ucontext_t));// 创建协程coroutineB = (ucontext_t*)malloc(sizeof(ucontext_t));char *stack = (char*)malloc(STACK_SIZE);getcontext(coroutineB);coroutineB->uc_stack.ss_sp = stack;coroutineB->uc_stack.ss_size = STACK_SIZE;coroutineB->uc_link = coroutineA;makecontext(coroutineB, coroutineStart1, 0);// 切换运行协程swapcontext(coroutineA, coroutineB);printf("C");// 切换运行协程swapcontext(coroutineA, coroutineB);printf("E\n");free(stack);free(coroutineB);free(coroutineA);return 0;
}

运行结果:

ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ gcc -O0 -o ucontext ucontext.c
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ ./ucontext 
ABCDE

ucontext_t结构

#ifdef __USE_MISC
# define __ctx(fld) fld
#else
# define __ctx(fld) __ ## fld
#endif/* Userlevel context.  */
typedef struct ucontext_t{unsigned long int __ctx(uc_flags);struct ucontext_t *uc_link; // 当前context执行结束之后要执行的下一个contextstack_t uc_stack; // 上下文所需要的栈mcontext_t uc_mcontext; // 执行上下文sigset_t uc_sigmask; // 信号掩码struct _libc_fpstate __fpregs_mem; // fpu寄存器__extension__ unsigned long long int __ssp[4]; // 栈顶指针} ucontext_t;

栈:

/* Structure describing a signal stack.  */
typedef struct{void *ss_sp; // 栈底指针int ss_flags; // SS_ONSTACK - 表明进程正在备选信号栈上执行 SS_DISABLE - 备用堆栈被停用size_t ss_size; // 栈大小} stack_t;

上下文:

/* Context to describe whole processor state.  */
typedef struct{gregset_t __ctx(gregs); // 通用寄存器/* Note that fpregs is a pointer.  */fpregset_t __ctx(fpregs); // fpu寄存器指针__extension__ unsigned long long __reserved1 [8];
} mcontext_t;/* Type for general register.  */
__extension__ typedef long long int greg_t;/* Number of general registers.  */
#define __NGREG 23
#ifdef __USE_MISC
# define NGREG  __NGREG
#endif/* Container for all general registers.  */
typedef greg_t gregset_t[__NGREG];/* Structure to describe FPU registers.  */
typedef struct _libc_fpstate *fpregset_t;

setjump、longjump

相关函数

#include <setjmp.h>
int setjmp(jmp_buf env);将状态信息保存在env中。返回值:若直接调用则返回0,若从longjmp调用返回则返回非0值的longjmp中的val值。
void longjmp(jmp_buf env, int val);调用此函数则返回到语句setjmp所在的地方,其中env就是setjmp中的 env,而val则是使setjmp的返回值变为val。

例子

#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>// 栈运行函数指针类型
typedef void(*coroutineStart)();// 切换栈
#define yieldTo(srcCoroutine, descCoroutine) \
if (0 == setjmp(srcCoroutine->buf)) \
{ \descCoroutine->jmp(); \
}class coroutine
{
public:jmp_buf buf;coroutineStart entry;bool called;coroutine(coroutineStart entry){this->entry = entry;called = (entry == NULL);}void jmp(){if (called){longjmp(buf, 1);}else{called = true;entry();}}
};coroutine* coroutineA, * coroutineB;// 栈运行函数
void coroutineStart1()
{printf("B");// 切换运行协程yieldTo(coroutineB, coroutineA);printf("D");// 切换运行协程yieldTo(coroutineB, coroutineA);
}int main()
{printf("A");// 保存主线程coroutineA = new coroutine(NULL);// 创建协程coroutineB = new coroutine(coroutineStart1);// 切换运行协程yieldTo(coroutineA, coroutineB);printf("C");// 切换运行协程yieldTo(coroutineA, coroutineB);printf("E\n");delete coroutineA;delete coroutineB;return 0;
}

运行结果:

ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ g++ -O0 -o setjump setjump.cpp
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ ./setjump 
ABCDE

jmp_buf结构

typedef struct __jmp_buf_tag jmp_buf[1]; // 这边为什么要设置成只有一个长度的数组?/* Calling environment, plus possibly a saved signal mask.  */
struct __jmp_buf_tag{/* NOTE: The machine-dependent definitions of `__sigsetjmp'assume that a `jmp_buf' begins with a `__jmp_buf' and that`__mask_was_saved' follows it.  Do not move these membersor add others before it.  */__jmp_buf __jmpbuf;    /* Calling environment.  */ // 环境信息int __mask_was_saved;  /* Saved the signal mask?  */ // 信号是否保存__sigset_t __saved_mask;  /* Saved signal mask.  */ // 信号集};

为什么要设置成只有一个长度的数组?
在这里插入图片描述

sizeof(数组名) 为数组长度
&数组名 得到的是数组指针,数组指针的步长为整个数组的长度
作为函数参数退化为指针

buffer:

# if __WORDSIZE == 64
typedef long int __jmp_buf[8]; // 8个寄存器的保存
# elif defined  __x86_64__
__extension__ typedef long long int __jmp_buf[8];
# else
typedef int __jmp_buf[6];
# endif

setjmp 在 glibc 的实现:

ENTRY (__sigsetjmp)/* Save registers.  */movq %rbx, (JB_RBX*8)(%rdi)
#ifdef PTR_MANGLE // 这里对%rbp、%rsp、%rip加密了
# ifdef __ILP32__/* Save the high bits of %rbp first, since PTR_MANGLE willonly handle the low bits but we cannot presume %rbp isbeing used as a pointer and truncate it.  Here we write allof %rbp, but the low bits will be overwritten below.  */movq %rbp, (JB_RBP*8)(%rdi)
# endifmov %RBP_LP, %RAX_LPPTR_MANGLE (%RAX_LP)mov %RAX_LP, (JB_RBP*8)(%rdi)
#elsemovq %rbp, (JB_RBP*8)(%rdi)
#endifmovq %r12, (JB_R12*8)(%rdi)movq %r13, (JB_R13*8)(%rdi)movq %r14, (JB_R14*8)(%rdi)movq %r15, (JB_R15*8)(%rdi)lea 8(%rsp), %RDX_LP /* Save SP as it will be after we return.  */
#ifdef PTR_MANGLEPTR_MANGLE (%RDX_LP)
#endifmovq %rdx, (JB_RSP*8)(%rdi)mov (%rsp), %RAX_LP /* Save PC we are returning to now.  */LIBC_PROBE (setjmp, 3, LP_SIZE@%RDI_LP, -4@%esi, LP_SIZE@%RAX_LP)
#ifdef PTR_MANGLEPTR_MANGLE (%RAX_LP)
#endifmovq %rax, (JB_PC*8)(%rdi)

信号集:

#define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))
typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} __sigset_t;

汇编实现

看实现之前可以先看一下另一个博客特殊的栈溢出例子

#include <stdio.h>
#include <stdlib.h>// 栈大小
#define STACK_SIZE 1024// 栈运行函数指针类型
typedef void(*coroutineStart)();class coroutine
{
public:long* stack_pointer;char* stack;coroutine(coroutineStart entry){if (entry == NULL){stack = NULL;stack_pointer = NULL;return;}// 栈申请(堆空间模拟)stack = (char*)malloc(STACK_SIZE);// 栈基址char* base = stack + STACK_SIZE;stack_pointer = (long*) base;// 写入函数地址stack_pointer -= 1;*stack_pointer = (long) entry;// 写入栈基址stack_pointer -= 1;*stack_pointer = (long) base;}~coroutine(){if (!stack){return;}free(stack);stack = NULL;}
};// 切换栈
void yieldTo(coroutine* srcCoroutine, coroutine* descCoroutine)
{__asm__ ("movq %%rsp, %0\n\t""movq %%rax, %%rsp\n\t":"=m"(srcCoroutine->stack_pointer):"a"(descCoroutine->stack_pointer):);
}coroutine *coroutineA, *coroutineB;// 栈运行函数
void coroutineStart1()
{printf("B");// 切换运行协程yieldTo(coroutineB, coroutineA);printf("D");// 切换运行协程yieldTo(coroutineB, coroutineA);
}int main()
{printf("A");// 保存主线程coroutineA = new coroutine(NULL);// 创建协程coroutineB = new coroutine(coroutineStart1);// 切换运行协程yieldTo(coroutineA, coroutineB);printf("C");// 切换运行协程yieldTo(coroutineA, coroutineB);printf("E\n");delete coroutineA;delete coroutineB;return 0;
}

运行结果:

ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ g++ -g -o coroutine -O0 80x86.cpp
ray@ray-PC:/data/home/ray/cppcode/CoroutineSwitch$ ./coroutine 
ABCDE

解析

用堆模拟栈的时候,堆栈的生长方向是相反的。

其中__asm__的代码:

  • :"a"(descCoroutine->stack_pointer)表示把descCoroutine->stack_pointer变量的值给到rax寄存器当做输入
  • :"=m"(srcCoroutine->stack_pointer) 表示用srcCoroutine->stack_pointer承接值
  • "movq %%rsp, %0\n\t"表示把rsp的值给到%0也就是srcCoroutine->stack_pointer
  • "movq %%rax, %%rsp\n\t"表示把rax的值给到rsp,其中rax的值就是descCoroutine->stack_pointer

也就是说yieldTo函数主要做的工作就是:

  • 把当前的rsp给到srcCoroutine->stack_pointer
  • descCoroutine->stack_pointer给到rsp
  • 函数返回的时候会把rbp切换成descCoroutine的栈基址

使用objdump看一下:

g++ -g -o coroutine -O0 80x86.cpp
objdump -d coroutine00000000004007f8 <main>:
...40084d:       48 8b 15 1c 08 20 00    mov    0x20081c(%rip),%rdx // descCoroutine400854:       48 8b 05 0d 08 20 00    mov    0x20080d(%rip),%rax // srcCoroutine40085b:       48 89 d6                mov    %rdx,%rsi // descCoroutine40085e:       48 89 c7                mov    %rax,%rdi // srcCoroutine400861:       e8 27 ff ff ff          callq  40078d <_Z7yieldToP9coroutineS0_>000000000040078d <_Z7yieldToP9coroutineS0_>:40078d:       55                      push   %rbp40078e:       48 89 e5                mov    %rsp,%rbp400791:       48 89 7d f8             mov    %rdi,-0x8(%rbp) // 栈中开辟临时变量srcCoroutine400795:       48 89 75 f0             mov    %rsi,-0x10(%rbp) // 栈中开辟临时变量descCoroutine400799:       48 8b 45 f0             mov    -0x10(%rbp),%rax // descCoroutine -> rax40079d:       48 8b 00                mov    (%rax),%rax // 指针偏移索引成员变量stack_pointer,这边真好是+04007a0:       48 8b 55 f8             mov    -0x8(%rbp),%rdx // srcCoroutine -> rdx4007a4:       48 89 22                mov    %rsp,(%rdx) // 老rsp -> srcCoroutine->stack_pointer4007a7:       48 89 c4                mov    %rax,%rsp // descCoroutine->stack_pointer -> rsp4007aa:       5d                      pop    %rbp4007ab:       c3                      retq

图示

创建AB协程:
在这里插入图片描述
第一次yieldTo,切到coroutineStart1
在这里插入图片描述
第二次yieldTo,切到main
在这里插入图片描述

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

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

相关文章

【Linux】Pinctrl子系统和GPIO子系统

Pinctrl子系统 在许多soc内部包含了多个pin控制器&#xff0c;通过pin控制器的寄存器&#xff0c;我们可以配置一个或者一组引脚的功能和特性。Linux内核为了统一各soc厂商的pin脚管理&#xff0c;提供了pinctrl子系统。该系统的作用&#xff1a; 在系统初始化的时候&#xf…

《Vue3 报错》Uncaught TypeError: s.finally is not a function

解决方案&#xff1a; 新建文件 my-polyfill.js // 当浏览器环境不支持Promise.prototype.finally if (!Promise.prototype[finally]) {Promise.prototype[finally] function(callback) {let P this.constructor;return this.then(value > P.resolve(callback()).then(…

RabbitMQ 七种工作模式介绍

目录 1.简单模式队列 2.WorkQueue(⼯作队列) 3 Publish/Subscribe(发布/订阅) 4 Routing(路由模式) 5.Topics(通配符模式) 6 RPC(RPC通信) 7 Publisher Confirms(发布确认) RabbitMQ 共提供了7种⼯作模式供我们进⾏消息传递,接下来一一介绍它的实现与目的 1.简单模式队列…

数组类算法【leetcode】

704. 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c;写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。 二分查找 用于有序数组中&#xff0c;没有重复的数组。…

Pandas 数据分析工具详细教程

Pandas 数据分析工具详细教程 Pandas 是一个强大的 Python 数据分析库&#xff0c;广泛应用于数据科学、数据分析和机器学习等领域。它提供了高效的数据操作和分析功能&#xff0c;使得数据处理变得简单而高效。本文将详细介绍 Pandas 的基本概念、数据结构、常用操作及其在数…

基于 EventBridge + DashVector 打造 RAG 全链路动态语义检索能力

作者&#xff1a;肯梦 本文将演示如何使用事件总线&#xff08;EventBridge&#xff09;&#xff0c;向量检索服务&#xff08;DashVector&#xff09;&#xff0c;函数计算&#xff08;FunctionCompute&#xff09;结合灵积模型服务 [ 1] 上的 Embedding API [ 2] &#xff0…

GooglePlay: 应用和游戏的内容分级

对于后台私信的开发者们,希望能够携带详细过审记录和拒审邮件一同发来,方便我们尽快解决问题 应用与游戏 为您的应用或游戏选择类别和标签选择要添加的标签选择类别并添加标签类别示例与应用、游戏以及两者中所投放广告的内容分级相关的要求应用如何获得内容分级内容分级的用…

将Notepad++添加到右键菜单【一招实现】

一键添加注册表 复制以下代码保存为 Notepad.reg&#xff0c;将红框内路径修改为自己电脑的“Notepad.exe路径”后&#xff0c;再双击运行即可。 Windows Registry Editor Version 5.00[HKEY_CLASSES_ROOT\*\shell\NotePad] "Notepad" "Icon""D:\\N…

[复健计划][紫书]Chapter 7 暴力求解法

7.1 简单枚举 例7-1 Division uva725 输入正整数n&#xff0c;按从小到大的顺序输出所有形如abcde/fghij n的表达式&#xff0c;其中a&#xff5e;j恰好为数字0&#xff5e;9的一个排列&#xff08;可以有前导0&#xff09;&#xff0c;2≤n≤79。枚举fghij&#xff0c;验证a…

【测试工具篇一】全网最强保姆级教程抓包工具Fiddler(2)

本文接上篇Fiddler介绍&#xff0c;开始讲fiddler如何使用之前&#xff0c;给大家讲讲http以及web方面的小知识&#xff0c;方便大家后面更好得理解fiddler使用。 目录 一、软件体系结构---B/S与C/S架构 B/S架构 C/S架构 二、HTTP基础知识 什么是http请求和响应? http协…

如何基于pdf2image实现pdf批量转换为图片

最近为了将pdf报告解析成为文本和图片&#xff0c;需要将大量多页的pdf文件拆分下单独的一页一页的图像&#xff0c;以便后续进行OCR和图像处理&#xff0c;因此就需要实现将pdf2image&#xff0c;本文主要结合开源的pdf2image和poppler&#xff0c;实现了pdf转换为png格式图片…

【Linux】Linux下查看cpu信息指令(top/mpstat/iostat/pidstat)说明

top命令 top(1) - Linux manual page (man7.org) top查看总的CPU利用率 us: 用户空间消耗的CPU资源占比&#xff0c;进程在用户态执行函数调用&#xff0c;编解码消耗的都是us sy: 内核空间消耗的CPU资源占比&#xff0c;进程调用系统调用达到内核后会增加sy的消耗 ni&…

Java学习者的福音:SpringBoot教学辅助平台

1系统概述 1.1 研究背景 随着计算机技术的发展以及计算机网络的逐渐普及&#xff0c;互联网成为人们查找信息的重要场所&#xff0c;二十一世纪是信息的时代&#xff0c;所以信息的管理显得特别重要。因此&#xff0c;使用计算机来管理教学辅助平台的相关信息成为必然。开发合适…

csrf令牌

csrf get请求 路由 // index.php Route::get(/, function () {// return view(welcome);return view(login); });Route::get(d3,function(Request $request){echo "输入的内容是" . "<font color>".$request -> input(mytext)."</fon…

高校实验室安全巡检系统设计与实现(源码+定制+开发)高校实验室巡检系统、实验室安全管理平台、实验室安全监控系统、智能实验室巡查系统、高校实验室风险管理

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

容器内pip安装Apache Airflow的经历:如何重置初始密码

背景 Apache Airflow™https://github.com/apache/airflow 是一个开源平台&#xff0c;用于开发、调度和监控面向批处理的工作流程。Airflow 可扩展的 Python 框架使您能够构建几乎可以连接任何技术的工作流程。Web 界面有助于管理工作流程的状态。Airflow 可以通过多种方式部…

微服务透传日志traceId

问题 在微服务架构中&#xff0c;一次业务执行完可能需要跨多个服务&#xff0c;这个时候&#xff0c;我们想看到业务完整的日志信息&#xff0c;就要从各个服务中获取&#xff0c;即便是使用了ELK把日志收集到一起&#xff0c;但如果不做处理&#xff0c;也是无法完整把一次业…

精心整理教育研究专题数据资源大全-最新出炉_附下载链接

教育研究专题数据资源大全V1.0 下载链接-点它&#x1f449;&#x1f449;&#x1f449;&#xff1a;教育研究专题数据资源大全-最新出炉.zip 资源介绍 一、中国教育统计年鉴面板数据 简介&#xff1a;《中国教育统计年鉴》是由教育部发展规划司根据全国各省、自治区、直辖市…

汽修行业员工培训SOP的智能化搭建

汽修行业正经历着技术革新和服务模式的双重变革&#xff0c;员工的专业培训变得尤为重要。智能化的员工培训标准操作程序&#xff08;SOP&#xff09;在线知识库不仅能够提升培训效率&#xff0c;还能确保服务质量和作业安全。本文将探讨汽修行业如何智能化地搭建员工培训的SOP…

还在担心Mac卸载不干净?XApp帮你干净完成卸载

Mac的卸载机制非常独特&#xff0c;虽然将app拖拽到废纸篓也能够完成卸载&#xff0c;但是会有很多的文件残留&#xff0c;那么如何卸载干净非常重要 XApp&#xff0c;免费的Mac卸载工具&#xff0c;有着强大的垃圾检测机制&#xff0c;检测出更深层的垃圾&#xff0c;卸载更干…