Обзор

Вы когда-нибудь интересовались, что происходит за кулисами, когда мы запускаем или завершаем процесс? В этом уроке мы узнаем, как Linux генерирует PID для процессов.

Таблица процессов в Linux

Ядро Linux использует структуру данных, называемую таблицей процессов, для различных задач, таких как планирование процессов. Каждый раз, когда мы запускаем процесс, ядро вставляет в таблицу запись со следующей информацией:

  • PID

  • Родительский процесс

  • Переменные окружения

  • Прошедшее время

  • Статус — один из D (Непрерываемый), R (Выполняется), S (Спящий), T (Остановлен) или Z (Зомби)

  • Использование памяти

Мы можем получить эту информацию через файловую систему procfs, смонтированную вкаталоге /proc, с помощью различных инструментов мониторинга ресурсов, таких как top.

Давайте посмотрим на некоторые из этих данных, выполнив команду top:

Mem: 4241112K used, 12106916K free, 360040K shrd, 20K buff, 1772160K cached
CPU:  0.8% usr  0.8% sys  0.0% nic 98.3% idle  0.0% io  0.0% irq  0.0% sirq
Load average: 0.26 0.33 0.35 1/489 21888
  PID  PPID USER     STAT   VSZ %VSZ CPU %CPU COMMAND
    1     0 root     S     2552  0.0  10  0.0 init
30405 30404 baeldung S     2552  0.0   7  0.0 /bin/sh
  219     1 root     S     2552  0.0  10  0.0 /bin/getty 38400 tty2
 1309  1308 baeldung S     2552  0.0   9  0.0 /bin/sh
21873   640 baeldung S     2552  0.0   5  0.0 [sleep]
21874 21758 baeldung R     2552  0.0   8  0.0 top

Генерация PID

Linux назначает идентификаторы процессов последовательно, начиная с 0 и вплоть до максимального установленного значения.

За процессом ядра idle task, который обеспечивает постоянное наличие готовой к выполнению задачи, зарезервирован PID 0, а за системой init, которая явлется первым процессом, зарезервирован PID 1.

Процесс с PID 0 — это холостая задача, которая является «процессом‑заготовкой». Он запускается, когда другие процессы не могут быть запущены, гарантируя, что процессор остается активным и готовым к выполнению других задач по мере их появления.

Мы можем проверить максималоно допустимое значение PID в системе, заглянув в файл /proc/sys/kernel/pid_max. Обычно это 5-значное число:

$ cat /proc/sys/kernel/pid_max 
32768

Мы можем настроить ограничение максимум до 222 (4 194 304), записав под root желаемое число в файл:

# desired=4194304# echo {pid##*/}" # Извлечение PID    [ "highest" ] && highest="highest"for _ in (readlink /proc/self)"done

Наша вторая попытка выдала ошибку, так как число превышает 222.

Когда мы запускаем процесс, для него генерируется PID, позволяющий однозначно идентифицировать его. Это делается просто путем увеличения текущего наивысшего PID на 1.

Давайте подтвердим это с помощью простого скрипта:

!/bin/sh -e
# Этот скрипт предполагает, что printf является встроенной функцией shell и, следовательно, не занимает лишних PID.
highest=0

for pid in /proc/[0-9]*; do
    pid="${pid##*/}" # Извлекаем PID
    [ "$pid" -gt "$highest" ] && highest="$pid" # -gt означет больше, чем ("greater than")
done

printf "Найбольший PID равен %d\n" "$highest"

for _ in $(seq 4); do
    printf "Запущен новый процесс с PID %d\n" "$(readlink /proc/self)"
done

Сначала мы вычислили текущий найбольший PID в системе. Затем мы запустили четыре процесса readlink, каждый из которых отобразил присвоенный ему новый PID.

Взглянем на вывод скрипта:

Наибольший PID - 10522
Запущен новый процесс с PID 10524
Запущен новый процесс с PID 10525
Запущен новый процесс с PID 10526
Запущен новый процесс с PID 10527

Мы можем заметить, что между проверкой наивысшего PID и печатью новых PID существует разрыв в 1 PID, поскольку сама команда seq сама запускает еще один дополнительный процесс. Следовательно, внешние процессы влияет на подобные тесты, т.к. сами процессы создают новые PID.

Могут ли закончиться PIDы?

В предыдущем разделе мы обсуждали максимальное значение PID в системе, так что же произойдет, когда мы достигнем этого предела?

Если мы достигаем максимального значения PID, то поиск следующего PID начинается снова с минимального значения и продолжается, пока не найдётся свободный PID, принадлежащий уже завершённому процессу.

Следует отметить, что процесс считается «завершенным» только когда родительский процесс получил его статус завершения. Таким образом, зловредная программа может исчерпать доступные PID в системе.

Заключение

В этой статье мы узнали об идентификаторах процессов в Linux — как они генерируются, как высоко они могут подниматься и что происходит при достижении предела.

Комментарии (5)


  1. pae174
    14.07.2024 17:34
    +5

    Добавлю свои две копейки. В некоторых операционных системах, например FreeBSD, PIDы назначаются случайным образом а не последовательно. Считается, что если процессы имеют случайные PID и юзер не видит список процессов другого юзера, то это более безопасно, чем простой инкремент PID.


    1. jurikolo
      14.07.2024 17:34

      Интересно, насколько сильно такой подход увеличивает нагрузку в ситуации с большим кол-вом запущенных процессов, ведь поиск свободного числа случайным образом не оптимален. В прошлом сталкивался с ситуациями, когда приложение упиралось в ограничение PID'ов, приходилось повышать лимит.

      Сейчас глянул, на домашнем ноутбуке лимит по-умолчанию соответствует максимуму, то есть 4194304.


      1. KivApple
        14.07.2024 17:34

        Можно хранить список свободных pid и брать случайный элемент из него одновременно удаляя. А при завершении процесса класть его pid в список. Тогда генерация случайного pid может иметь O(1) ценой роста потребления памяти. Но хранить 32768 int на современном железе не проблема.


        1. saboteur_kiev
          14.07.2024 17:34

          большинство современных дистрибутивов из коробки уже имеют 4 млн пидов.
          Линукс часто используется в ембеддед устройствах и контейнерах, каждая лишняя страница памяти на учете может быть.


  1. saboteur_kiev
    14.07.2024 17:34

    Это делается просто путем увеличения текущего наивысшего PID на 1

    Ну уже ж не просто путем увеличения наивысшего, а где-то хранится определенный счетчик, который инкрементируется, пока не достигнет max pid, потом пойдет с начала, плюс будет проверять что данный pid сейчас свободен. Искать наивысший на текущий момент пид по дереву процессов дольше. Плюс не забываем про баги, из-за которых появляются зомби процессы.

    что процесс считается "завершенным" только в том случае, если его статус завершения был получен его родителем.

    Словно чатгпт писал. Я не понимаю смысл. Система может прибить процесс, у которого есть дочерние процессы. У них временно вообще родителя может не быть, ОС потом присвоит им родителем процесс с PID1. Я не могу точно сказать в какой момент именно происходит возврат exit code родительскому процессу, но вполне могу предположить, что после того, как завершился дочерний процесс и освободил PID, параллельно может родиться новый процесс, а потом exit code дойдет до родительского процесса, или хотя бы дойдет до родительского процесса и будет как-то обработано.

    Из-за счетчика процессов вероятность того, что новый процесс займет это же PID невероятно низка из-за счетчика процессов, в котором max-pid в современных системах уже не пятизначный, но технически это наверное возможно.

    Но это все слишком базовые вещи.
    Если уж писать статью, то хотя бы чуть больше, чем написано в википедии. Например о том, что дерево процессов может быть не одно, что весьма полезно учитывая нынешние тренды контейнеров.