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. iOS开发-通过正则表达式进行各种判断银行卡,车牌号,邮箱地址,QQ,身份证,全字母,仅输入字母或数字同时包含大小写字母和数字,仅能输入中文等

    /* *  验证银行卡号是否正确 *  车牌号验证 *  检验邮箱地址是否正确 *  手机号中间四位密文显示 *  判断QQ号是否正确(5-11位) *  判断身份证号是否正确(如末位为字母请用“x” ...

  2. Floodlight下发流表过程分析

    https://blog.csdn.net/vonzhoufz/article/details/32166445 当一个packet到达openflow交换机,会进行流表的匹配,如果没有找到相应的流表 ...

  3. 硬盘空间术语:unallocated, unused and reserved

    通过standard reports查看Disk Usage,选中Database,右击,选择Reports->Standard Reports->Disk Space Usage,截图如 ...

  4. zabbix3.4 监控路由器报错No Such Instance currently exists at this OID

    zabbix 3.4 监控报错No Such Instance currently exists at this OID 1.首先查看监控的路由器的监控项是否报警 监控主机报错出现这个 No Such ...

  5. 解决了一个困扰我近一年的vim显示中文乱码的问题

    今天解决了vi命令打开日志文件中文总是显示乱码的问题.由于项目组中的日志包含一些特殊字符,所以使用vim打开日志文件时总是不能正确识别出文件字符编码.此时用:set fileencoding命令可以看 ...

  6. 记一次nginx -t非常慢的排障经历

    在一次修改nginx配置时候,执行 case: #/usr/local/nginx/sbin/nginx -t 出现执行命令出现很久没返回结果,也没返回成功或是失败,就是一直卡住的状态,严重影响ngi ...

  7. 常用函数-filter、map、reduce、sorted

    常用函数 filter map reduce sorted和列表自带sort 待续... 一.filter函数 1.说明 filter()函数接收一个函数 f 和一个可迭代对象,这个函数 f 的作用是 ...

  8. oracle删除死锁进程

    在命令行下运行: select SID,SERIAL# from v$session t1, v$locked_object t2 where t1.sid = t2.SESSION_ID; alte ...

  9. +new Date()的用法

    var s=+newDate();   var s=+newDate(); 解释如下:=+是不存在的; +new Date()是一个东西; +相当于.valueOf(); 看到回复补充一下.getTi ...

  10. Daily Scrum (2015/10/30)

    据组员们反映其他组都会有休息时间,所以我和PM讨论把每周5晚上作为日常休息时间,这一天组员们自由阅读.