управление памятью — выделение стека для зеленых потоков C ++

Я делаю некоторые исследования в C ++ зеленые темы, в основном boost::coroutine2 и аналогичные функции POSIX, такие как makecontext()/swapcontext()и планируем реализовать библиотеку зеленых потоков C ++ поверх boost::coroutine2, Оба требуют, чтобы пользовательский код выделял стек для каждой новой функции / сопрограммы.

Моя целевая платформа — x64 / Linux. Я хочу, чтобы моя библиотека зеленых потоков была пригодна для общего использования, поэтому стеки должны расширяться по мере необходимости (допустим верхний предел, например, 10 МБ), было бы здорово, если бы стеки могли сокращаться, когда слишком много памяти не используется (не требуется) ). Я не нашел подходящий алгоритм для распределения стеков.

После некоторого поиска в Google я сам нашел несколько вариантов:

  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() вызов ?
  2. Верен ли этот подход, если доступ к нераспределенной памяти осуществляется в пространстве ядра? например когда read() называется ?

Есть ли другие (лучшие) варианты размещения стека для зеленых потоков? Как выделяются стеки зеленых потоков в других реализациях, например Go / Java?

12

Решение

Способ, которым 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 неограниченно" делать?

Если вы хотите ограниченный размер стека, который обычно делают люди для обработчиков сигналов, если они хотят вызвать sigaltstack(2), просто выполните обычный вызов mmap.

Ядро Linux всегда отображает физические страницы, которые поддерживают виртуальные страницы, улавливая ошибку страницы при первом обращении к странице (возможно, не в ядрах реального времени, но, безусловно, во всех других конфигурациях). Вы можете использовать /proc/<pid>/pagemap интерфейс (или этот инструмент, который я написал https://github.com/dwks/pagemap), чтобы проверить это, если вы заинтересованы.

2

Другие решения

Почему ММАП? Когда вы выделяете с помощью new (или malloc), память остается нетронутой и определенно не отображается.

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

Теперь у p достаточно памяти для нужных вам потоков. Когда вам понадобится память, начните доступ к p + STACK_SIZE * i

0

По вопросам рекламы ammmcru@yandex.ru
Adblock
detector