12

我正在对 C++ 绿色线程进行一些研究,主要是boost::coroutine2类似的 POSIX 函数makecontext()/swapcontext(),并计划在boost::coroutine2. 两者都需要用户代码为每个新函数/协程分配一个堆栈。

我的目标平台是 x64/Linux。我希望我的绿色线程库适用于一般用途,因此堆栈应根据需要扩展(合理的上限很好,例如 10MB),如果未使用太多内存时堆栈可以收缩(不需要),那就太好了)。我还没有找到合适的算法来分配堆栈。

经过一番谷歌搜索,我自己想出了几个选项:

  1. 使用编译器实现的拆分堆栈(gcc -fsplit-stack),但拆分堆栈有性能开销。由于性能原因,Go 已经远离了拆分堆栈。
  2. 分配一大块内存,mmap()希望内核足够聪明,不分配物理内存,只在访问堆栈时分配。在这种情况下,我们受内核的支配。
  3. 预留一个大的内存空间mmap(PROT_NONE)并设置一个SIGSEGV信号处理程序。在信号处理程序中,当SIGSEGV由堆栈访问引起时(访问的内存在保留的大内存空间内),分配所需的内存mmap(PROT_READ | PROT_WRITE)。这是这种方法的问题:mmap()不是异步安全的,不能在信号处理程序中调用。它仍然可以实现,但非常棘手:在程序启动期间创建另一个线程用于内存分配,并用于pipe() + read()/write()将内存分配信息从信号处理程序发送到线程。

关于选项 3 的更多问题:

  1. 我不确定这种方法的性能开销,当内存空间由于数千次mmap()调用而极度碎片化时,内核/CPU 的性能如何?
  2. 如果在内核空间中访问未分配的内存,这种方法是否正确?例如,何时read()调用?

绿色线程的堆栈分配还有其他(更好的)选项吗?在其他实现(例如 Go/Java)中如何分配绿色线程堆栈?

4

2 回答 2

3

glibc 为普通 C 程序分配堆栈的方式是使用专门为此目的设计的以下 mmap 标志来映射一个区域:

   MAP_GROWSDOWN
          Used for stacks.  Indicates to the kernel virtual memory  system
          that the mapping should extend downward in memory.

为了兼容性,您可能也应该使用MAP_STACK。这样你就不用自己写 SIGSEGV 处理程序了,堆栈会自动增长。可以按照此处所述设置边界“ulimit -s unlimited”是做什么的?

如果你想要一个有界的堆栈大小,这通常是人们想要调用信号处理程序时所做的sigaltstack(2),只需发出一个普通的 mmap 调用。

Linux 内核总是映射支持虚拟页面的物理页面,在第一次访问页面时捕获页面错误(可能不是在实时内核中,但肯定在所有其他配置中)。如果您有兴趣,可以使用/proc/<pid>/pagemap界面(或我写的这个工具https://github.com/dwks/pagemap )来验证这一点。

于 2016-03-26T23:32:14.277 回答
0

为什么是地图?当您使用 new(或 malloc)分配内存时,内存不会受到影响,并且绝对不会被映射。

const int STACK_SIZE = 10 * 1024*1024;
char*p = new char[STACK_SIZE*numThreads];

p 现在有足够的内存用于您想要的线程。当你需要内存时,开始访问 p + STACK_SIZE * i

于 2016-02-15T13:42:06.400 回答