当前位置: 首页 > news >正文

linux网络编程7——协程设计原理与汇编实现

文章目录

  • 协程设计原理与汇编实现
    • 1. 协程概念
    • 2. 协程的实现
      • 2.1 setjmp
      • 2.2 ucontext
      • 2.3 汇编实现
      • 2.4 优缺点
      • 2.5 实现协程原语
        • 2.5.1 create()
        • 2.5.2 yield()
        • 2.5.3 resume()
        • 2.5.4 exit()
        • 2.5.5 switch()
        • 2.5.6 sleep()
      • 2.6 协程调度器
    • 3. 利用hook使用协程版本的库函数
    • 学习参考

协程设计原理与汇编实现

本文介绍了协程的概念、特征、优势、以及其实现原理。

1. 协程概念

协程是一种轻量级的用户态线程。它允许在单个线程内执行多个任务,使得程序可以在不同的函数之间灵活地切换,以便更好地利用 CPU 资源。这种机制特别适合 IO 密集型任务(如网络请求、文件读写)和异步编程场景。协程可以被暂停和恢复,避免了阻塞等待,同时不需要系统级线程的切换成本。

协程的实现在底层是由执行流的跳转切换机制实现的。一般情况,有一个协程调度器作为每个协程挂起时要切换回的代码。

应用场景

  • webserver
  • kv存储
  • 图床,网络层

同步和异步

"同步"和"异步"主要是指在执行任务时,任务与调用方的相互关系。在同步操作中,调用方会等待任务执行完毕然后继续执行。在异步操作中,调用方会立即返回并继续执行后续的操作,不会等待任务执行完,任务执行完可以通过回调、事件等方式通知调用方。

异步的好处

  • 多线程并发,充分利用cpu,性能好。

异步的坏处

  • 代码复杂,不好理解,需要设置回调函数或者使用事件机制。

协程的好处

  • 同步的编程方式,实现异步的性能。

互联网中协程可能被用到的场景

  1. 浏览器网页加载发送异步HTTP请求时可能用到了协程。
  2. 淘宝商店界面加载商品信息
  3. 直播界面加载评论和视频流
  4. 贴吧加载新的帖子回复
  5. bilibili异步加载新的回复
  6. 网络游戏中加载各种位置信息
  7. 微信聊天时,需要异步加载和发送信息
  8. 音视频通话异步加载流媒体
  9. chatgpt异步发送和接收问答消息
  10. github的git仓库托管服务器可能使用协程处理用户的push、pull等请求

2. 协程的实现

2.1 setjmp

setjmplongjmp 提供了一种低级的非局部跳转机制,适用于需要在 C 程序中实现复杂控制流或异常处理的情况。但由于它们带来的复杂性和潜在风险,使用时需要小心,确保不会影响程序的可维护性和可读性。

代码示例

#include <setjmp.h>
#include <stdio.h>jmp_buf env1, env2, env3;// coroutine1
void func1(void)
{int cur = 0;int ret = setjmp(env1);if (ret == 0)longjmp(env3, 1);printf("func1: %d [%d]\n", ret, cur++);if (ret < 20){longjmp(env2, ++ret);    }
}// coroutine1
void func2(void)
{int cur = 0;int ret = setjmp(env2);printf("func2: %d [%d]\n", ret, cur++);if (ret < 20){longjmp(env1, ++ret);    }
}int main()
{int ret = setjmp(env3);if (ret == 0)func1();elsefunc2();return 0;
}

从实现代码中可以看到setjmp机制需要我们自己保证协程所在的栈空间已被建立,并且还没有退出。协程所在的函数需要先手动执行,才能进行调度。协程的调度也比较麻烦。

2.2 ucontext

ucontext 是一种用于实现协程和用户态线程的机制。它在一些类 Unix 系统(例如 Linux)中提供了在用户态创建、切换和恢复上下文的接口。ucontext 通过保存和恢复 CPU 寄存器、堆栈指针等状态,允许程序在不同执行流之间切换,适用于实现协程和轻量级任务调度等。

其中保存协程上下文信息的结构体ucontext_t为

#include <ucontext.h>typedef struct ucontext {ucontext_t *uc_link;       // 执行结束后切换到的上下文sigset_t uc_sigmask;       // 信号屏蔽字stack_t uc_stack;          // 栈信息(地址和大小)mcontext_t uc_mcontext;    // 寄存器状态
} ucontext_t;

ucontext API 提供了几个主要函数来创建和切换上下文:

  1. getcontext(ucontext_t *ucp):获取当前上下文并保存到 ucp
  2. setcontext(const ucontext_t *ucp):恢复指定上下文并跳转到该上下文。
  3. makecontext(ucontext_t *ucp, void (*func)(), int argc, ...):为 ucp 配置要执行的函数 func 及其参数。
  4. swapcontext(ucontext_t *oucp, const ucontext_t *ucp):保存当前上下文到 oucp,然后切换到 ucp 上下文。

代码示例

#include <ucontext.h>
#include <stdio.h>ucontext_t ctx[2];
ucontext_t main_ctx;int count = 0;// coroutine1
void func1(void)
{int cur = 0;while (count++ < 20){printf("func1: %d [%d]\n", count, cur++);// yieldswapcontext(&ctx[0], &ctx[1]);}
}// coroutine1
void func2(void)
{int cur = 0;while (count++ < 20){printf("func2: %d [%d]\n", count, cur++);// yieldswapcontext(&ctx[1], &ctx[0]);}
}int main()
{char stack1[2048] = {0};char stack2[2048] = {0};getcontext(&ctx[0]);ctx[0].uc_stack.ss_sp = stack1;ctx[0].uc_stack.ss_size = sizeof(stack1);// 执行完之后跳转的地方ctx[0].uc_link = &main_ctx;makecontext(&ctx[0], func1, 0);getcontext(&ctx[1]);ctx[1].uc_stack.ss_sp = stack2;ctx[1].uc_stack.ss_size = sizeof(stack2);ctx[1].uc_link = &main_ctx;makecontext(&ctx[1], func2, 0);printf("start\n");swapcontext(&main_ctx, &ctx[0]);return 0;
}

ucontext 机制虽然强大,但需要谨慎使用。现代开发中,通常使用其他更高层的协程库,如 libco、libuv 或 Boost.Context 等。

2.3 汇编实现

使用汇编语言来实现协程的切换:主要操作为恢复和保存寄存器的值

int _switch(nty_cpu_ctx *new_ctx, nty_cpu_ctx *cur_ctx);__asm__(
"    .text                                  \n"
"       .p2align 4,,15                                   \n"
".globl _switch                                          \n"
".globl __switch                                         \n"
"_switch:                                                \n"
"__switch:                                               \n"
"       movq %rsp, 0(%rsi)      # save stack_pointer     \n"
"       movq %rbp, 8(%rsi)      # save frame_pointer     \n"
"       movq (%rsp), %rax       # save insn_pointer      \n"
"       movq %rax, 16(%rsi)                              \n"
"       movq %rbx, 24(%rsi)     # save rbx,r12-r15       \n"
"       movq %r12, 32(%rsi)                              \n"
"       movq %r13, 40(%rsi)                              \n"
"       movq %r14, 48(%rsi)                              \n"
"       movq %r15, 56(%rsi)                              \n"
"       movq 56(%rdi), %r15                              \n"
"       movq 48(%rdi), %r14                              \n"
"       movq 40(%rdi), %r13     # restore rbx,r12-r15    \n"
"       movq 32(%rdi), %r12                              \n"
"       movq 24(%rdi), %rbx                              \n"
"       movq 8(%rdi), %rbp      # restore frame_pointer  \n"
"       movq 0(%rdi), %rsp      # restore stack_pointer  \n"
"       movq 16(%rdi), %rax     # restore insn_pointer   \n"
"       movq %rax, (%rsp)                                \n"
"       ret                                              \n"
);

上面的_switch函数实现了协程上下文的切换,和线程切换所作的工作类似

2.4 优缺点

  1. setjmp实现方式复杂,但是跨平台性好
  2. ucontext实现方式简单,但是跨平台性一般
  3. 汇编实现方式复杂,跨平台型差,但是效率高

2.5 实现协程原语

2.5.1 create()

主要工作是创建一个保存协程上下文的数据结构。一个协程的上下文必须包括如下信息:

  • 协程运行的函数和参数信息

  • cpu寄存器上下文

  • 运行时栈上下文

  • 协程状态

  • 协程id

  • 协程所属的调度器

  • 其他信息

一个示例如下:

struct _coroutine_context
{ucontext_t ctx;			// 里面包括寄存器状态和栈上下文proc_coroutine func;	// 协程运行的函数和参数信息void *arg;void *data;coroutine_status status;	// 协程状态scheduler *sched;			// 所属的调度器uint64_t id;
};

创建协程所作的主要工作包括:

  • 分配一个协程上下文并初始化
  • 获取并设置调度器
  • 将改协程加入调度器进行管理
2.5.2 yield()

主要工作是调用swapcontext()或者_switch()切换会协程调度器。

2.5.3 resume()

主要工作是恢复协程的执行。

2.5.4 exit()

主要工作是协程从调度器中删除,然后释放协程上下文。

2.5.5 switch()

协程切换,主要是切换协程的寄存器。

2.5.6 sleep()

让协程停止执行一段时间。

2.6 协程调度器

协程调度器管理协程,包括一个就绪协程队列,一个sleep协程的集合,一个运行时协程队列,一个等待协程集合。可以采用事件机制,当某事件发生时(例如某fd可读),可以将相应的协程从等待集合中取出并恢复执行。

其核心代码如下

while (1)
{// 检查sleep集合,查看是否有协程超时coroutine_context *expired;while ((expired = check_expired(sched))){resume(expired);}// 检查wait结合,查看是否有协程有监听的事件发生coroutine_context *waked;int nready = epoll_wait(epfd, events, EVENTS_SIZE, 1);for (int i = 0; i < nready; ++i){waked = wait_search(events[i].data.fd);resume(waked);}// 恢复ready队列中的协程的运行coroutine_context *rdy;while (!is_ready_empty(sched)){rdt = ready_pop(sched);resyme(rdt);}
}

3. 利用hook使用协程版本的库函数

利用运行时动态链接,可以在运行时将一个函数替换为为使用协程的版本。

例如,以下代码将read函数在运行时替换为了另一个函数:

#include <dlfcn.h>
#include <unistd.h>typedef ssize_t (*readf_t)(int fd, void *buf, size_t count);readf_t readf;void init_hook()
{readf = (readf_t)dlsym(RTLD_NEXT, "read");
}ssize_t read(int fd, void *buf, size_t count)
{if (!readf) init_hook();// 如果对应的fd不可读,那么就挂起协程yield_if_not_ok(fd, POLLIN | POLLERR | POLLHUP);return readf(fd, buf, count);
}

学习参考

学习更多相关知识请参考零声 github。

相关文章:

linux网络编程7——协程设计原理与汇编实现

文章目录 协程设计原理与汇编实现1. 协程概念2. 协程的实现2.1 setjmp2.2 ucontext2.3 汇编实现2.4 优缺点2.5 实现协程原语2.5.1 create()2.5.2 yield()2.5.3 resume()2.5.4 exit()2.5.5 switch()2.5.6 sleep() 2.6 协程调度器 3. 利用hook使用协程版本的库函数学习参考 协程设…...

Ubuntu22.04版本左右,扩充用户可使用内存

1 取得root权限后&#xff0c;输入命令 lsblk 查看所有磁盘和分区&#xff0c;找到想要替换用户可使用文件夹内存的磁盘和分区。若没有进行分区&#xff0c;并转为所需要的分区数据类型&#xff0c;先进行分区与格式化&#xff0c;过程自行查阅。 扩充替换过程&#xff0c;例如…...

基于ArcMap中Python 批量处理栅格数据(以按掩膜提取为例)

注&#xff1a;图片来源于公众号&#xff0c;公众号也是我自己的。 ArcMap中的python编辑器是很多本科生使用ArcMap时容易忽略的一个工具&#xff0c;本人最近正在读一本书《ArcGIS Python 编程基础与应用》&#xff0c;在此和大家分享、交流一些相关的知识。 这篇文章主要分享…...

【flink】之集成mybatis对mysql进行读写

背景&#xff1a; 在现代大数据应用中&#xff0c;数据的高效处理和存储是核心需求之一。Flink作为一款强大的流处理框架&#xff0c;能够处理大规模的实时数据流&#xff0c;提供丰富的数据处理功能&#xff0c;如窗口操作、连接操作、聚合操作等。而MyBatis则是一款优秀的持…...

Java设计模式—观察者模式详解

引言 模式角色 UML图 示例代码 应用场景 优点 缺点 结论 引言 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为设计模式&#xff0c;它定义了对象之间的一对多依赖关系&#xff0c;当一个对象的状态发生改变时&#xff0c;所有依赖于它的对象都会得到通知…...

【Cri-Dockerd】安装cri-dockerd

cri-dockerd的作用&#xff1a; 在k8s1.24之前。k8s会通过dockershim来调用docker进行容器运行时containerd&#xff0c;并且会自动安装dockershim&#xff0c;但是从1.24版本之前k8s为了降低容器运行时的调用的复杂度和效率&#xff0c;直接调用containerd了&#xff0c;并且…...

GCC及GDB的使用

参考视频及博客 https://www.bilibili.com/video/BV1EK411g7Li/?spm_id_from333.999.0.0&vd_sourceb3723521e243814388688d813c9d475f https://www.bilibili.com/video/BV1ei4y1V758/?buvidXU932919AEC08339E30CE57D39A2BABF6A44F&from_spmidsearch.search-result.0…...

大数据新视界 -- 大数据大厂之大数据重塑影视娱乐产业的未来(4 - 3)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…...

数据结构——基础知识补充

1.队列 1.普通队列 queue.Queue 是 Python 标准库 queue 模块中的一个类&#xff0c;适用于多线程环境。它实现了线程安全的 FIFO&#xff08;先进先出&#xff09;队列。 2.双端队列 双端队列&#xff08;Deque&#xff0c;Double-Ended Queue&#xff09;是一种具有队列和…...

只有.git文件夹时如何恢复项目

有时候误删文件但由于.git是隐藏文件夹而幸存&#xff0c;或者项目太大&#xff0c;单单甩给你一个.git文件夹让你自己恢复整个项目&#xff0c;该怎么办呢&#xff1f; 不用担心&#xff0c;只要进行以下步骤&#xff0c;即可把原项目重新搭建起来&#xff1a; 创建一个文件…...

anchor、anchor box、bounding box之间关系

最近学YOLO接触到这些概念&#xff0c;一下子有点蒙&#xff0c;简单总结一下。 anchor和anchor box Anchor&#xff1a;表示一组预定义的尺寸比例&#xff0c;用来代表常见物体的宽高比。可以把它看成是一个模板或规格&#xff0c;定义了物体框的“形状”和“比例”&#xff…...

代码随想录算法训练营第三十天 | 452.用最少数量的箭引爆气球 435.无重叠区间 763.划分字母区间

LeetCode 452.用最少数量的箭引爆气球&#xff1a; 文章链接 题目链接&#xff1a;452.用最少数量的箭引爆气球 思路&#xff1a; 气球的区间有重叠部分&#xff0c;只要弓箭从重叠部分射出来&#xff0c;那么就能减少所使用的弓箭数 **局部最优&#xff1a;**只要有重叠部分…...

海亮科技亮相第84届中国教装展 尽显生于校园 长于校园教育基因

10月25日&#xff0c;第84届中国教育装备展示会&#xff08;以下简称“教装展”&#xff09;在昆明滇池国际会展中心开幕。作为国内教育装备领域规模最大、影响最广的专业展会&#xff0c;本届教装展以“数字赋能教育&#xff0c;创新引领未来”为主题&#xff0c;为教育领域新…...

C语言数据结构学习:栈

C语言 数据结构学习 汇总入口&#xff1a; C语言数据结构学习&#xff1a;[汇总] 1. 栈 栈&#xff0c;实际上是一种特殊的线性表。这里使用的是链表栈&#xff0c;链表栈的博客&#xff1a;C语言数据结构学习&#xff1a;单链表 2. 栈的特点 只能在一端进行存取操作&#x…...

如何快速分析音频中的各种频率成分

从视频中提取音频 from moviepy.editor import VideoFileClip# Load the video file and extract audio video_path "/mnt/data/WeChat_20241026235630.mp4" video_clip VideoFileClip(video_path)# Extract audio and save as a temporary file for further anal…...

MongoDB 6.0 主从复制配置

以下是 MongoDB 6.0 版本配置主从的详细安装步骤&#xff1a; 1. 安装 MongoDB&#xff1a;可以从官网下载 MongoDB 6.0 的安装包并进行安装&#xff0c;或者使用相应的包管理工具进行安装。 2. 配置主节点&#xff1a;在主节点的 MongoDB 配置文件&#xff08;默认路径为 …...

NPU 神经网络处理单元

Ⅰ 什么是 NPU&#xff1f; 当前正处于神经网络和机器学习处理需求爆发的初期。传统的 CPU&#xff08;中央处理器&#xff09;/GPU&#xff08;图形处理器&#xff09;可以执行类似任务&#xff0c;但专门为神经网络优化的 NPU&#xff08;神经处理单元&#xff09;比 CPU/GP…...

安宝特分享 | AR技术引领:跨国工业远程协作创新模式

在当今高度互联的工业环境中&#xff0c;跨国合作与沟通变得日益重要。然而&#xff0c;语言障碍常常成为高效协作的绊脚石。安宝特AR眼镜凭借其强大的多语言自动翻译和播报功能&#xff0c;正在改变这一局面&#xff0c;让远程协作变得更加顺畅。 01 多语言翻译优势 安宝特A…...

Vulkan 开发(五):Vulkan 逻辑设备

图片来自《Vulkan 应用开发指南》 Vulkan 开发系列文章&#xff1a; 1. 开篇&#xff0c;Vulkan 概述 2. Vulkan 实例 3. Vulkan 物理设备 4. Vulkan 设备队列 在 Vulkan 中&#xff0c;逻辑设备&#xff08;Logical Device&#xff09;是与物理设备&#xff08;Physical D…...

Kafka 解决消息丢失、乱序与重复消费

一、引言 在分布式系统中&#xff0c;Apache Kafka 作为一种高吞吐量的分布式发布订阅消息系统&#xff0c;被广泛应用于日志收集、流式处理、消息队列等场景。然而&#xff0c;在实际使用过程中&#xff0c;可能会遇到消息丢失、乱序、重复消费等问题&#xff0c;这些问题可能…...

IEC102协议报文解析:从格式到传输的实战指南

1. IEC102协议基础入门&#xff1a;电力系统的"语言密码" 第一次接触IEC102协议时&#xff0c;我完全被那些十六进制代码和术语搞晕了。直到有一次在变电站调试电表&#xff0c;看到主站和终端设备用这种"暗号"流畅对话&#xff0c;才真正理解它的价值。简…...

特征选择新思路:Laplacian Score与PCA/Lasso对比实验报告

特征选择方法深度对比&#xff1a;Laplacian Score在真实数据集中的突围表现 当面对高维数据时&#xff0c;特征选择就像是在嘈杂的市场中寻找真正有价值的声音。传统的PCA和Lasso方法已经服务了我们多年&#xff0c;但Laplacian Score带来的图论视角正在悄然改变游戏规则。本文…...

Windows 系统下通过 composer 快速搭建 ThinkPHP6 开发环境及实战配置指南

1. 环境准备&#xff1a;Windows下搭建ThinkPHP6的基础条件 在Windows系统下搭建ThinkPHP6开发环境&#xff0c;首先需要确保基础软件栈的完整性。我遇到过不少新手开发者直接跳过了环境检查环节&#xff0c;结果在后续步骤中频繁报错。这里分享几个必须提前准备好的关键组件&a…...

2步实现格式自由:Save Image as Type让网页图片转换体验升级10倍

2步实现格式自由&#xff1a;Save Image as Type让网页图片转换体验升级10倍 【免费下载链接】Save-Image-as-Type Save Image as Type is an chrome extension which add Save as PNG / JPG / WebP to the context menu of image. 项目地址: https://gitcode.com/gh_mirrors…...

nli-distilroberta-base效果展示:Entailment/Contradiction/Neutral三类判别置信度热力图

nli-distilroberta-base效果展示&#xff1a;Entailment/Contradiction/Neutral三类判别置信度热力图 1. 项目概述 nli-distilroberta-base是基于DistilRoBERTa模型的自然语言推理(NLI)Web服务&#xff0c;专门用于分析两个句子之间的逻辑关系。这个轻量级模型能够快速准确地…...

数据库工具效率提升指南:三步掌握开源数据库管理新范式

数据库工具效率提升指南&#xff1a;三步掌握开源数据库管理新范式 【免费下载链接】dblab The database client every command line junkie deserves. 项目地址: https://gitcode.com/gh_mirrors/db/dblab 在数据驱动开发的时代&#xff0c;开源数据库管理工具已成为开…...

无需Root!用KSWEB在旧安卓手机上搞个私人服务器:文件共享+内网穿透实战

无需Root&#xff01;用KSWEB在旧安卓手机上搭建全能私人服务器 家里闲置的安卓手机别急着扔&#xff0c;只需安装一个KSWEB应用&#xff0c;就能变身为功能齐全的私人服务器。这个方案特别适合想低成本搭建家庭NAS、个人云存储或测试环境的极客用户。相比动辄上千元的专业NAS设…...

三步搞定!用bilidown轻松下载B站8K超清视频的完整指南

三步搞定&#xff01;用bilidown轻松下载B站8K超清视频的完整指南 【免费下载链接】bilidown 哔哩哔哩视频解析下载工具&#xff0c;支持 8K 视频、Hi-Res 音频、杜比视界下载、批量解析&#xff0c;可扫码登录&#xff0c;常驻托盘。 项目地址: https://gitcode.com/gh_mirr…...

如何用Chanlun-Pro实现量化缠论交易?终极实战指南

如何用Chanlun-Pro实现量化缠论交易&#xff1f;终极实战指南 【免费下载链接】chanlun-pro 基于缠中说禅所讲缠论理论&#xff0c;以便量化分析市场行情的工具 项目地址: https://gitcode.com/gh_mirrors/ch/chanlun-pro Chanlun-Pro是一款基于缠中说禅理论的量化交易工…...

Linux服务器无GPU也能跑!Ollama部署DeepSeek-R1模型存储路径自定义与性能调优指南

Linux服务器无GPU高效部署DeepSeek-R1模型全攻略&#xff1a;从存储路径优化到性能调优 当你在云服务器或老旧设备上尝试运行AI模型时&#xff0c;是否经常遇到存储空间不足或性能低下的困扰&#xff1f;本文将带你深入探索如何在无GPU的Linux环境中&#xff0c;通过Ollama高效…...