- Все, что вам нужно знать о процессах в Linux
- Типы процессов
- Что такое демоны
- Создание процессов в Linux
- Как Linux идентифицирует процессы?
- Процесс Init
- Запуск процессов в Linux
- Фоновые процессы в Linux
- Состояние процесса в Linux
- Как просмотреть активные процессы в Linux
- 1. Команда ps
- 2. top – утилита системного мониторинга
- 3. glances – утилита системного мониторинга
- Управление процессами в Linux
- Отправка сигналов процессу
- Изменение приоритета процесса
- Изучаем процессы в Linux
- Содержание
- Введение
- Атрибуты процесса
- Жизненный цикл процесса
- Рождение процесса
- Состояние «готов»
- Состояние «выполняется»
- Перерождение в другую программу
- Состояние «ожидает»
- Состояние «остановлен»
- Завершение процесса
- Состояние «зомби»
- Забытье
- Благодарности
Все, что вам нужно знать о процессах в Linux
Оригинал: All You Need To Know About Processes in Linux [Comprehensive Guide]
Автор: Aaron Kili
Дата публикации: 31 марта 2017 года
Перевод: А. Кривошей
Дата перевода: август 2017 г.
В этой статье мы дадим базовое понимание процессов и кратко рассмотрим управление процессами в Linux с помощью специальных команд.
Процесс относится к выполнению программы — он представляет собой запущенный экземпляр программы, составленный из инструкций, данных, считанных из файлов, других программ, или полученных от пользователя.
Типы процессов
В Linux есть два основных типа процессов:
Процессы переднего плана (также известны как интерактивные процессы) — они инициализируются и контролируются в терминальной сессии. Другими словами, для запуска таких процессов в системе должен находиться пользователь, они не запускаются автоматически как часть системных служб.
Фоновые процессы (также известны как неинтерактивные/автоматические процессы) — не подключены к терминалу. Они не ждут ввода от пользователя.
Что такое демоны
Это специальные типы фоновых процессов, которые запускаются при загрузке системы и остаются запущенными в виде служб, они не завершаются. Демоны запускаются как системные задачи, спонтанно. Тем не менее, пользователь может контролировать их через процесс init.
Создание процессов в Linux
Обычно новый процесс создается уже существующим процессом, который делает в памяти свою точную копию. Дочерний процесс получает то же окружение, что и его родительский процесс, отличается только номер ID.
Есть два распространенных способа создания нового процесса в Linux:
1. С помощью функции System(). Этот способ сравнительно прост, однако неэффективен и создает определенные риски с точки зрения безопасности.
2. С помощью функций fork() и exec() — более продвинутая техника с точки зрения гибкости, скорости и безопасности.
Как Linux идентифицирует процессы?
Поскольку Linux — многопользовательская система, и различные пользователи могут одновременно запускать разные программы, каждый запущенный экземпляр программы должен получать уникальный идентификатор от ядра системы.
Программы идентифицируются по ID процесса (PID), а также по ID родительского процесса (PPID), поэтому процессы можно разделить на следующие категории:
Родительские процессы — это процессы, которые в процессе работы создают другие процессы.
Дочерние процессы — это процессы, созданные другими процессами.
Процесс Init
Процесс Init — это родительский процесс для всех процессов в системе, это первая программа, которая исполняется при загрузке системы Linux; он управляет всеми другими процессами в системе. Init запускается непосредственно ядром системы, поэтому он в принципе не имеет родительского процесса.
Процесс Init всегда получает ID 1. Он функционирует как приемный родитель для всех осиротевших процессов.
Для определения ID процесса можно использовать команду pidof:
Найти ID процесса и ID родительского процесса для системной оболочки можно с помощью команд:
Запуск процессов в Linux
При старте команды или программы (например cloudcmd – CloudCommander), она запускает процесс в системе. Вы можете запустить процесс переднего плана (интерактивный), как показано ниже, он подключится к терминалу и пользователь сможет взаимодействовать с ним:
Фоновые процессы в Linux
Для запуска фонового процесса (неинтерактивного) используется символ &, при этом процесс не сможет читать ввод от пользователя, пока не будет перемещен на передний план.
Вы также можете отправить процесс на задний план, приостановив его с помощью [Ctrl + Z], это отправит сигнал SIGSTOP процессу, тем самым прекратив его работу; он простаивает:
Для продолжения выполнения приостановленного в фоне процесса, используется команда bg:
Для перевода процесса из фонового режима на передний план используется команда fg вместе с ID:
Состояние процесса в Linux
В зависимости от различных обстоятельств состояние процесса во время работы может меняться. В Linux процесс может находиться в следующих состояниях:
Running (работа) — процесс работает (он является текущим процессом в системе) или готов к работе (ждет выделения ресурсов процессора).
Waiting (ожидание) — в этом состоянии процесс ждет события, которое должно запустить его, или выделения системных ресурсов.
Кроме того, ядро системы делит процессы в состоянии ожидания на два типа: перываемые процессы, состояние ожидания которых может быть прервано сигналом, и непрерываемые, состояние ожидания которых может быть прервано только аппаратным способом.
Stopped (остановка) — в этом состоянии процесс останавливает работу, обычно после получения соответствующего сигнала. Например, процесс может быть остановлен для отладки.
Zombie (зомби) — процесс мертв, то есть он был остановлен, но в системе осталась выполняемая им задача.
Как просмотреть активные процессы в Linux
В Linux есть несколько утилит для просмотра запущенных в системе процессов, наиболее широко известны команды ps и top:
1. Команда ps
Она выводит информацию о выбранных активных процессах, как показано ниже.
2. top – утилита системного мониторинга
top — это мощная утилита, которая позволяет в режиме реального времени просматривать список запущенных процессов, как показано ниже:
3. glances – утилита системного мониторинга
glances — это сравнительно новая утилита для мониторинга активности системы с продвинутыми возможностями:
Есть также еще несколько полезных программ, которые вы можете использовать для просмотра списка активных процессов, почитать о них можно по ссылкам ниже.
Управление процессами в Linux
В Linux также имеются команды для управления процессами, например kill, pkill, pgrep и killall. Ниже приведено несколько примеров их использования:
Если вы хотите подробно изучить использование этих команд, информация по ссылкам ниже.
Обратите внимание, что с их помощью вы можете завршать зависшие приложения, которые тормозят вашу систему.
Отправка сигналов процессу
Фундаментальный способ управления процессами в Linux — это отправка им сигналов, которых имеется достаточно много. Посмотреть список всех сигналов можно с помощью команды:
Для отправки сигналов процессу используются описанные выше команды kill, pkill или pgrep. Однако программа ответит на сигнал, только если она запрограммирована распознавать такой сигнал.
Большинство сигналов предназначены для использования системой или программистами при написании кода. Следующие сигналы могут быть полезны пользователю:
SIGHUP 1 – отправляется процессу при закрытии контролирующего его терминала.
SIGINT 2 – отправляется процессу контролирующим его терминалом, если пользователь прерывает работу процесса клавишами [Ctrl+C].
SIGQUIT 3 – отправляется процессу, если пользователь посылает сигнал выхода из программы [Ctrl+D].
SIGKILL 9 – этот сигнал немедленно завершает (убивает) процесс без выполнения любых операций очистки файлов, логов и т.д.
SIGTERM 15 – это сигнал завершения работы программы (он по умоланию отправляется командой kill).
SIGTSTP 20 – отправляется процессу контролирующим его терминалом с запросом на остановку (terminal stop); инициируется при нажатии [Ctrl+Z].
Ниже приведены примеры использования команд kill для завершения работы Firefox при его зависании с использованием PID:
Для завершения программы с использованием ее названия используются команды pkill или killall:
Изменение приоритета процесса
В Linux все активные процессы имеют определенное значение приоритета (nice). Процессы с более высоким приоритетом обычно получают больше процессорного времени, чем процессы с более низким приоритетом.
Однако пользователь с привилегиями root может менять приоритет с помощью команд nice и renice.
В выводе команды top столбец NI отображает значения nice для процессов.
Вы можете использовать команду nice, чтобы задать значение nice процесса. Не забывайте, что обычный пользователь может присвоить процессу значение nice от 0 до 20, только если это процесс ему принадлежит.
Отрицательные значения nice может использовать только пользователь root.
Для понижения приоритета процесса используется команда renice:
Другие статьи об управлении процессами в Linux вы найдете на странице «Процессы в Linux-системе».
Источник
Изучаем процессы в Linux
В этой статье я хотел бы рассказать о том, какой жизненный путь проходят процессы в семействе ОС Linux. В теории и на примерах я рассмотрю как процессы рождаются и умирают, немного расскажу о механике системных вызовов и сигналов.
Данная статья в большей мере рассчитана на новичков в системном программировании и тех, кто просто хочет узнать немного больше о том, как работают процессы в Linux.
Всё написанное ниже справедливо к Debian Linux с ядром 4.15.0.
Содержание
Введение
Системное программное обеспечение взаимодействует с ядром системы посредством специальных функций — системных вызовов. В редких случаях существует альтернативный API, например, procfs или sysfs, выполненные в виде виртуальных файловых систем.
Атрибуты процесса
Процесс в ядре представляется просто как структура с множеством полей (определение структуры можно прочитать здесь).
Но так как статья посвящена системному программированию, а не разработке ядра, то несколько абстрагируемся и просто акцентируем внимание на важных для нас полях процесса:
- Идентификатор процесса (pid)
- Открытые файловые дескрипторы (fd)
- Обработчики сигналов (signal handler)
- Текущий рабочий каталог (cwd)
- Переменные окружения (environ)
- Код возврата
Жизненный цикл процесса
Рождение процесса
Только один процесс в системе рождается особенным способом — init — он порождается непосредственно ядром. Все остальные процессы появляются путём дублирования текущего процесса с помощью системного вызова fork(2) . После выполнения fork(2) получаем два практически идентичных процесса за исключением следующих пунктов:
- fork(2) возвращает родителю PID ребёнка, ребёнку возвращается 0;
- У ребёнка меняется PPID (Parent Process Id) на PID родителя.
После выполнения fork(2) все ресурсы дочернего процесса — это копия ресурсов родителя. Копировать процесс со всеми выделенными страницами памяти — дело дорогое, поэтому в ядре Linux используется технология Copy-On-Write.
Все страницы памяти родителя помечаются как read-only и становятся доступны и родителю, и ребёнку. Как только один из процессов изменяет данные на определённой странице, эта страница не изменяется, а копируется и изменяется уже копия. Оригинал при этом «отвязывается» от данного процесса. Как только read-only оригинал остаётся «привязанным» к одному процессу, странице вновь назначается статус read-write.
Состояние «готов»
Сразу после выполнения fork(2) переходит в состояние «готов».
Фактически, процесс стоит в очереди и ждёт, когда планировщик (scheduler) в ядре даст процессу выполняться на процессоре.
Состояние «выполняется»
Как только планировщик поставил процесс на выполнение, началось состояние «выполняется». Процесс может выполняться весь предложенный промежуток (квант) времени, а может уступить место другим процессам, воспользовавшись системным вывозом sched_yield .
Перерождение в другую программу
В некоторых программах реализована логика, в которой родительский процесс создает дочерний для решения какой-либо задачи. Ребёнок в данном случае решает какую-то конкретную проблему, а родитель лишь делегирует своим детям задачи. Например, веб-сервер при входящем подключении создаёт ребёнка и передаёт обработку подключения ему.
Однако, если нужно запустить другую программу, то необходимо прибегнуть к системному вызову execve(2) :
или библиотечным вызовам execl(3), execlp(3), execle(3), execv(3), execvp(3), execvpe(3) :
Все из перечисленных вызовов выполняют программу, путь до которой указан в первом аргументе. В случае успеха управление передаётся загруженной программе и в исходную уже не возвращается. При этом у загруженной программы остаются все поля структуры процесса, кроме файловых дескрипторов, помеченных как O_CLOEXEC , они закроются.
Как не путаться во всех этих вызовах и выбирать нужный? Достаточно постичь логику именования:
- Все вызовы начинаются с exec
- Пятая буква определяет вид передачи аргументов:
- l обозначает list, все параметры передаются как arg1, arg2, . NULL
- v обозначает vector, все параметры передаются в нуль-терминированном массиве;
- Далее может следовать буква p, которая обозначает path. Если аргумент file начинается с символа, отличного от «/», то указанный file ищется в каталогах, перечисленных в переменной окружения PATH
- Последней может быть буква e, обозначающая environ. В таких вызовах последним аргументом идёт нуль-терминированный массив нуль-терминированных строк вида key=value — переменные окружения, которые будут переданы новой программе.
Семейство вызовов exec* позволяет запускать скрипты с правами на исполнение и начинающиеся с последовательности шебанг (#!).
Есть соглашение, которое подразумевает, что argv[0] совпадает с нулевым аргументов для функций семейства exec*. Однако, это можно нарушить.
Любопытный читатель может заметить, что в сигнатуре функции int main(int argc, char* argv[]) есть число — количество аргументов, но в семействе функций exec* ничего такого не передаётся. Почему? Потому что при запуске программы управление передаётся не сразу в main. Перед этим выполняются некоторые действия, определённые glibc, в том числе подсчёт argc.
Состояние «ожидает»
Некоторые системные вызовы могут выполняться долго, например, ввод-вывод. В таких случаях процесс переходит в состояние «ожидает». Как только системный вызов будет выполнен, ядро переведёт процесс в состояние «готов».
В Linux так же существует состояние «ожидает», в котором процесс не реагирует на сигналы прерывания. В этом состоянии процесс становится «неубиваемым», а все пришедшие сигналы встают в очередь до тех пор, пока процесс не выйдет из этого состояния.
Ядро само выбирает, в какое из состояний перевести процесс. Чаще всего в состояние «ожидает (без прерываний)» попадают процессы, которые запрашивают ввод-вывод. Особенно заметно это при использовании удалённого диска (NFS) с не очень быстрым интернетом.
Состояние «остановлен»
В любой момент можно приостановить выполнение процесса, отправив ему сигнал SIGSTOP. Процесс перейдёт в состояние «остановлен» и будет находиться там до тех пор, пока ему не придёт сигнал продолжать работу (SIGCONT) или умереть (SIGKILL). Остальные сигналы будут поставлены в очередь.
Завершение процесса
Ни одна программа не умеет завершаться сама. Они могут лишь попросить систему об этом с помощью системного вызова _exit или быть завершенными системой из-за ошибки. Даже когда возвращаешь число из main() , всё равно неявно вызывается _exit .
Хотя аргумент системного вызова принимает значение типа int, в качестве кода возврата берется лишь младший байт числа.
Состояние «зомби»
Сразу после того, как процесс завершился (неважно, корректно или нет), ядро записывает информацию о том, как завершился процесс и переводит его в состояние «зомби». Иными словами, зомби — это завершившийся процесс, но память о нём всё ещё хранится в ядре.
Более того, это второе состояние, в котором процесс может смело игнорировать сигнал SIGKILL, ведь что мертво не может умереть ещё раз.
Забытье
Код возврата и причина завершения процесса всё ещё хранится в ядре и её нужно оттуда забрать. Для этого можно воспользоваться соответствующими системными вызовами:
Вся информация о завершении процесса влезает в тип данных int. Для получения кода возврата и причины завершения программы используются макросы, описанные в man-странице waitpid(2) .
Передача argv[0] как NULL приводит к падению.
Бывают случаи, при которых родитель завершается раньше, чем ребёнок. В таких случаях родителем ребёнка станет init и он применит вызов wait(2) , когда придёт время.
После того, как родитель забрал информацию о смерти ребёнка, ядро стирает всю информацию о ребёнке, чтобы на его место вскоре пришёл другой процесс.
Благодарности
Спасибо Саше «Al» за редактуру и помощь в оформлении;
Спасибо Саше «Reisse» за понятные ответы на сложные вопросы.
Они стойко перенесли напавшее на меня вдохновение и напавший на них шквал моих вопросов.
Источник