PID_NAMESPACES(7) | Руководство программиста Linux | PID_NAMESPACES(7) |
pid_namespaces - обзор пространств имён Linux PID
Обзор пространств имён смотрите в namespaces(7).
Пространства имён PID изолируют пространство номеров идентификаторов процессов, то есть процессы в разных пространствах имён PID могут иметь единый PID. Пространства имён PID позволяют предоставлять такие возможности в контейнерах как приостановку/возобновление работы набора процессов в контейнере и перенос контейнера на новый узел, при чём процессы внутри контейнера сохранят свои PID.
Идентификаторы в новом пространстве имён PID начинаются с 1 как в автономной системе, и вызовы fork(2), vfork(2) или clone(2) будут создавать процессы с уникальными идентификаторами в пределах пространства имён.
Для использования пространств имён PID требуется, чтобы ядро было собрано с параметром CONFIG_PID_NS.
Первый процесс, созданный в новом пространстве имён (т. е., процесс, созданный вызовом clone(2) с флагом CLONE_NEWPID или первый потомок, созданный процессом после вызова unshare(2) с флагом CLONE_NEWPID) имеет PID 1, и это «начальный» (init) процесс пространства имён (смотрите init(1)). Этот процесс становится родителем всех дочерних процессов, которые осиротели из-за завершения родителей, находящихся внутри этого пространства имён PID (дополнительную информацию смотрите ниже).
Если процесс «init» пространства имён PID завершает работу, то ядро завершает работу всех процессов в пространстве имён с помощью сигнала SIGKILL. Такое поведение показывает, что процесс «init» необходим для корректной работы пространства имён PID. В этом случае последующий вызов fork(2) в этом пространство имён PID завершается ошибкой ENOMEM; невозможно создать новые процессы в пространстве имён PID, в котором не работает процесс «init». Такое может произойти, например, когда процесс использует открытый файловый дескриптор файла /proc/[pid]/ns/pid, соответствующий процессу, который был в пространстве имён setns(2), в этом пространстве имён после того, как процесс «init» завершился. Другой возможный сценарий может произойти после вызова unshare(2): если первый потомок после этого созданный fork(2) завершится, то последующие вызовы fork(2) завершаются ошибкой ENOMEM.
Члены пространства имён PID могут посылать процессу «init» только сигналы, для которых процесс «init» установил обработчики. Это ограничение применяется даже к привилегированным процессам, и не позволяет другим членам пространства имён PID нечаянно убить процесс «init».
Также, процесс в родительском пространстве имён может (пройдя обычные проверки прав, описанные в kill(2)) послать сигнал процессу «init» дочернего пространства имён PID только, если процесс «init» задал обработчики для этого сигнала (внутри обработчика si_pid в siginfo_t, описанное в sigaction(2) будет равно нулю). Для сигналов SIGKILL и SIGSTOP сделано исключение: эти сигналы принудительно доставляются при отправке из родительского пространства имён PID. Ни один из них не может быть пойман процессом «init», и в результате будут выполнены обычные действия, связанные с этими сигналами (завершение и останов процесса, соответственно).
Начиная с Linux 3.4, системный вызов reboot(2) посылает сигнал в пространство имён процесса «init». Подробности смотрите в reboot(2).
Пространства имён PID могут быть вложенными: каждое пространство имён PID имеет родителя, за исключением начального («корневого») пространства имён PID. Родитель пространства имён PID — это пространство имён PID процесса, который был создан в пространстве имён с помощью clone(2) или unshare(2). Таким образом пространства имён PID образуют дерево со всеми пространствами имён по которому, в конечном счёте, можно проследить их родословную до корневого пространства имён. Начиная с Linux 3.7, ядро ограничивает глубину максимальной вложенности пространств имён PID значением 32.
Процесс видим другим процессам в своём пространстве имён PID, и процессам в каждом прямом родительском пространстве имён PID вплоть до корневого пространства имён PID. В этом контексте «видимость» означает, что процесс может быть целью операции другого процесса в системных вызовах, который указывает идентификатор процесса. И наоборот, процессы в дочернем пространстве имён PID не видят процессы в родительском и более удалённых родительских пространствах имён. Более кратко: процесс может видеть (например, посылать сигналы с помощью kill(2), назначать значения уступчивости с помощью setpriority(2) и т. п.) только процессы из своего пространства имён PID и в потомках этого пространства имён.
Процесс имеет идентификатор в каждом слое иерархии пространства имён PID, в котором он виден, и двигаясь назад, в каждом прямом предке пространства имён вплоть до корневого пространства имён PID. Системные вызовы, работающие с идентификатором процесса, всегда используют идентификатор процесса, который видим в пространстве имён PID вызывающего. Вызов getpid(2) всегда возвращает PID, связанный с пространством имён, в котором был создан процесс.
Некоторые процессы в пространстве имён PID могут иметь родителей, которые находятся вне пространства имён. Например, родитель начального процесса в пространстве имён (т. е., процесс init(1) с PID 1) неизбежно находится в другом пространстве имён. Аналогичным образом, прямые потомки процесса, который использует setns(2) для объединения потомков в пространство имён PID, находятся в другом пространстве имён PID относительно вызывающего setns(2). При вызове getppid(2) для таких процессов возвращает 0.
Хотя процессы могут свободно перемещаться вниз в дочерние пространства имён PID (например, с помощью setns(2) с файловым дескриптором пространства имён PID), они не могут перемещаться в обратном направлении. То есть процессы не могут входить в пространства имён предка (родителя, прародителя и т .п.). Изменение пространств имён PID — это односторонняя операция.
Операцию NS_GET_PARENT ioctl(2) можно использовать для обнаружения родительской связи между пространствами имён PID; смотрите ioctl_ns(2).
Последовательно выполняемые вызовы setns(2) с файловым дескриптором пространства имён PID и вызов unshare(2) с флагом CLONE_NEWPID помещают потомков в другое пространство имён PID, отличное от пространства вызывающего (начиная с Linux 4.12 данное пространство имён PID показывается через файл /proc/[pid]/ns/pid_for_children как описано в namespaces(7)). Однако, эти вызовы не изменяют пространство имён PID вызывающего процесса, так как это позволило бы изменять PID вызывающего (сообщаемые getpid()), что сломало бы многие приложения и библиотеки.
Посмотрим на вещи под другим углом: членство процесса в пространстве имён PID определяется при создании процесса и не может быть изменено в дальнейшем. Помимо прочего, это означает, что родственные отношения между процессами зеркально отражают родственные отношения между пространствами имён PID: родитель процесса находится в том же пространстве имён или в непосредственном родительском пространстве имён namespace.
Процесс может вызвать unshare(2) с флагом CLONE_NEWPID только один раз. После выполнения этой операции его символическая ссылка /proc/PID/ns/pid_for_children станет пустой, пока пространстве имён не будет создать первый потомок.
Когда дочерний процесс становится сиротой, его родителем становится «начальный» процесс в пространстве имён PID его родителя (если один из ближайших родственных процессов родителя не вызывал команду prctl(2) PR_SET_CHILD_SUBREAPER для назначения себя сборщиком собирателем дочерних процессов). Заметим, что благодаря семантике setns(2) и unshare(2), описанной выше, это может быть процесс «init» в пространстве имён PID, являющийся родителем пространства имён PID потомка, а не процесс «init» пространства имён PID самого потомка.
В текущих версиях Linux CLONE_NEWPID нельзя объединять вместе с CLONE_THREAD. Для нитей требуется находиться в том же пространстве имён PID, чтобы нити процесса могли посылать сигналы друг другу. Также требуется видеть все нити процессов в файловой системе proc(5). Также, если две нити находятся в разных пространствах имён PID, то ID процесса, посылающего сигнал, невозможно достоверно закодировать при посылке (смотрите описание типа siginfo_t в sigaction(2)). Так как вычисление выполняется когда сигнал попадает в очередь сигналов, общая для процессов из нескольких пространств имён PID, очередь сигналов не позволяет этого.
В ранних версиях Linux значение CLONE_NEWPID также запрещалось (возвращалась ошибка EINVAL) объединять с CLONE_SIGHAND (до Linux 4.3), а также с CLONE_VM (до Linux 3.12). Изменения, снявшие эти ограничения, были также перенесены в более ранние стабильные ядра.
В файловой системе /proc отображаются (в каталогах /proc/[pid]) только процессы, видимые в пространстве имён PID процесса, который выполнил монтирование, даже если файловая система /proc видима из процессов другого пространства имён.
После создания нового пространства имён PID у потомка полезно изменить его корневой каталог и смонтировать новый экземпляр procfs в /proc для того, чтобы корректно работали инструменты типа ps(1). Если одновременно создаётся новое пространство имён монтирования, добавлением флага CLONE_NEWNS в аргумент flags вызова clone(2) или unshare(2), то необязательно изменять корневой каталог: новый экземпляр procfs можно смонтировать непосредственно в /proc.
Команда оболочки для монтирования /proc:
$ mount -t proc proc /proc
Вызов readlink(2) с путём /proc/self выдаёт идентификатор процесса вызывающего из пространства имён PID, откуда смонтирован procfs (т. е., из пространства имён PID процесса, который смонтировал procfs). Это может быть полезно при самоанализе, когда процесс хочет узнать свой PID в других пространствах имён.
Когда идентификатор процесса передаётся через доменный сокет UNIX в процесс в другом пространстве имён PID (смотрите описание SCM_CREDENTIALS в unix(7)), то он транслируется в соответствующее значения PID из пространства имён PID принимающего процесса.
Пространства имён есть только в Linux.
См. user_namespaces(7).
clone(2), reboot(2), setns(2), unshare(2), proc(5), capabilities(7), credentials(7), mount_namespaces(7), namespaces(7), user_namespaces(7), switch_root(8)
2019-03-06 | Linux |