Можно ли развить процесс без наследования пространства виртуальной памяти родительского процесса?
Поскольку родительский процесс использует огромное монтирование памяти, fork
может потерпеть неудачу с errno
из ENOMEM
при некоторой конфигурации ядра overcommit policy. Даже если дочерний процесс может только exec
программа с низким потреблением памяти, такая как ls.
Чтобы прояснить проблему, когда /proc/sys/vm/overcommit_memory настроен на 2, выделение (виртуальной) памяти ограничено SWAP + MEMORY * ration(default to 50%)
, Когда процесс разветвляется, виртуальная память не копируется благодаря COW. Но ядру все равно нужно выделять пространство виртуальной памяти. По аналогии, fork похож на malloc(размер пространства виртуальной памяти), который не будет выделять физическую память, а запись в разделяемую память приведет к копированию виртуальной памяти и выделению физической памяти. Если для overcommit_memory задано значение 2, ответвление может завершиться ошибкой из-за выделения пространства виртуальной памяти.
Это возможно fork
процесс без наследования пространства виртуальной памяти родительского процесса в следующих условиях?
если дочерний процесс вызывает
exec
послеfork
если дочерний процесс не вызывает
exec
и не будет использовать какие-либо глобальные или статические переменные из родительского процесса. Например, дочерний процесс просто делает некоторые записи, а затем завершается.
3 ответа
Как ответил Василий Старынкевич, это невозможно.
Однако для этого используется очень простое и распространенное решение, которое не зависит от поведения Linux или контроля за чрезмерной загрузкой памяти: используйте рано-разветвленный подчиненный процесс, сделайте fork и exec.
Сделайте так, чтобы большой родительский процесс создал сокет домена unix и как можно раньше разветвлял подчиненный процесс, закрывая все другие дескрипторы в подчиненном (повторное открытие STDIN_FILENO
, STDOUT_FILENO
, а также STDERR_FILENO
в /dev/null
). Я предпочитаю сокет датаграмм за его простоту и гарантии, хотя сокет потока также будет работать.
В некоторых редких случаях полезно, чтобы подчиненный процесс выполнял отдельную выделенную небольшую вспомогательную программу. В большинстве случаев это не является необходимым и значительно упрощает проектирование безопасности. (В Linux вы можете включать вспомогательные сообщения SCM_CREDENTIALS при передаче данных через сокет домена Unix и использовать ID процесса в нем для проверки идентификатора / исполняемого файла, который использует узел /proc/PID/exe
псевдо-файл).
В любом случае подчиненный процесс заблокирует чтение из сокета. Когда другой конец закроет сокет, чтение / получение вернет 0, и подчиненный процесс завершится.
Каждая дейтаграмма, которую получает подчиненный процесс, описывает команду для выполнения. (Использование дейтаграммы позволяет использовать строки C, разделенные символами NUL, без экранирования и т. Д.; для использования потокового сокета Unix обычно требуется как-то разграничить "команду", что, в свою очередь, означает экранирование разделителей в строках компонентов команды.)
Подчиненный процесс создает один или несколько каналов и разветвляет дочерний процесс. Этот дочерний процесс закрывает исходный сокет Unix, заменяет стандартные потоки соответствующими концами канала (закрывая другие концы) и выполняет требуемую команду. Лично я предпочитаю использовать дополнительный сокет close-on-exec в Linux для обнаружения успешного выполнения; в случае ошибки код errno записывается в сокет, так что подчиненный родитель может надежно обнаружить сбой и точную причину. В случае успеха подчиненный родитель закрывает ненужные концы канала, отвечает на исходный процесс об успехе, а другой конец канала представляет собой вспомогательные данные SCM_RIGHTS. После отправки сообщения оно закрывает остальную часть канала и ждет нового сообщения.
На стороне оригинального процесса вышеупомянутый процесс является последовательным; только один поток может выполнить запуск внешнего процесса одновременно. (Вы просто сериализуете доступ с помощью мьютекса.) Несколько могут работать одновременно; это только запрос и ответ от вспомогательного помощника, который сериализуется.
Если это проблема - это не должно быть в типичных случаях - вы можете, например, мультиплексировать соединения, добавляя к каждому сообщению префикс ID (назначенный родительским процессом, монотонно увеличивающийся). В этом случае вы, вероятно, будете использовать выделенный поток на родительском конце для управления связью с ведомым устройством, поскольку вы, конечно, не можете иметь несколько потоков, считывающих данные из одного и того же сокета одновременно, и ожидать детерминированных результатов.
Дальнейшие улучшения схемы включают в себя такие вещи, как использование выделенной группы процессов для выполняемых процессов, установка для них ограничений (путем установки ограничений для подчиненного процесса) и выполнение команд в качестве выделенных пользователей и групп с использованием привилегированного ведомого устройства.
В случае привилегированного ведомого наиболее полезно, чтобы родительский узел выполнил для него отдельный вспомогательный процесс. В Linux обе стороны могут использовать SCM_CREDENTIALS
вспомогательные сообщения через доменные сокеты Unix для проверки личности (PID и с ID, исполняемый файл) однорангового узла, что делает его довольно простым для реализации надежной безопасности. (Но учтите, что /proc/PID/exe
необходимо проверять более одного раза, чтобы отследить атаки, когда сообщение отправляется вредоносной программой, быстро выполняющей соответствующую программу, но с аргументами командной строки, которые приводят к ее быстрому завершению, из-за чего она иногда выглядит как правильный исполняемый файл, сделавший запрос, в то время как копия дескриптора - и, следовательно, весь канал связи - находилась под контролем ненастоящего пользователя.)
Таким образом, исходная проблема может быть решена, хотя ответ на поставленный вопрос - Нет. Если выполнения чувствительны к безопасности, например, изменение привилегий (учетных записей пользователей) или возможностей (в Linux), то проект должен быть тщательно продуман. рассматривается, но в обычных случаях реализация довольно проста.
Я был бы рад разработать в случае необходимости.
Нет, это невозможно. Вас может заинтересовать vfork(2), который я не рекомендую. Смотрите также в mmap(2) и его MAP_NORESERVE
флаг. Но методы копирования при записи используются ядром, поэтому вы практически не удвоите потребление оперативной памяти.
Мое предложение состоит в том, чтобы иметь достаточно места подкачки, чтобы не быть обеспокоенным такой проблемой. Поэтому настройте свой компьютер таким образом, чтобы иметь больше доступного пространства подкачки, чем самый большой запущенный процесс. Вы всегда можете создать временный файл подкачки (например, с помощью dd if=/dev/zero of=/var/tmp/swapfile bs=1M count=32768
затем mkswap /var/tmp/swapfile
) затем добавьте его в качестве временной зоны подкачки (swapon /var/tmp/swapfile
) и удалите его (swapoff /var/tmp/swapfile
а также rm /var/tmp/swapfile
) когда тебе это больше не нужно.
Вы, вероятно, не хотите менять местами файловую систему tmpfs, например /tmp/
это часто происходит, поскольку файловые системы tmpfs резервируются с помощью пространства подкачки!.
Мне не нравится чрезмерная загрузка памяти, и я отключаю ее (через proc (5)). YMMV.
Я не знаю ни одного способа сделать (2), но для (1) вы можете попробовать использовать vfork
который будет форкировать новый процесс без копирования таблиц страниц родительского процесса. Но это, как правило , не рекомендуется по ряду причин, в том числе из-за того, что родительский блок блокируется, пока ребенок не выполнит execve
или прекращается.
Это возможно в Linux. Использовать clone
системный вызов без флага CLONE_THREAD
и с флагом CLONE_VM
, Родительский и дочерний процессы будут использовать те же отображения, что и поток; нет COW или копирования таблицы страниц.
madvise(addr, size, MADV_DONTFORK)
В качестве альтернативы вы можете вызвать munmap() после fork(), чтобы удалить виртуальные адреса, унаследованные от родительского процесса.