ucontext簇函数学习

https://github.com/zfengzhen/Blog/blob/master/article/ucontext%E7%B0%87%E5%87%BD%E6%95%B0%E5%AD%A6%E4%B9%A0.md

作者: fergus (zfengzhen@gmail.com)

系统手册学习:

名字

getcontext, setcontext —— 获取或者设置用户上下文

概要

#include <ucontext.h>

int getcontext(ucontext_t *ucp);
int setcontext(const ucontext_t *ucp);

描述

在类System-V环境中,定义在<ucontext.h>头文件中的mcontext_t和ucontext_t的两种数据类型,以及getcontext(),setcontext(),makecontext()和swapcontext()四个函数允许在一个进程不同的协程中用户级别的上下文切换。
mcontext_t数据结构是依赖机器和不透明的。ucontext_t数据结构至少包含下面的字段:

typedef struct ucontext {
struct ucontext *uc_link;
sigset_t uc_sigmask;
stack_t uc_stack;
mcontext_t uc_mcontext;
...
} ucontext_t;

sigset_t和stack_t定义在<signal.h>头文件中。uc_link指向当前的上下文结束时要恢复到的上下文(只在当前上下文是由makecontext创建时,个人理解:只有makecontext创建新函数上下文时需要修改),uc_sigmask表示这个上下文要阻塞的信号集合(参见sigprocmask),uc_stack是这个上下文使用的栈(个人理解:非makecontext创建的上下文不要修改),uc_mcontext是机器特定的保存上下文的表示,包括调用协程的机器寄存器。
getcontext()函数初始化ucp所指向的结构体,填充当前有效的上下文。
setcontext()函数恢复用户上下文为ucp所指向的上下文。成功调用不会返回。ucp所指向的上下文应该是getcontext()或者makeontext()产生的。
如果上下文是getcontext()产生的,切换到该上下文,程序的执行在getcontext()后继续执行。
如果上下文被makecontext()产生的,切换到该上下文,程序的执行切换到makecontext()调用所指定的第二个参数的函数上。当该函数返回时,我们继续传入makecontext()中的第一个参数的上下文中uc_link所指向的上下文。如果是NULL,程序结束。

返回值

成功时,getcontext()返回0,setcontext()不返回。错误时,都返回-1并且赋值合适的errno。

注意

这个机制最早的化身是setjmp/longjmp机制。但是它们没有定义处理信号的上下文,下一步就出了sigsetjmp/siglongjmp。当前这套机制给予了更多的控制权。但是另一方面,没有简单的方法去探明getcontext()的返回是第一次调用还是通过setcontext()调用。用户不得不发明一套他自己的书签的数据,并且当寄存器恢复时,register声明的变量不会恢复(寄存器变量)。
当信号发生时,当前的用户上下文被保存,一个新的内核为信号处理器产生的上下文被创建。不要在信号处理器中使用longjmp:它是未定义的行为。使用siglongjmp()或者setcontext()替代。

名字

makecontext,swapcontext —— 操控用户上下文

概要

#include <ucontext.h>

void makecontext(ucontext_t *ucp, void (*func)(void), int argc, ...);
int swapcontext(ucontext_t *restrict oucp, const ucontext_t *restrict ucp);

描述

makecontext()函数修改ucp所指向的上下文,ucp是被getcontext()所初始化的上下文。当这个上下文采用swapcontext()或者setcontext()被恢复,程序的执行会切换到func的调用,通过makecontext()调用的argc传递func的参数。
在makecontext()产生一个调用前,应用程序必须确保上下文的栈分配已经被修改。应用程序应该确保argc的值跟传入func的一样(参数都是int值4字节);否则会发生未定义行为。
当makecontext()修改过的上下文返回时,uc_link用来决定上下文是否要被恢复。应用程序需要在调用makecontext()前初始化uc_link。
swapcontext()函数保存当前的上下文到oucp所指向的数据结构,并且设置到ucp所指向的上下文。

保存了旧值oucp,跳转到ucp所指的地方

返回值

成功完成,swapcontext()返回0。否则返回-1,并赋值合适的errno。

错误

swapcontext()函数可能会因为下面的原因失败:
ENOMEM ucp参数没有足够的栈空间去完成操作。

例子

#include <stdio.h>
#include <ucontext.h> static ucontext_t ctx[3]; static void
f1 (void)
{
puts("start f1");
swapcontext(&ctx[1], &ctx[2]);
puts("finish f1");
} static void
f2 (void)
{
puts("start f2");
swapcontext(&ctx[2], &ctx[1]);
puts("finish f2");
} int
main (void)
{
char st1[8192];
char st2[8192]; getcontext(&ctx[1]);
ctx[1].uc_stack.ss_sp = st1;
ctx[1].uc_stack.ss_size = sizeof st1;
ctx[1].uc_link = &ctx[0];
makecontext(&ctx[1], f1, 0); getcontext(&ctx[2]);
ctx[2].uc_stack.ss_sp = st2;
ctx[2].uc_stack.ss_size = sizeof st2;
ctx[2].uc_link = &ctx[1];
makecontext(&ctx[2], f2, 0); swapcontext(&ctx[0], &ctx[2]);
return 0;
}

代码试用总结:

  • 1 makecontext之前必须调用getcontext初始化context,否则会段错误core
  • 2 makecontext之前必须给uc_stack分配栈空间,否则也会段错误core
  • 3 makecontext之前如果需要上下文恢复到调用前,则必须设置uc_link以及通过swapcontext进行切换
  • 4 getcontext产生的context为当前整个程序的context,而makecontext切换到的context为新函数独立的context,但setcontext切换到getcontext的context时,getcontext所在的函数退出时,并不需要uc_link的管理,依赖于该函数是在哪被调用的,整个栈会向调用者层层剥离
  • 5 不产生新函数的上下文切换指需要用到getcontext和setcontext
  • 6 产生新函数的上下文切换需要用到getcontext,makecontext和swapcontext

ucontext性能小试:

运行环境为我的mac下通过虚拟机开启的centos64位系统,不代表一般情况,正常在linux实体机上应该会好很多吧

  • 1 单纯的getcontext:
    function[ getcontext(&ctx) ] count[ 10000000 ]
    cost[ 1394.88 ms] avg_cost[ 0.14 us]
    total CPU time[ 1380.00 ms] avg[ 0.14 us]
    user CPU time[ 560.00 ms] avg[ 0.06 us]
    system CPU time[ 820.00 ms] avg[ 0.08 us]

  • 2 新函数的协程调用
    通过getcontext和对uc_link以及uc_stack赋值,未了不增加其他额外开销,uc_stack为静态字符串数组分配,运行时不申请,makecontext中的函数foo为空函数,调用swapcontext切换协程调用测试
    function[ getcontext_makecontext_swapcontext() ] count[ 1000000 ]
    cost[ 544.55 ms] avg_cost[ 0.54 us]
    total CPU time[ 550.00 ms] avg[ 0.55 us]
    user CPU time[ 280.00 ms] avg[ 0.28 us]
    system CPU time[ 270.00 ms] avg[ 0.27 us]

每秒百万级别的调用性能。

ucontext协程的实际使用:

将getcontext,makecontext,swapcontext封装成一个类似于lua的协同式协程,需要代码中主动yield释放出CPU。
协程的栈采用malloc进行堆分配,分配后的空间在64位系统中和栈的使用一致,地址递减使用,uc_stack.uc_size设置的大小好像并没有多少实际作用,使用中一旦超过已分配的堆大小,会继续向地址小的方向的堆去使用,这个时候就会造成堆内存的越界使用,更改之前在堆上分配的数据,造成各种不可预测的行为,coredump后也找不到实际原因。
对使用协程函数的栈大小的预估,协程函数中调用其他所有的api的中的局部变量的开销都会分配到申请给协程使用的内存上,会有一些不可预知的变量,比如调用第三方API,第三方API中有非常大的变量,实际使用过程中开始时可以采用mmap分配内存,对分配的内存设置GUARD_PAGE进行mprotect保护,对于内存溢出,准确判断位置,适当调整需要分配的栈大小。

getcontext makecontext setcontext swapcontext介绍的更多相关文章

  1. ucontext-人人都可以实现的简单协程库

    ucontext的介绍 http://blog.csdn.net/qq910894904/article/details/41911175 协程的介绍 https://en.wikipedia.org ...

  2. ucontext的简单介绍

    简介 结构体 函数 getcontext setcontext makecontext swapcontext 简介 ucontext.h是GNU C库的一个头文件,主要用于用户态下的上下文切换.需要 ...

  3. app启动速度怎么提升?

    简介: APP 启动速度的重要性不言而喻.高德地图是一个有着上亿用户的超级 APP,本文从唤端技术.H5 启动页.下载速度.APP加载.线程调度和任务编排等方面,详解相关技术原理和实现方案,分享高德在 ...

  4. goroutine

    Go语言从诞生到普及已经三年了,先行者大都是Web开发的背景,也有了一些普及型的书籍,可系统开发背景的人在学习这些书籍的时候,总有语焉不详的感觉,网上也有若干流传甚广的文章,可其中或多或少总有些与事实 ...

  5. [转载] goroutine背后的系统知识

    原文: http://www.sizeofvoid.net/goroutine-under-the-hood/ 文章写的非常好, 对内部原理解释的非常清楚, 是我喜欢的风格, 感谢作者的精彩文章. = ...

  6. goroutine背后的系统知识

    http://www.sizeofvoid.net/goroutine-under-the-hood/ o语言从诞生到普及已经三年了,先行者大都是Web开发的背景,也有了一些普及型的书籍,可系统开发背 ...

  7. 实现一个简单的C++协程库

    之前看协程相关的东西时,曾一念而过想着怎么自己来实现一个给 C++ 用,但在保存现场恢复现场之类的细节上被自己的想法吓住,也没有深入去研究,后面一丢开就忘了.近来微博上看人在讨论怎么实现一个 user ...

  8. cloudwu/coroutine 源码分析

    1 与其它协程库使用对比 这个 C 协程库是云风(cloudwu) 写的,其接口风格与 Lua 协程类似,并且都是非对称 stackful 协程.这个是源代码中的示例: #include " ...

  9. 浅谈:深入理解struts2的流程已经spring和struts2的整合

    第一步:在tomcat启动的时候 1.在tomcat启动的时候,首先会加载struts2的核心过滤器StrutsPrepareAndExecuteFilter <filter> <f ...

随机推荐

  1. P3703 [SDOI2017]树点涂色

    P3703 [SDOI2017]树点涂色 链接 分析: 首先对于询问,感觉是线段树维护dfs序,每个点记录到根的颜色个数.第二问差分,第三问区间取max. 那么考虑修改,每次将一个点的颜色变成和父节点 ...

  2. [BZOJ4476][JSOI2015]送礼物[分数规划+单调队列]

    题意 题目链接 分析 分数规划之后可以得到式子:\(max-min-r*mid+l*mid\geq k*mid\) . 贪心选择,肯定区间的端点是极小或者极大值.特殊处理区间长度 \(\leq L\) ...

  3. c# 缓存详解

    如果说要对一个站点或者应用程序经常优化,可以说缓存的使用是最快也是效果最明显的方式.一般而言,我们会把一些常用的,或者需要花费大量的资源或时间而产生的数据缓存起来,使得后续的使用更加快速. 如果真要细 ...

  4. HALCON视觉算子相关函数中文说明System(2)

    16.6  Parameters get_system_ 功能:根据HALCON系统参数获取关于当前的信息. set_system 功能:HALCON系统参数的设置. 16.7  Serial cle ...

  5. Jmeter接口测试(六)关联&正则匹配

    问题思考 接口测试过程中经常需要接口之间关联调用,比如获取上一个接口的返回值,作为另一个接口的请求参数,那么该如何从处理呢? 这里需要使用 Jmeter 的正则表达式提取器,通过对响应的数据来提取指定 ...

  6. java查询几个菜单下的所有下级菜单

    需求: 假如有几个一级菜单,一级菜单下面有几个二级菜单,二级菜单下又还有三级菜单.现在要求一级菜单里面的几个设置为无效,将不显示在前端.现在需要的是查询出一级菜单下面所有的菜单,包括二级,三级菜单 原 ...

  7. 013-- mysql常用的查询优化方法

    浅谈MySQL中优化sql语句查询常用的30种方法   1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引. 2.应尽量避免在 where 子句 ...

  8. linux命令系列 ls

    ls是linux中最常用的命令之一 ls 的功能是list directory contents,其常用的选项如下: (1) -l   use a long listing format(长格式,显示 ...

  9. Xcode中的Target

    Xcode中的Target,主要包含下面几点知识: Target依赖 Build Phase Build Rule Target依赖 Target的依赖关系表示一个Target要构建成功,必先依赖于其 ...

  10. 20172332 2017-2018-2 《程序设计与数据结构》Java哈夫曼编码实验--哈夫曼树的建立,编码与解码

    20172332 2017-2018-2 <程序设计与数据结构>Java哈夫曼编码实验--哈夫曼树的建立,编码与解码 哈夫曼树 1.路径和路径长度 在一棵树中,从一个结点往下可以达到的孩子 ...