Процесс — это среда выполнения для работы экземпляра программы. Например, есть какая-то программа, и при её запуске для неё создается процесс. Этот процесс наделяется некоторыми свойствами, ему выделяется часть оперативной памяти и присваивается уникальный идентификатор. И программа работает в рамках этого процесса.
Процессор (либо каждое из ядер процессора в случае многоядерных процессоров) обрабатывает эти процессы не одновременно. Он очень быстро переключается с одного процесса на другой, за счет чего возникает ощущение многозадачности.
С процессом связан целый ряд атрибутов, которые расширяют или ограничивают его возможности. В течение жизни процесс может находится в разных состояниях.
Жизнь процесса начинается с рождения, совсем как у человека. Еще одно сравнение с человеком — это то, что процесс может порождать другие процессы. Так процессы в Linux делятся на родительские и дочерние по отношению друг к другу. Например, когда вы работаете в терминале, вы работаете с интерпретатором bash, который работает в своем процессе. И если вы запускаете какую-нибудь команду, например ls, то процесс программы bash запускает процесс для программы ls. При этом bash становится родительским процессом для ls, а ls — дочерним для bash.
Единственный процесс, который запускается не другим процессом, а ядром — это процесс инициализации системы. Он запускается самым первым и запускает все остальные процессы в Linux. Его атрибут PID (см. далее) равен 1.
Когда процесс завершает свою работу, он сообщает об этом родительскому процессу и освобождает все свои ресурсы, кроме одного атрибута, который называется PID, это уникальный номер процесса. Удалить PID умершего процесса должен его родитель. Пока он этого не сделает, умерший процесс остается в состоянии «Зомби». Но об этом позже.
Если процесс запущен на 32-разрядной операционной системе, то система ему сможет выделить максимум 2^32 = 4GB памяти. Если же это 64-разрядная операционная система, то теоретически процесс сможет получить 2^48 = 256TB памяти. В эту оперативную память загружается:
Если для кода и данных память выделяется сразу в момент рождения процесса, то стек и куча могут расти или уменьшаться.
В системе Linux существуют разные типы процессов:
Пользовательские Linux-процессы работают от имени обычной учетной записи пользователя и выполняются в пространстве пользователя. Если процесс не запущен таким образом, который дает процессу дополнительные разрешения, то пользовательский процесс не имеет доступа к тем файлам в системе, к которым не имеет доступа сам пользователь, запустивший процесс.
Процессы-демоны предназначенные для работы приложения в фоновом режиме. Такие процессы обычно управляются какими-нибудь службами (сервисами SystemD). Процесс-демон может прослушивать входящие запросы к службе. Например, apache2 является процессом-демоном и прослушивает запросы на просмотр веб-страниц. Такие процессы могут работать от имени root или от имени специальных системных пользователей. Например, apache2 работает от имени пользователя www-data.
Процессы ядра выполняются только в пространстве ядра. Они похожи на процессы-демоны. Основное отличие состоит в том, что процессы ядра имеют полный доступ к структурам ядра. Процессы ядра не так гибки, как процессы-демоны. Вы можете изменить поведение процесса-демона, изменив конфигурационные файлы и перезагрузив службу, но для изменения процессов ядра может потребоваться перекомпиляция ядра.
Каждый процесс имеет набор атрибутов, перечислим основные атрибуты:
pid — идентификатор процесса;
ppid — идентификатор родительского процесса;
tty — управляющий терминал, с которого поступает ввод и на который поступает вывод. То есть stdin, stderr, stdout;
sid — идентификатор сеанса;
ruid — идентификатор пользователя, запустившего исполняемый файл;
rgid — идентификатор группы пользователя, запустившего исполняемый файл;
euid — идентификатор фактического пользователя, от имени которого работает процесс;
egid — идентификатор фактической группы, от имени которой работает процесс;
pgid — идентификатор группы процессов;
tpgid — идентификатор терминальной группы процессов (foreground group);
pri — приоритет работы процесса, чем выше значение, тем выше приоритет. Пользователь, даже root, не может менять это значение;
ni — любезность (уступчивость) процесса, от него зависит pri. Чем выше ni, тем ниже pri. Только root может менять это значение;
s — состояние, в котором находится процесс.
Процесс либо обрабатывается процессором, либо не обрабатывается. Только один процесс может обрабатываться на одном ядре процессора. Все остальные процессы должны ждать или находиться в каком-то другом состоянии. Вот список состояний, в которых может находится процесс:
R (running — запущенный). Процесс находится на обработке у процессора.
R (runnable — готов к работе). Процесс готов к обработке и находится в очереди к процессору. Running и runnable обозначаются одинаковой буквой R.
D (uninterruptible sleep — в беспробудном сне). Когда процесс обращается к устройству, например к диску или сетевой карте, он переходит в это состояние. А возвращается из него только после получения запрошенной информации. Обычно в таком состоянии процесс находится недолго. В таком состоянии процесс перестает обрабатывать любые сигналы и, если что-то пойдет не так, то завершить такой зависший процесс, не перезагружая сервер, не получится.
I (Idle — бездействующий поток ядра). Состояние похоже на D, но такие процессы не нагружают процессор и исключены из расчета средней нагрузки системы (load average).
S (sleeping — спит). Процесс ожидает какие-то ресурсы, которые в данный момент недоступны. При переходе в это состояние процесс немедленно отказывается от доступа к процессору. Когда ресурс, который ожидает процесс, становится доступным, процесс может снова перейти в состояние R (runnable). В спящем состоянии процесс продолжает обрабатывать сигналы.
T (stopped by job control signal — остановленный специальным сигналом). Про сигналы поговорим позже, тут главное понять, что процессы могут обрабатывать сигналы, и один из них может остановить процесс. Это состояние похоже на паузу, то есть из этого состояния процесс может выйти и продолжить свою работу в состоянии R или S.
t (stopped by debugger during trace — остановлен отладчиком во время трассировки). Состояние подобное T, но в этом случае остановка процесса произошла не по сигналу, а во время отладки.
Z (zombie — зомби). Когда процесс завершает свою работу, он освобождает свои ресурсы, но не освобождает свой PID в таблице процессов. Вместо этого он отправляет сигнал родителю, сообщая что он завершается. Родительский процесс должен освободить PID дочернего процесса. Отрезок времени между завершением процесса и освобождением родителем PID завершенного дочернего процесса называется состоянием зомби. Процесс может остаться в состоянии зомби, если родительский процесс умрет до того, как освободит PID дочернего процесса
Сигналы – это один из способов межпроцессного взаимодействия. Они обеспечивают возможность передачи процессами друг-другу команд и сообщений, таких, как, например, сообщение о завершении работы дочернего процесса, или команда перечитать файл конфигурации, или сообщение об ошибке операции с плавающей запятой. Одно из главных направлений использования сигналов состоит в снятии процессов с исполнения. Список сигналов, используемых в системе, можно получить с помощью команды:
kill -l
Наиболее часто приходится использовать следующие сигналы:
1 – HUP – разрыв связи с терминалом (Hang Up – положить трубку). Многие демоны используют этот сигнал, как команду перечитать их конфигурационный файл и продолжить работу с измененными настройками. Оболочка Bash реагирует на этот сигнал завершением сеанса.
2 – INT – клавиатурное прерывание процесса. В GNU/Linux генерируется при нажатии Ctrl + C.
3 – QUIT – “отключение” клавиатуры. Получение этого сигнала обычно снимает процесс с исполнения.
15 – TERM – сигнал для завершения процесса. Получив этот сигнал приложение должно (но не обязано) завершить свою работу корректно, закрыв потоки ввода-вывода. Этот сигнал команда kill посылает по умолчанию.
9 – KILL – безусловное и немедленное снятие процесса с исполнения без корректного завершения процесса.
Каким образом то или иное приложение реагирует на получение некоторого сигнала зависит от того, как эта программа написана. В программе получение сигнала может перехватываться и обрабатываться специальным образом. Сигнал KILL не может быть перехвачен. Этот сигнал приводит к немедленному и, таким образом, часто некорректному снятию процесса с исполнения. При этом файлы, открытые процессом не закрываются нормальным способом, что может привести к существенной потере данных или даже сбою настроек терминала.
Чтобы отправлять сигналы мы используем утилиту kill (даже если отправляем сигнал отличный от KILL). В этом примере мы отправляем сигнал INT:
kill -2 <pid>
Посылать сигналы процессам могут только их владелец и суперпользователь. Если родительским процессом получен сигнал, приводящий к его остановке, то сняты с выполнения будут также и все его дочерние процессы.
Удобно пользоваться командами pkill и killall. Они действует подобно команде kill, позволяя, как и kill, послать требуемый сигнал нескольким заданным процессам. Однако, если для kill эти процессы должны быть указаны с помощью их PID, то командам pkill и killall достаточно указать имя командной строки процесса. Например, следующая команда приведет к немедленному останову всех копий демона httpd:
killall -9 httpd
В результате передачи сигнала 9 (KILL) всем процессам httpd они будут сняты с исполнения, так как перехватить такой сигнал невозможно.