https://github.com/DoasIsay/ToyCoroutine

如何检测协程是否需要进行栈扩充?

我们先思考一个问题,glibc的pthread_create创建的线程是如何检测到用户栈的溢出而及时终止线程的?

如下代码

g++ test.cpp -lpthread

strace ./a.out 结果如下图

由strace 结果可知pthread_create先使用mmap为线程申请了的用户空间stack,然后使用mprotect对stack的栈顶进行保护,即栈顶的4kb无读写权限,一旦被读写就说明产生了栈溢出,操作系统就会向读写此4kb的任务发送SIGSEGV信号,最后才是调用clone创建线程

如果越过这4kb访问前面的内存会怎样?如果刚好是另一个线程的stack?只要不访问这4kb或其它线程的这4kb都不会有问题,顶多就是会破坏其它线程的栈,或自已的栈被其它线程破坏,然后进程core掉,,,

因此这篇博客《一个C/C++协程库的思考与实现之栈溢出检测》

https://blog.csdn.net/DoasIsay/article/details/107396105

就需要更新一下了,因为我们找到了一种更好的方法mprotect来主动检测栈溢出

对于操作系统的任务(进程或线程)而言,任务所需的栈内存,堆内存,并不是任务启动后或发起内存申请(brk/mmap/malloc/new)后操作系统立即为其分配物理内存,而是先为其在进程的虚拟地址空间中找到一块空闲的空间标记其大小起止地址及访问权限,当CPU真正访问到任务未分配物理内存的虚拟页内的地址时MMU会产生一个内存缺页中断,此时在缺页中断处理中操作系统才会真正的为任务分配一页物理内存并更新进程的页表

对于在用户空间实现的协程而言并不能使用操作系统及CPU提供的这种按需延迟分配的机制,但操作系统向用户提供了信号处理这种软中断及mprotect这种接口,但是它还是不能支撑我们在用户空间模拟实现这种机制,如在内存越界访问时,发出信号,由于协程栈是在堆上分配的,当栈溢出时就会发生堆内存越界访问,此时如果对协程的栈顶即堆的起始一段内存进行mprotect,当栈溢出时就会触发SIGSEGV信号,在信号处理函数中我们可以为当前触发SIGSEGV信号的协程扩充栈空间

想法甚好,但是,,,

问题1

如何获得触发SIGSEGV信号的协程?

在多线程环境中当向一个进程发送信号后,信号被投递到那个线程完成完全是随机的,除了硬件错误与定时器触发的信号,SIGSEGV是一个硬件错误?如果它不是一个硬件错误,有可能当前进行信号处理的线程就不是触发SIGSEGV信号的协程所在的线程,我们获取当前线程正在调度运行的协程是通过__thread线程局部变量current,此变量是一个指向当前线程正在执行的程协对象的指针,因此处理SIGSEGV信号的线程一定要是触发SIGSEGV信号的协程所在的线程,才能获取到触发SIGSEGV信号的协程

问题2

如何区分是因协程栈溢出导致触发SIGSEGV信号,还是野指针导致的?

如果我们能在信号处理函数中得到导致触发SIGSEGV信号的内存地址,再与当前触发SIGSEGV信号的协程的栈地址进行比较,如果相差不远,那就不会是野指针导致的,但是我们无法在信号处理函数中获取到触发SIGSEGV信号的内存地址

经过测试,触发SIGSEGV信号的线程会收到SIGSEGV信号,但是在信号处理函数中无法完成问题2的操作,而且就算问题2可以解决,我们在SIGSEGV的信号处理函数中为协程扩充了栈空间后,此线程也只会被内核不断的发送SIGSEGV信号,因为信号处理函数会返回到触发SIGSEGV信号的那条指令继续执行,而我们无法修改这条指令所使用的地址,也就是在信号处理函数中对a变量的修改无法被fun函数再次获取到

比如下代码

因此就真的不能在用户空间为协程实现栈的动态按需增长

一种比较朴素的实现方法,在协程的函数调用的入口加入检测代码就像栈溢出检测那样丑陋的代码,检测cpu当前sp寄存器的值与栈的未尾做对比,比如还剩80%的空间就进行栈的扩充,但是如下代码会令你的检测失效,比如栈空间是2kb,假设进入fun函数前已经使用了1kb,进入fun函数后进行检测发现只使用了50%的栈空间,但检测后立即在栈上申请了1kb的空间,此时代码继续运行就有可能产生栈溢出

void fun(){

check_stack();

char a[1024];

xxxxxxx;

xxxxxxx;

}

因此应尽量提高栈扩充的检测条件,比如栈空间使用超过50%后就扩充,另外尽量不在栈中创建大的临时变量

如何进行栈的扩充?

使用malloc分配新的栈空间拷贝老栈的内容到新栈,这会有个问题就是栈中的局部变量的地址还是老栈的,因此我们需要修改每一个局部变量的地址?不,不需要

如下代码

因为在函数调用的栈帧中是通过bp基栈指针+相对地址去访问栈上的变量的,我们仅需修改协程的函数调用链中每个栈帧上保存的bp,new_bp=new_stack_start+(old_bp-old_stack_start)通过老的值计算出相对偏移量然后与新栈起始地址相加计算出新值回填到栈帧上,此处就涉及到栈的回溯,其实很简单,取出当前栈帧上保存的上一个栈帧的bp,以此类推直至协程的入口函数调用的栈帧,然后再修改协程context的bp/sp就可以了

步骤如下:

  1. 检测到需进行栈扩充
  2. 保存协程当前上下文到协程context
  3. 切换到调度器栈
  4. 分配内存进行栈拷贝
  5. 进行栈回溯计算修改每个栈帧上保存的bp
  6. 当遇到栈帧的bp与协程栈的起始地址相等时就停止回溯,这时已经到达了协程的入口函数
  7. 计算修改context的bp/sp,new_sp=(old_sp-old_stack_start)+new_start_stack
  8. 恢复协程的context,此时协程会继续在新扩充的栈上进行函数的调用

在每个函数调用中加入检测代码是丑陋且繁琐的,而且我们也无法在所有的函数调用中加入检测代码,因为还有第三方库的函数,因此在协程库中用这种方式实现栈的动态扩充并不是很优雅,除非是编译器支持,如gcc提供的分段栈,就算这样我们也不能保证我们使用的所有依赖库在编译时都打开了分段栈的选项,对于协程栈的动态扩充还是别想了吧

既然操作系统提供了虚拟内存,任务申请的内存只是虚拟内存,申请了不用就不会占用物理内存,那么我们直接给足协程的栈,不就行了?只不过会导致进程占用的虚拟内存变大而已,还搞什么协程栈的动态按需增长,,,

一个C/C++协程库的思考与实现之协程栈的动态按需增长相关推荐

  1. linux c++11高性能协程库netco

    目录 一.开源协程库调研 1.golang语言自带协程 2.云风的coroutine协程库 3.腾讯的libco协程库 4.魅族的libgo协程库 二.netco协程库概述 三.netco的实现 1. ...

  2. 【并发编程二十】协程(coroutine)_协程库

    [并发编程二十]协程(coroutine) 一.线程的缺点 二.协程 三.优点 四.个人理解 五.协程库 1.window系统 2.unix系统(包括linux的各个版本) 2.1.makeconte ...

  3. 一个“蝇量级” C 语言协程库

    协程(coroutine)顾名思义就是"协作的例程"(co-operative routines).跟具有操作系统概念的线程不一样,协程是在用户空间利用程序语言的语法语义就能实现逻 ...

  4. Libco是一个C/C++协程库,在微信服务中广泛使用

    Table of Contents 协程简介 协程Libco库 libco的特性 PS:CGI框架 PS:Lambda表达式 协程简介 协程这个概念其实在<操作系统>系统里面应该有了解过, ...

  5. 最轻量级的C协程库:Protothreads

    原文地址:https://www.linuxidc.com/Linux/2012-07/66395p2.htm 协程的好处不用再多说,作为与函数调用/返回相对的概念,它使我们思考问题的方式经历一场变革 ...

  6. C++ 开源协程库 libco——原理及应用

    1 导论 使用 C++ 来编写高性能的网络服务器程序,从来都不是件很容易的事情.在没有应用任何网络框架,从 epoll/kqueue 直接码起的时候尤其如此.即便使用 libevent, libev这 ...

  7. C/C++协程库libco:微信怎样漂亮地完成异步化改造

    如今,微信拥有月活跃用户8亿. 不可否认,当今的微信后台拥有着强大的并发能力. 不过, 正如罗马并非一日建成:微信的技术也曾经略显稚嫩. 微信诞生于2011年1月,当年用户规模为0.1亿左右:2013 ...

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

    1.干货写在前面 协程是一种用户态的轻量级线程.本篇主要研究协程的C/C++的实现. 首先我们可以看看有哪些语言已经具备协程语义: 比较重量级的有C#.erlang.golang* 轻量级有pytho ...

  9. C++协程库coroutine使用指南

    https://github.com/tonbit/coroutine是一个精巧的C++非对称协程库.库只有一个.h文件(<500行的代码),使用时也仅需要include这个头文件.但是在功能上 ...

  10. cmake导入so库_libgo - 协程库、并行编程库

    libgo是一个使用C++11编写的协作式调度的stackful协程库, 同时也是一个强大的并行编程库, 是专为Linux服务端程序开发设计的底层框架. 目前支持三个平台: Linux (GCC4.8 ...

最新文章

  1. 前端基础入门(html+css+详)
  2. 三台主机分别部署LAMP
  3. python爬虫获取的网页数据为什么要加[0-使用 Python 爬取网页数据
  4. Apache+Tomcat中支持“UTF-8”编码的中文地址
  5. Android之jni编译报错comparsion between signed and unsigned integer expressions解决办法
  6. 外部中断实验 编写程序学习外部中断的电平触发方式。无中断时发光让发光二极管从左到右依次点亮,有外部中断请求时,4位数码管从0000开始加1显示(加到9999后复位为0000),同时蜂鸣器报警。
  7. 理想汽车下调第三季度交付展望 预计交付量约为2.45万辆
  8. 微信8.0来了!黄脸表情会动了,还能扔炸弹...一大波新功能上线!
  9. 大话云时代rac_网易Q1财报中的增长信号:有道和云音乐如何打通“任督二脉”?...
  10. 查看Linux连接数
  11. Oracle数据库:下载与安装图解
  12. python 文件夹操作_Python之路(第九篇)Python文件操作
  13. XCTF|PWN-string-WP
  14. 运行 Android 的笔记本 Cosmo 已众筹超 130 万美元
  15. springboot 2.X——短信网关使用初体验
  16. 关于@Vaild注解的使用
  17. 用户行为分析zhi应用分析模型
  18. 厦大2021级期末上机考试
  19. Latex和word相互转换。word表格制作
  20. 五分钟上手Premiere——小白的视频剪辑笔记

热门文章

  1. cocos2d-x 3.2 之 三消类游戏——万圣大作战
  2. 翠竹林 Opencv+C++之人脸识别
  3. 如何检索论文被引用情况
  4. 双色球历史数据下载最新2003年2021年
  5. Android 时间API
  6. 在eclipse部署OpenBravo项目
  7. QScrollArea 动态添加控件问题
  8. H5 页面36种漂亮的CSS3网页按钮Button样式
  9. 墨尔本大学计算机科学博士怎么样,墨尔本大学计算机科学专业怎么样?成为IT大牛的不二之选...
  10. 【分治算法-02】算法经典问题