发布时间:2026/6/27 2:00:08
【C/C++】从 setjmp 到 ucontext 再到 hook:C 语言协程是怎么跑起来的?
【C/C】从 setjmp 到 ucontext 再到 hookC 语言协程是怎么跑起来的1. 为什么需要协程网络服务里经常有一个矛盾同步阻塞代码好写send()之后recv()业务逻辑顺着往下走异步事件模型性能好epoll_wait()拿到事件再按 fd 分发处理但纯异步代码容易变成回调、状态机、上下文传递一旦多个请求之间有依赖代码很快变复杂。课程笔记里对这个问题的概括很直接func(){async_send();async_recv();}async_xxx(){if(1poll(fd,0)){switch();// 判断 IO然后切换出去}}协程要解决的就是这个问题业务代码看起来像同步代码底层遇到 IO 不就绪时主动让出 CPU等 fd 就绪后调度器再恢复它。所以协程不是为了“炫技式切栈”而是为高并发 IO 服务写法上接近同步执行上接近异步调度发生在用户态切换成本比线程更轻和epoll组合后可以用少量线程管理大量连接。2. 协程的本质保存现场再恢复现场一个函数普通调用时执行权从调用者进入被调用者函数返回后栈帧销毁不能随便回到中间某一行继续执行。协程不一样。它要支持两个基本动作yield当前协程主动让出执行权保存自己的执行现场resume调度器恢复某个协程让它从上次暂停的位置继续跑。课程里列了三种常见实现路线1. setjmp / longjmp 2. ucontext 3. asm 汇编这三种方法的核心都一样保存 CPU 寄存器、栈信息和下一条执行位置恢复时再把这些状态装回去。3. 第一站setjmp / longjmp 认识“跳回保存点”先看最小示例jmp.c#includesetjmp.h#includestdio.hjmp_buf env;voidfunc(intval){printf(In func, about to longjmp: %d\n,val);longjmp(env,val);}intmain(){intretsetjmp(env);if(ret0){func(ret);}elseif(ret1){func(ret);}elseif(ret2){func(ret);}elseif(ret3){func(ret);}}setjmp(env)做了两件事第一次调用时保存当前执行现场然后返回0后续如果有人longjmp(env, value)程序会跳回这个保存点并让setjmp返回value。运行结果类似In func, about to longjmp:0In func, about to longjmp:1In func, about to longjmp:2In func, about to longjmp:3这已经有一点“切换”的味道了程序不是按普通调用栈返回而是跳回了之前保存的点。但setjmp/longjmp还不够像完整协程因为它默认没有给每个协程准备独立栈。真正的协程需要多个执行流各自拥有自己的栈否则很难让多个任务都停在自己的调用链中间。4. 第二站ucontext 做出真正的用户态切换ucontext.c就是一个非常适合入门的协程雏形。它创建了三个上下文ctx[0]、ctx[1]、ctx[2]再用main_ctx充当最小调度器。关键初始化代码如下ucontext_tctx[3];ucontext_tmain_ctx;getcontext(ctx[0]);char*stack1malloc(SIGSTKSZ);ctx[0].uc_stack.ss_spstack1;ctx[0].uc_stack.ss_sizeSIGSTKSZ;ctx[0].uc_linkmain_ctx;makecontext(ctx[0],func1,0);这里有四个关键点getcontext(ctx[0])初始化一个上下文结构uc_stack给这个上下文配置独立栈uc_link main_ctx当func1执行结束时回到哪里makecontext(ctx[0], func1, 0)指定这个上下文第一次被恢复时执行func1。协程函数里主动切回main_ctxvoidfunc1(){while(count30){printf(In func1\n);swapcontext(ctx[0],main_ctx);printf(Back in func1\n);}}调度器再按顺序恢复某个协程while(count30){printf(In main\n);swapcontext(main_ctx,ctx[count%3]);printf(Back in main\n);}这两句swapcontext分别对应swapcontext(ctx[0], main_ctx)当前协程yield保存ctx[0]切回调度器swapcontext(main_ctx, ctx[i])调度器resume某个协程从它上次暂停的位置继续。我在 Linux/WSL 下重新编译运行过输出开头如下In main In func1 Backinmain In main In func2 Backinmain In main In func3 Backinmain In main Backinfunc1 In func1注意Back in func1它不是重新调用func1而是从上一次swapcontext(ctx[0], main_ctx)后面继续执行。这就是协程“暂停后恢复”的直观证据。5. 从 demo 到协程库需要 coroutine 和 scheduler上面的ucontext.c只有三个固定函数还不是完整协程库。课程笔记里给出了更接近工程实现的抽象。协程对象大致需要保存structcoroutine{intfd;ucontext_tctx;// stack, stack_size, funcvoid*arg;queue_node(coroutine,)ready_queue;rbtree_node(coroutine,)wait_rb;rbtree_node(coroutine,)sleep_rb;};调度器则需要管理structscheduler{intepfd;structepoll_eventevents[];queue_node(coroutine,)ready_head;rbtree_root(coroutine,)wait;rbtree_root(coroutine,)sleep;};这两个结构说明了一件事协程库不是只有“切换上下文”这么简单。真正跑起来时调度器至少要维护三类协程ready已经可以运行等待被恢复wait正在等待某个 fd 的 IO 事件sleep等待定时器到期。一个典型调度循环可以理解成这样while(1){// 1. 运行 ready 队列里的协程while(!queue_empty(ready)){copop_ready();resume(co);}// 2. 计算最近的 sleep 超时时间timeoutnearest_timer_timeout();// 3. 等待 IO 事件nepoll_wait(epfd,events,maxevents,timeout);// 4. fd 就绪把等待该 fd 的协程放回 readyfor(i0;in;i){coevents[i].data.ptr;push_ready(co);}// 5. 定时器到期sleep 协程也放回 readyexpire_sleep_coroutines();}这样yield/resume就和epoll_wait接上了协程不是随机切换而是根据“可运行、IO 就绪、定时器到期”来调度。6. 第三站hook把阻塞 IO 变成可调度 IO如果业务代码里写的是while(1){recv(fd,buffer,sizeof(buffer),0);parser(buffer);send(fd,response,response_len,0);}问题来了recv如果真的阻塞整个线程都会卡住调度器也没机会运行其他协程。所以协程库通常会 hook 常见阻塞调用例如read / write / recv / send / accept / connect / sleep目录下的hook.c是一个最小 hook 示例它用dlsym(RTLD_NEXT, ...)找到真正的 libc 函数#define_GNU_SOURCE#includedlfcn.htypedefssize_t(*read_t)(intfd,void*buf,size_tcount);read_tread_fNULL;typedefssize_t(*write_t)(intfd,constvoid*buf,size_tcount);write_twrite_fNULL;ssize_tread(intfd,void*buf,size_tcount){ssize_tretread_f(fd,buf,count);returnret;}ssize_twrite(intfd,constvoid*buf,size_tcount){returnwrite_f(fd,buf,count);}voidinit_hook(){read_fdlsym(RTLD_NEXT,read);write_fdlsym(RTLD_NEXT,write);}这段代码本身还没有调度逻辑但它证明了一个关键机制我们可以拦截业务代码里的read/write在自己的函数里决定什么时候调用真实系统调用。真正接入协程调度后hook read 的逻辑可以画成这样伪代码如下ssize_tread(intfd,void*buf,size_tcount){structpollfdpfd{.fdfd,.eventsPOLLIN,};intreadypoll(pfd,1,0);if(ready0){// fd 暂时不可读把当前协程挂到 wait 结构里epoll_ctl(scheduler-epfd,EPOLL_CTL_ADD,fd,ev);// 当前协程 yield调度器去跑别的协程coroutine_yield();}// 被 resume 回来时fd 已经可读再调用真实 readreturnread_f(fd,buf,count);}这就是“同步写法、异步执行”的核心。业务层仍然写nread(fd,buf,sizeof(buf));但底层实际发生的是fd 可读直接调用真实read_ffd 不可读注册到epoll当前协程yieldepoll_wait发现 fd 就绪调度器把对应协程放回 ready 队列协程resume再次进入 hook最终完成真实读取。7. 异步 DNS 和 epoll 服务端协程要解决的真实场景扩展目录里还有两个很有代表性的文件。async_dns_client_noblock.c展示了“纯异步”的写法创建非阻塞 UDP socket发送 DNS 请求把 fd 注册到 epoll等响应回来后回调处理结果。核心结构很简单structasync_context{intepfd;};structep_arg{intsockfd;async_result_cb cb;};事件线程里等待 DNS 响应intnreadyepoll_wait(epfd,events,ASYNC_CLIENT_NUM,-1);for(i0;inready;i){structep_arg*data(structep_arg*)events[i].data.ptr;intsockfddata-sockfd;intnrecvfrom(sockfd,buffer,sizeof(buffer),0,(structsockaddr*)addr,(socklen_t*)addr_len);structdns_item*domain_listNULL;intcountdns_parse_response(buffer,domain_list);data-cb(domain_list,count);}这种写法性能很好但业务逻辑会被拆成“提交请求”和“回调处理结果”。协程库想做的就是把这种事件驱动能力藏到 hook 和 scheduler 下面让上层代码重新变回顺序逻辑。server_mulport_epoll.c则展示了高并发网络服务常见结构多端口监听epoll_wait接收大量连接事件客户端 fd 设置非阻塞事件到来后可以直接处理也可以投递到线程池。课程截图里可以看到大量客户端回显输出这类场景正是协程库的用武之地每个连接可以对应一个协程业务代码像处理单连接一样顺序执行调度器在背后负责把不可读、不可写、睡眠中的协程挂起。8. 把整条链路串起来到这里可以把协程实现拆成四层第 1 层上下文切换 setjmp/longjmp、ucontext、汇编保存和恢复 CPU/栈状态。 第 2 层协程对象 每个 coroutine 保存 ctx、stack、入口函数、参数、状态。 第 3 层调度器 维护 ready / wait / sleep使用 epoll_wait 驱动 IO 协程恢复。 第 4 层系统调用 hook 拦截 read/write/recv/send/sleep把阻塞点改造成 yield 点。执行流程可以概括为create coroutine - makecontext / 初始化栈 - 放入 ready 队列 - scheduler resume - 业务代码执行 - 遇到 IO 不就绪 - hook 注册 epoll 并 yield - scheduler 跑其他协程 - epoll_wait 返回事件 - 对应协程回到 ready - resume 后继续执行这也是 ntyco、libco、go runtime 等协程/轻量线程系统的共同味道把“等待”从线程阻塞变成任务挂起把“恢复”交给调度器。9. 编译运行本文示例这些示例依赖 Linux API建议在 Linux 或 WSL 下运行。gcc-Wall-Wextra-O0-g-ojmp jmp.c ./jmp gcc-Wall-Wextra-O0-g-oucontext ucontext.c ./ucontext gcc-Wall-Wextra-O0-g-ohook hook.c-ldl./hook我本地验证到的hook输出为buffer:1234567890这说明顶层hook.c已经成功通过自定义read/write包装函数调用到了真实 libc 系统调用。10. 小结协程可以用一句话理解协程是在用户态保存和恢复执行现场并把 IO 等待交给调度器管理的一种并发执行单元。本文从目录里的代码出发走了一条从浅到深的路线jmp.c理解“跳回保存点”ucontext.c理解“独立栈 yield/resume”hook.c理解“拦截系统调用”server_mulport_epoll.c/async_dns_client_noblock.c理解协程为什么适合高并发 IO。如果继续往下实现一个完整协程库下一步就可以补齐coroutine_create()分配协程对象和栈coroutine_yield()/coroutine_resume()封装上下文切换scheduler_run()实现 ready/wait/sleep 三类任务调度read/recv/send/sleephook把阻塞点接入 epoll多线程多核模式每个线程一个 scheduler连接按线程分片。做到这里一个“看起来同步、跑起来异步”的 C 协程网络库就有了骨架。学习链接: https://github.com/0voice

相关新闻

最新学量化交易,先分清策略表达和代码执行
2026/6/27 2:00:08

最新学量化交易,先分清策略表达和代码执行

从零学习量化交易时,很多内容会同时出现:概念、策略、代码、执行流程。它们彼此有关,但并不是同一个问题。先把基本概念和入门门槛看清楚,再处理这些差异,会让学习更稳。代码要回到规则本身如果读者还不知道量化交易中…

阅读更多
YiFeiWebApi — 易飞ERP集成平台 综合评估报告
2026/6/27 2:00:08

YiFeiWebApi — 易飞ERP集成平台 综合评估报告

YiFeiWebApi — 易飞ERP集成平台 综合评估报告一、产品概述 YiFeiWebApi 是一款专为易飞ERP打造的高性能 Web API 集成平台,旨在帮助中小企业以极低成本实现 ERP 与 MES、WMS、PLM、OA、CRM 等外部系统的无缝对接。平台覆盖易飞 ERP 97 个核心业务单据的读写操作&am…

阅读更多
告别 Origin 反复调试!Paperxie AI 科研绘图,自带样例一键产出论文合规插图
2026/6/27 1:00:08

告别 Origin 反复调试!Paperxie AI 科研绘图,自带样例一键产出论文合规插图

paperxie-免费查重复率aigc检测/开题报告/毕业论文/智能排版/文献综述/科研绘图科研绘图 - PaperXie智能写作PaperXie免费论文查重检测-首款免费论文检测软件,为毕业生提供专业的论文重复率检测、论文降重、Aigc检测、智能排版 、论文写作等一站式服务。https://www.paperxie.c…

阅读更多
福州橱柜定制怎么选?从豪宅案例看高定木作的真实差距
2026/6/27 4:00:08

福州橱柜定制怎么选?从豪宅案例看高定木作的真实差距

厨房是家里使用频率最高的空间,橱柜定制也因此成为全屋定制里最考验功力的项目。一套好的橱柜,不仅要颜值在线,更要收纳合理、五金耐用、防潮性好,能用十几年不出问题。福州作为湿度偏高的南方城市,对橱柜的工艺和安装…

阅读更多
第41期 | 项目1:AI知识库产品
2026/6/27 4:00:08

第41期 | 项目1:AI知识库产品

第41期 | 项目1:AI知识库产品 🎯 今天你将学会 从产品视角设计一个 AI 知识库产品(不只是技术实现)产品级开发的项目规划方法(需求→设计→实现→测试→部署)实现完整的 AI 知识库:文档管理 …

阅读更多
从 AI Router 到智能体经济网络:UniKey 如何走出下一代 AI 基础设施路线?
2026/6/27 4:00:08

从 AI Router 到智能体经济网络:UniKey 如何走出下一代 AI 基础设施路线?

AI 能力入口正在成为新一代基础设施赛道随着全球 AI 模型、工具和智能体应用快速增长,AI 行业正在出现一个明显趋势:用户真正需要的不再只是某一个强模型,而是一个能够统一调用、智能路由、管理用量、连接开发者和支持支付结算的 AI 能力入口…

阅读更多
第40期 | 模块四复盘:AI应用开发模式总结
2026/6/27 4:00:08

第40期 | 模块四复盘:AI应用开发模式总结

第40期 | 模块四复盘:AI应用开发模式总结 🎯 今天你将学会 整理 AI 应用的常见架构模式,形成你的「开发模式库」掌握 AI 应用特有的性能优化策略理解 AI 应用安全注意事项——比传统 Web 应用更多回顾模块四全部知识,完成能力自…

阅读更多
2026年ai网站建设公司有哪些?挑选ai网站建设公司要看这几点!
2026/6/27 4:00:08

2026年ai网站建设公司有哪些?挑选ai网站建设公司要看这几点!

2026年ai网站建设公司有哪些?挑选ai网站建设公司要看这几点! IDC中国2026年一季度调查数据显示,国内AI建站市场规模将突破260亿元,年增速保持在35%以上,小微企业占到全部建站用户的近七成,体现了AI零代码建…

阅读更多
亿级流量洪峰下的防线:限流降级与多级缓存协同架构实战
2026/6/27 3:00:08

亿级流量洪峰下的防线:限流降级与多级缓存协同架构实战

亿级流量洪峰下的防线:限流降级与多级缓存协同架构实战一、流量洪峰压垮系统的三重困境:从超时到雪崩 在电商大促、秒杀抢购等场景中,流量往往在短时间内飙升数十倍。裸奔的系统面对这种冲击,会依次陷入三重困境。第一重是接口超时…

阅读更多
嵌入式语音编解码实战:G.726 ADPCM库集成与优化指南
2026/6/25 12:25:54

嵌入式语音编解码实战:G.726 ADPCM库集成与优化指南

1. 项目概述与G.726 ADPCM技术背景在嵌入式语音处理领域,带宽和存储资源往往是寸土寸金的。如果你做过对讲机、VoIP网关或者早期的数字录音设备,一定对如何在有限的比特率下保住语音可懂度这件事深有感触。我当年接手一个车载调度系统的项目,…

阅读更多
ITU656格式化器寄存器配置实战:VBI数据处理与VCR特技播放兼容性
2026/6/25 22:07:52

ITU656格式化器寄存器配置实战:VBI数据处理与VCR特技播放兼容性

1. 项目概述与核心挑战在数字视频处理领域,将原始的视频数据、同步时序以及各种辅助信息打包成一个标准、稳定的串行数据流,是确保设备间互联互通的基础。ITU-R BT.656标准(常简称为ITU656)正是为此而生的一套“交通规则”。它定义…

阅读更多
嵌入式GUI开发实战:emWin环境搭建、配置优化与性能调优指南
2026/6/25 20:03:50

嵌入式GUI开发实战:emWin环境搭建、配置优化与性能调优指南

1. 项目概述与emWin核心价值解析在嵌入式系统开发领域,人机交互(HMI)的设计正从简单的LED指示灯和按键,快速向全彩图形化界面演进。无论是智能家电上的触摸屏、工业PLC的操作面板,还是医疗设备的参数显示,一…

阅读更多
139、飞控中的气压计选型:MS5611、BMP280
2026/6/27 0:00:07

139、飞控中的气压计选型:MS5611、BMP280

飞控中的气压计选型:MS5611、BMP280 从一次炸机说起 去年夏天调试一架四轴,气压计定高模式,悬停时高度波动从0.3米慢慢变成1.5米,最后直接飘到3米开外,切回自稳才救回来。落地一看日志,气压值在起飞后20分钟开始出现周期性跳变,每5秒跳一次,幅度相当于2米高度变化。当…

阅读更多
专业级Iwara视频下载工具深度解析:3大核心特性与架构设计实战指南
2026/6/27 0:00:07

专业级Iwara视频下载工具深度解析:3大核心特性与架构设计实战指南

专业级Iwara视频下载工具深度解析:3大核心特性与架构设计实战指南 【免费下载链接】IwaraDownloadTool Iwara 下载工具 | Iwara Downloader 项目地址: https://gitcode.com/gh_mirrors/iw/IwaraDownloadTool IwaraDownloadTool是一款专为Iwara视频平台设计的…

阅读更多
Iwara视频下载工具:轻松批量下载Iwara平台视频的完整指南
2026/6/27 0:00:07

Iwara视频下载工具:轻松批量下载Iwara平台视频的完整指南

Iwara视频下载工具:轻松批量下载Iwara平台视频的完整指南 【免费下载链接】IwaraDownloadTool Iwara 下载工具 | Iwara Downloader 项目地址: https://gitcode.com/gh_mirrors/iw/IwaraDownloadTool Iwara视频下载工具是一款专为Iwara平台设计的智能下载解决…

阅读更多
GIT修改用户名
2026/6/26 3:53:45

GIT修改用户名

在GIT中修改用户名可按以下步骤操作: 查看当前git的用户名,使用命令git config --list或git config user.name。修改git用户名,使用命令git config --global user.name "xxx(新的用户名)",将其中…

阅读更多
Win11Debloat:让你的Windows系统重获新生的终极优化工具
2026/6/26 13:36:46

Win11Debloat:让你的Windows系统重获新生的终极优化工具

Win11Debloat:让你的Windows系统重获新生的终极优化工具 【免费下载链接】Win11Debloat A simple, lightweight PowerShell script that allows you to remove pre-installed apps, disable telemetry, as well as perform various other changes to declutter and …

阅读更多
技术深度解析:m4s-converter实现原理与B站缓存视频转换最佳实践
2026/6/26 13:36:41

技术深度解析:m4s-converter实现原理与B站缓存视频转换最佳实践

技术深度解析:m4s-converter实现原理与B站缓存视频转换最佳实践 【免费下载链接】m4s-converter 一个跨平台小工具,将bilibili缓存的m4s格式音视频文件合并成mp4 项目地址: https://gitcode.com/gh_mirrors/m4/m4s-converter m4s-converter是一个…

阅读更多