Я делаю некоторые исследования в C ++ зеленые темы, в основном boost::coroutine2
и аналогичные функции POSIX, такие как makecontext()/swapcontext()
и планируем реализовать библиотеку зеленых потоков C ++ поверх boost::coroutine2
, Оба требуют, чтобы пользовательский код выделял стек для каждой новой функции / сопрограммы.
Моя целевая платформа — x64 / Linux. Я хочу, чтобы моя библиотека зеленых потоков была пригодна для общего использования, поэтому стеки должны расширяться по мере необходимости (допустим верхний предел, например, 10 МБ), было бы здорово, если бы стеки могли сокращаться, когда слишком много памяти не используется (не требуется) ). Я не нашел подходящий алгоритм для распределения стеков.
После некоторого поиска в Google я сам нашел несколько вариантов:
mmap()
надеюсь, ядро достаточно умен, чтобы оставить физическую память нераспределенной и распределять ее только при обращении к стекам. В этом случае мы находимся во власти ядра.mmap(PROT_NONE)
и настроить SIGSEGV
обработчик сигнала. В обработчике сигнала, когда SIGSEGV
вызвано стековым доступом (доступ к памяти находится внутри большого зарезервированного пространства памяти), выделите необходимую память с помощью mmap(PROT_READ | PROT_WRITE)
, Вот проблема для этого подхода: mmap()
не асинхронно безопасен, не может быть вызван внутри обработчика сигнала. Это все еще может быть реализовано, очень сложно хотя: создайте другой поток во время запуска программы для выделения памяти и используйте pipe() + read()/write()
отправлять информацию о выделении памяти из обработчика сигнала в поток. Еще несколько вопросов по варианту 3:
mmap()
вызов ?read()
называется ?Есть ли другие (лучшие) варианты размещения стека для зеленых потоков? Как выделяются стеки зеленых потоков в других реализациях, например Go / Java?
Способ, которым 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), чтобы проверить это, если вы заинтересованы.
Почему ММАП? Когда вы выделяете с помощью new (или malloc), память остается нетронутой и определенно не отображается.
const int STACK_SIZE = 10 * 1024*1024;
char*p = new char[STACK_SIZE*numThreads];
Теперь у p достаточно памяти для нужных вам потоков. Когда вам понадобится память, начните доступ к p + STACK_SIZE * i