Процессы и потребление ресурсов сервера
Last updated
Last updated
В общем представлении, процесс - это программа, выполняющаяся в оперативной памяти компьютера. Реально - все гораздо сложней.
В многозадачной системе может быть запущено множество программ. Каждая программа может запустить множество процессов (подпрограмм). При этом в единственный момент на машине, выполняется только 1 процесс.
То есть в единственный момент времени ресурсы железа (процессорное время, память, порт ввода/вывода) могут использоваться только единственным процессом. Очередью, в которой процессу выделяется определенный ресурс железа, управляет планировщик. При этом, во время прерывания одного процесса и запуска (возобновления) другого процесса, состояние процесса (выполняемые действия, на каком этапе процесс приостановлен) запоминается и записывается в область памяти.
Планировщик в Linux - это часть ядра, отвечающая за указанную функциональность.
В задачи планировщика так же входит отслеживание и выделение запускаемым процессам определенного приоритета, чтобы процессы "не мешали" друг-другу работать, а так же распределение пространства памяти, чтобы пространство памяти одного процесса не пересекалось с пространством другого.
Все новые процессы в Linux порождаются клонированием какого-то уже имеющегося процесса, с помощью вызова системных функций clone(2) и fork(2) (от forking - порождение). У нового (порожденного или дочернего) процесса то же окружение, что и у родителя, отличается только номер ID процесса (т.н. PID).
Жизнь типичного процесса в Linux можно представить следующей схемой:
На которой можно описать пошагово следующие этапы:
процесс /bin/bash клонирует себя системным вызовом fork()
при этом создается клон /bin/bash с новым PID (идентификатор процесса) и PPID - равный PID родителя
клон выполняет системный вызов exec с указанием на исполняемый файл и заменяет свой код - кодом исполняемого файла (родительский процесс при этом ждет завершения потомка - wait)
при этом, если по каком-то причинам, потомок завершил свою работу, а родительский процесс не смог получить об этом сигнал, то данный процесс (потомок) не освобождает занятые структуры ядра и состояние процесса становиться - zombie. О состояниях процесса ниже...
Очень наглядную схему предоставила Wikipedia:
Из вышесказанного может последовать логичный вопрос: если новый процесс - всегда копия существующего, то каким образом в системе берется самый первый из процессов?
Первый процесс в системе запускается при инициализации ядра. Данный процесс называется - init и имеет PID=1. Это прародитель всех процессов в системе. Подробнее о процессе загрузки ядра и рождении процесса init можно почитать тут.
В каких же состояниях может находиться процесс в Linux?
Каждый запущенный процесс в любой момент времени находится в одном из следующих состояний (которое называют еще статусом процесса):
Активен (R=Running) – процесс находится в очереди на выполнение, то есть либо выполняется в данный момент, либо ожидает выделения ему очередного кванта времени центрального процессора.
«Спит» (S=Sleeping) – процесс находится в состоянии прерываемого ожидания, то есть ожидает какого-то события, сигнала или освобождения нужного ресурса.
Находится в состоянии непрерываемого ожидания (D=Direct) – процесс ожидает определенного («прямого») сигнала от аппаратной части и не реагирует на другие сигналы;
Приостановлен (T) – процесс находится в режиме трассировки (обычно такое состояние возникает при отладке программ).
«Зомби» (Z=Zombie) – это процесс, выполнение которого завершилось, но относящиеся к нему структуры ядра по каким-то причинам не освобождены. Одной из причин их появления в системе может быть следующая ситуация: обычно освобождение структур ядра, относящихся к процессу, выполняет процесс-родитель после получения от потомка сигнала о завершении. Но бывают случаи, когда родительский процесс завершается раньше дочернего.
Процессы, не имеющие родителя, называются "сиротами". "Сироты" автоматически усыновляются процессом init, который и принимает сигналы об их завершении. Если процесс-родитель или init по каким-то причинам не может принять сигнал о завершении дочернего процесса, то процесс-потомок превращается в "зомби" и получает статус Z. Процессы-зомби не занимают процессорного времени (т. е. их выполнение прекращается), но соответствующие им структуры ядра не освобождаются. В некотором смысле это «мертвые» процессы. Уничтожение таких процессов — одна из обязанностей системного администратора.
Хочу отметить, что появление данных процессов говорит о том, что в системе что-то не в порядке, и скорее всего не в порядке с аппаратной частью, так что берем memtest и MHDD и тестируем-тестируем. Не исключен вариант и кривого кода программы.
Так же, говоря о процессах в Linux, можно выделить особый вид процессов - демоны. Данный вид процессов работает в фоне (подобно службам в Windows), без терминала и выполняет задачи для других процессов. Данный вид процессов на серверных системах является основным.
Т.к. в большинстве случаев, демоны в Linux простаивают и ожидают поступления каких-либо данных, соответственно, нужны относительно редко, так что держать их в памяти постоянно загруженными и расходовать на это ресурсы системы нерационально. Для организации работы демонов придуман демон inetd или его более защищенная модификация xinetd (eXtended InterNET Daemon или расширенный Интернет демон). В функции inetd (Xinetd) можно выделить:
установление ограничений на количество запускаемых серверов (служб, демонов)
наблюдение за соединениями на определенных портах и обработка входящих запросов
ограничение доступа к сервисам на основе ACL (списков контроля доступа)
Все процессы в системе, не важно Linux это или другая ОС, обмениваются между собой какой-либо информацией. Отсюда можно задать вопрос, а как же происходит межпроцессный обмен?
В ОС LINUX существует несколько видов межпроцессного обмена, а точнее сказать средств межпроцессного взаимодействия (Interprocess Communication - IPC), которые можно разбить на несколько уровней:
pipe (они же конвейеры, так же неименованные каналы), о них я много рассказывал в прошлом посте, примером можно привести: команда1 | команда2. По сути, pipe использует stdin, stdout и stderr.
Именованные каналы (FIFO: First In First Out). Данный вид канала создаётся с помощью mknod или mkfifo, и два различных процесса могут обратиться к нему по имени. Пример работы с fifo:
В первом терминале (создаем именованный канал в виде файла pipe и из канала направляем данные с помощью конвейера в архиватор):
Во втором терминале (отправляем в именованный канал данные):
В результате это приведет к сжатию передаваемых данных gzip-ом
с терминала, нажатием специальных клавиш или комбинаций (например, нажатие Ctrl-C генерирует SIGINT, а Ctrl-Z SIGTSTP);
ядром системы:
при возникновении аппаратных исключений (недопустимых инструкций, нарушениях при обращении в память, системных сбоях и т. п.);
ошибочных системных вызовах;
для информирования о событиях ввода-вывода;
одним процессом другому (или самому себе), с помощью системного вызова kill(), в том числе:
из шелла, утилитой /bin/kill.
Сигнал — это асинхронное уведомление процесса о каком-либо событии. Когда сигнал послан процессу, операционная система прерывает выполнение процесса. Если процесс установил собственный обработчик сигнала, операционная система запускает этот обработчик, передав ему информацию о сигнале. Если процесс не установил обработчик, то выполняется обработчик по умолчанию. Все сигналы начинаются на «SIG…» и имеют числовые соответствия, определяемые в заголовочном файле signal.h. Числовые значения сигналов могут меняться от системы к системе, хотя основная их часть имеет в разных системах одни и те же значения. Утилита kill позволяет задавать сигнал как числом, так и символьным обозначением.
Разделяемую память применяют для того, чтобы увеличить скорость прохождения данных между процессами. В обычной ситуации обмен информацией между процессами проходит через ядро. Техника разделяемой памяти позволяет осуществить обмен информацией не через ядро, а используя некоторую часть виртуального адресного пространства, куда помещаются и откуда считываются данные.
После создания разделяемого сегмента памяти любой из пользовательских процессов может подсоединить его к своему собственному виртуальному пространству и работать с ним, как с обычным сегментом памяти.
В общих чертах обмен сообщениями выглядит примерно так: один процесс помещает сообщение в очередь посредством неких системных вызовов, а любой другой процесс может прочитать его оттуда, при условии, что и процесс-источник сообщения и процесс-приемник сообщения используют один и тот же ключ для получения доступа к очереди.
RPC — разновидность технологий, которая позволяет компьютерным программам вызывать функции или процедуры в другом адресном пространстве (как правило, на удалённых компьютерах). Обычно, реализация RPC технологии включает в себя два компонента: сетевой протокол (чаще TCP и UDP, реже HTTP) для обмена в режиме клиент-сервер и язык сериализации объектов (или структур, для необъектных RPC).
Сокеты UNIX бывают 2х типов: локальные и сетевые. При использовании локального сокета, ему присваивается UNIX-адрес и просто будет создан специальный файл (файл сокета) по заданному пути, через который смогут сообщаться любые локальные процессы путём простого чтения/записи из него. Сокеты представляют собой виртуальный объект, который существует, пока на него ссылается хотя бы один из процессов. При использовании сетевого сокета, создается абстрактный объект привязанный к слушающему порту операционной системы и сетевому интерфейсу, ему присваивается INET-адрес, который имеет адрес интерфейса и слушающего порта.
Обычно - пакеты программного обеспечения, которые реализуют промежуточный слой между системной платформой и приложением. Эти пакеты предназначены для переноса уже испытанных протоколов коммуникации приложения на более новую архитектуру. Примером можно привести: DIPC, MPI и др.
Итак. Подведем маленький итог:
В Linux есть процессы,
каждый процесс может запускать подпроцессы (нити),
создание нового процесса создается клонированием исходного,
прородителем всех процессов в системе является процесс init, запускаемый ядром системы при загрузке.
процессы взаимодействуют между собой по средствам можпроцессного взаимодействия:
каналы
сигналы
сокеты
разделяемая память
каждый процесс обладает свойствами (читай: обладает следующим контекстом):
PID - идентификатор процесса
PPID - идентификатор процесса, породившего данный
UID и GID - идентификаторы прав процесса (соответствует UID и GID пользователя, от которого запущен процесс)
приоритет процесса
состояние процесса (выполнение, сон и т.п.)
так же у процесса есть таблица открытых (используемых) файлов
Далее поговорим о том, как посмотреть состояние процессов в Linux и о том, как же ими управлять.
Перед тем как управлять процессами, нужно научиться получать о процессах необходимую информацию. В Linux существует псевдофайловая система procfs, которая в большинстве дистрибутивов монтируется в общую ФС в каталог /proc. У данной файловой системы нет физического места размещения, нет блочного устройства, такое как жесткий диск. Вся информация, хранимая в данном каталоге находится в оперативной памяти компьютера, контролируется ядром ОС и она не предназначена для хранения файлов пользователя. О структуре данного каталога я написал в статье о файловой системе Linux. В этой файловой системе дано достаточно много информации, чтобы узнать о процессах и о системе в целом.
Но пользоваться данным каталогом очень не удобно, чтобы узнать о каком-либо процессе информацию, придется просмотреть кучу файлов и каталогов. Чтобы избавиться от ненужного труда, можно использовать существующие утилиты ps и top для просмотра информации о процессах.
Чтобы получить список всех процессов, достаточно ввести команду:
# ps aux
Прокомментируем некоторые интересные моменты. Можно заметить, что некоторые процессы указаны в квадратных скобках [ ] – это процессы, которые входят непосредственно в состав ядра и выполняют важные системные задачи, например, такие как управление буферным кэшем [pdflush] и организацией свопинга [kswapd]. С ними лучше не экспериментировать – ничего хорошего из этого не выйдет :). Остальная часть процессов относится к пользовательским.
Какую информацию можно получить по каждому процессу (комментарии к некоторым полям):
PID, PPID – идентификатор процесса и его родителя.
%CPU – доля процессорного времени, выделенная процессу.
%MEM – процент используемой оперативной памяти.
VSZ – виртуальный размер процесса.
TTY – управляющий терминал.
STAT– статус процесса:
R – выполняется;
S – спит;
Z – зомби;
< – Повышенный приоритет;
+ – Находится в интерактивном режиме.
START – время запуска.
TIME – время исполнения на процессоре.
Команда ps делает моментальный снимок процессов в текущий момент. В отличии от нее, команда top - динамически выводит состояние процессов и их активность в реальном режиме времени.
Пример вывода команды top:
В верхней части вывода отображается астрономическое время, время, прошедшее с момента запуска системы, число пользователей в системе, число запущенных процессов и число процессов, находящихся в разных состояниях, данные об использовании ЦПУ, памяти и свопа. А далее идет таблица, характеризующая отдельные процессы. Число строк, отображаемых в этой таблице, определяется размером окна: сколько строк помещается, столько и выводится.
Содержимое окна обновляется каждые 5 секунд. Список процессов может быть отсортирован по используемому времени ЦПУ (по умолчанию), по использованию памяти, по PID, по времени исполнения. Переключать режимы отображения можно с помощью следующих клавиатурных команд:
<Shift>+<N> — сортировка по PID;
<Shift>+<A> — сортировать процессы по возрасту;
<Shift>+<P> — сортировать процессы по использованию ЦПУ;
<Shift>+<M> — сортировать процессы по использованию памяти;
<Shift>+<T> — сортировка по времени выполнения.
С помощью команды <K> можно завершить некоторый процесс (его PID будет запрошен), а с помощью команды <R> можно переопределить значение nice для некоторого процесса.
Полезную информацию, так же, позволяет получить программа lsof, которая выдает список всех файлов, используемых сейчас процессами, включая каталоги, занятые потому, что какой-либо процесс использует их в качестве текущего или корневого; разделяемые библиотеки, загруженные в память; и т. д.
Итак, теперь об управлении процессами.
Каждому процессу при запуске устанавливается определенный приоритет, который имеет значение от -20 до +20, где +20 - самый низкий. Приоритет нового процесса равен приоритету процесса-родителя. Для изменения приоритета запускаемой программы существует утилита nice. Пример ее использования:
где adnice — значение (от –20 до +19), добавляемое к значению nice процесса-родителя. Отрицательные значения может устанавливать только суперпользователь. Если опция adnice не задана, то по умолчанию для процесса-потомка устанавливается значение nice, увеличенное на 10 по сравнению со значением nice родительского процесса.
Команда renice служит для изменения значения nice для уже выполняющихся процессов. Суперпользователь может изменить приоритет любого процесса в системе. Другие пользователи могут изменять значение приоритета только для тех процессов, для которых данный пользователь является владельцем. При этом обычный пользователь может только уменьшить значение приоритета. Поэтому процессы с низким приоритетом не могут породить "высокоприоритетных детей".
Как я уже писал, одним из средств управления процессами являются сигналы. Некоторые сигналы можно сгенерировать с помощью определенных комбинаций клавиш, но такие комбинации существуют не для всех сигналов. Зато имеется команда kill, которая позволяет послать заданному процессу (указав его PID) любой сигнал:
где SIG — это номер сигнала или наименование сигнала, причем если указание сигнала опущено, то посылается сигнал 15 (SIGTERM — программное завершение процесса). Часто используется сигнал 9 (KILL), с помощью которого суперпользователь может завершить любой процесс. Но сигнал этот очень "грубый", если можно так выразиться, потому что он просто «убивает» процесс, не давая ему времени на корректное сохранение всех обработанных данных. Поэтому в большинстве случаев рекомендуется использовать сигналы TERM или QUIT, которые завершают процесс более "мягко". Если процессу необходимо как-то по-особенному реагировать на сигнал, он может зарегистрировать обработчик, а если обработчика нет, за него отреагирует система.
Два сигнала – 9 ( KILL ) и 19 ( STOP ) – всегда обрабатывает система. Первый из них нужен для того, чтобы убить процесс наверняка (отсюда и название). Сигнал STOP приостанавливает процесс: в таком состоянии процесс не удаляется из таблицы процессов, но и не выполняется до тех пор, пока не получит сигнал 18 ( CONT) – после чего продолжит работу. В Linux сигнал STOP можно передать активному процессу с помощью управляющего символа " ^Z ".
Обычные пользователи могут посылать сигналы только тем процессам, для которых они являются владельцами. Если в команде kill воспользоваться идентификатором процесса (PID), равным -1, то указанный в команде сигнал будет послан всем принадлежащим данному пользователю процессам. Суперпользователь root может посылать сигналы любым процессам. Когда суперпользователь посылает сигнал идентификатору -1, он рассылается всем процессам, за исключением системных. Если этим сигналом будет SIGKILL, то у простых пользователей будут потеряны все открытые ими, но не сохраненные файлы данных.
При обычном запуске процесс работает на переднем плане, то есть процесс "привязывается" к терминалу, с которого он запущен, воспринимая ввод с этого терминала и осуществляя на него вывод. Но можно запустить процесс в фоновом режиме, когда он не связан с терминалом, для чего в конце командной строки запуска программы добавляют символ &.
В оболочке bash имеются две встроенные команды, которые служат для перевода процессов на передний план или возврата их в фоновый режим. Команда fg переводит указанный в аргументе процесс на передний план, а команда bg — переводит процесс в фоновый режим. Одной командой bg можно перевести в фоновый режим сразу несколько процессов, а вот возвращать их на передний план необходимо по одному. Аргументами команд fg и bg могут являться только номера заданий, запущенных из текущего экземпляра shell. Возможные значения заданий можно увидеть, выполнив команду jobs.
При завершении сессии оболочка посылает всем порожденным ею процессам сигнал "отбой", по которому порожденные ею процессы могут завершиться, что не всегда желательно. Если вы хотите запустить в фоновом режиме программу, которая должна выполняться и после вашего выхода из оболочки, то ее нужно запускать с помощью утилиты nohup:
Запущенный таким образом процесс будет игнорировать посылаемые ему сигналы (не игнорируются только сигналы SIGHUP и SIGQUIT). Хочу так же выделить команду pstree, которая показывает дерево процессов. Очень наглядно, кстати.