Изучаем процессы в Linux. Все, что вам нужно знать о процессах в Linux Debian процессы

Жаропонижающие средства для детей назначаются педиатром. Но бывают ситуации неотложной помощи при лихорадке, когда ребенку нужно дать лекарство немедленно. Тогда родители берут на себя ответственность и применяют жаропонижающие препараты. Что разрешено давать детям грудного возраста? Чем можно сбить температуру у детей постарше? Какие лекарства самые безопасные?

Чтобы эффективно управлять процессами и сервисами ОС Linux необходимо хорошо понимать, что представляет собой процесс с точки зрения ОС, и каким образом в ОС происходит взаимодействие между различными типами процессов.

Процесс - это объект ОС Linux, который состоит из адресного пространства памяти и набора структур данных. По сути, процесс это запущенная программа или служба.

Каждый запущенный процесс в ОС Linux может породить дополнительные процессы. Процесс, запустивший новый процесс называется родительским процессом. Новый процесс по отношению к создавшему его процессу называется дочерним.

Процессы - это не то же самое, что задачи: процессы являются частью операционной системы, тогда как о задачах известно только командному процессору, в котором они выполняются. Работающая программа заключает в себе один или более процессов; задача состоит из одной или более программ, выполняемых в виде команд командного процессора.

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

* Идентификатор процесса (PID) . Каждый процесс в системе имеет уникальный идентификатор. Каждый новый запущенный процесс получает номер на единицу больше предыдущего.

* Идентификатор родительского процесса (PPID) . Данный атрибут процесс получает во время своего запуска и используется для получения статуса родительского процесса.

* Реальный и эффективный идентификаторы пользователя (UID,EUID) и группы (GID, EGID) . Данные атрибуты процесса говорят о его принадлежности к конкретному пользователю и группе. Реальные идентификаторы совпадают с идентификаторами пользователя, который запустил процесс, и группы, к которой он принадлежит. Эффективные - от чьего имени был запущен процесс. Права доступа процесса к ресурсам ОС Linux эффективными идентификаторами. Если на исполняемом файле программы установлен специальный бит SGID или SUID, то процесс данной программы будет обладать правами доступа владельца исполняемого файла.
Для управления процессом (например, kill) используются реальные идентификаторы. Все идентификаторы передаются от родительского процесса к дочернему.
Для просмотра данных атрибутов можно воспользоваться командой , задав желаемый формат отображения колонок.

* Приоритет или динамический приоритет (priority) и относительный или статический (nice) приоритет процесса .
Статический приоритет или nice-приоритет лежит в диапазоне от -20 до 19, по умолчанию используется значение 0. Значение –20 соответствует наиболее высокому приоритету, nice-приоритет не изменяется планировщиком, он наследуется от родителя или его указывает пользователь. Динамический приоритет используется планировщиком для планирования выполнения процессов. Этот приоритет хранится в поле prio структуры task_struct процесса. Динамический приоритет вычисляется исходя из значения параметра пicе для данной задачи путем вычисления надбавки или штрафа, в зависимости от интерактивности задачи. Пользователь имеет возможность изменять только статический приоритет процесса. При этом повышать приоритет может только root. В ОС Linux существуют две команды управления приоритетом процессов: nice и renice.

* Состояние процесса. В ОС Linux каждый процесс обязательно находится в одном из перечисленных ниже состояний и может быть переведен из одного состояния в другое системой или командами пользователя. Различают следующее состояния процессов:

TASK_RUNNING - процесс готов к выполнению или выполняется (runnable). Обозначается символом R.

TASK_INTERRUPTIBLE - ожидающий процесс (sleeping). Данное состояние означает, что процесс инициализировал выполнение
какой-либо системной операции и ожидает ее завершения. К таким операциям относятся ввод/вывод, завершение дочернего процесса
и т.д. Процессы с таким состоянием обозначаются символом S.

TASK_STOPPED - выполнение процесса остановлено (stopping). Любой процесс можно остановить. Это может делать как система,
так и пользователь. Состояние такого процесса обозначается символом Т.

TASK_ZOMBIE - завершившийся процесс (zombie). Процессы данного состояния возникают в случае, когда родительский процесс
не ожидая завершения дочернего процесса, продолжает параллельно работать. Процессы с таким состоянием обозначаются символом Z.
Завершившиеся процессы больше не выполняются системой, но по-прежнему продолжают потреблять ее не вычислительные ресурсы.

TASK_UNINTERRUPTIBLE -непрерываемый процесс (uninterruptible). Процессы в данном состоянии ожидают завершения операции
ввода - вывода с прямым доступом в память. Такой процесс нельзя завершить, пока не завершится операция ввода/вывода.
Процессы с таким состоянием обозначаются символом D. Состояние аналогично TASK_INTERRUPTIBLE, за исключением того, что процесс
не возобновляет выполнение при получении сигнала. Используется в случае, когда процесс должен ожидать беспрерывно или
когда ожидается, что некоторое событие может возникать достаточно часто. Так как задача в этом состоянии не отвечает
на сигналы, TASK_UNINTERRUPTIBLE используется менее часто, чем TASK_INTERRUPTIBLE.

Типы процессов В Linux процессы делятся на три типа:

  • Системные процессы - являются частью ядра и всегда расположены в оперативной памяти. Системные процессы не имеют соответствующих им программ в виде исполняемых файлов и запускаются при инициализации ядра системы. Выполняемые инструкции и данные этих процессов находятся в ядре системы, таким образом, они могут вызывать функции и обращаться к данным, недоступным для остальных процессов. Системными процессами, например, являются: shed (диспетчер свопинга), vhand (диспетчер страничного замещения), kmadaemon (диспетчер памяти ядра).
  • Демоны - это неинтерактивные процессы, которые запускаются обычным образом - путем загрузки в память соответствующих им программ (исполняемых файлов), и выполняются в фоновом режиме. Обычно демоны запускаются при инициализации системы (но после инициализации ядра) и обеспечивают работу различных подсистем: системы терминального доступа, системы печати, с истемы сетевого доступа и сетевых услуг, почтовый сервер, dhcp-сервер и т. п. Демоны не связаны ни с одним пользовательским сеансом работы и не могут непосредственно управляться пользователем. Большую часть времени демоны ожидают пока тот или иной процесс запросит определенную услугу, например, доступ к файловому архиву или печать документа.
  • Прикладные (пользовательские) процессы . К прикладным процессам относятся все остальные процессы, выполняющиеся в системе. Как правило, это процессы, порожденные в рамках пользовательского сеанса работы. Например, команда ls породит соответствующий процесс этого типа. Важнейшим прикладным процессом является командный интерпретатор (shell), который обеспечивает вашу работу в LINUX. Он запускается сразу же после регистрации в системе. Прикладные процессы linux могут выполняться как в интерактивном, так и в фоновом режиме, но в любом случае время их жизни (и выполнения) ограничено сеансом работы пользователя. При выходе из системы все прикладные процессы будут уничтожены.

Иерархия процессов

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

Рис. 3.1 – Фрагмент иерархии процессов

На последней фазе загрузки ядро монтирует корневую файловую систему и формирует среду выполнения нулевого процесса, создавая пространство процесса, инициализируя нулевую точку входа в таблице процесса и делая корневой каталог текущим для процесса. Когда формирование среды выполнения процесса заканчивается, система исполняется уже в виде нулевого процесса. Нулевой процесс "ветвится", запуская fork прямо из ядра, поскольку сам процесс исполняется в режиме ядра. Код, исполняемый порожденным процессом 1, включает в себя вызов системной функции exec, запускающей на выполнение программу из файла "/etc/init". В отличие от нулевого процесса, который является процессом системного уровня, выполняющимся в режиме ядра, процесс 1 относится к пользовательскому уровню. Обычно процесс 1 именуется процессом init, поскольку он отвечает за инициализацию новых процессов. На самом деле вы можете поместить любую программу в /sbin/init и ядро запустит её как только закончит загружаться. Задачей init"а является запуск всего остального нужным образом.

Init читает файл /etc/inittab, в котором содержатся инструкции для дальнейшей работы. Первой инструкцией, обычно, является запуск скрипта инициализации. В системах, основанных на Debian, скриптом инициализации будет /etc/init.d/rcS, в Red Hat - /etc/rc.d/rc.sysinit. Это то место где происходит проверка и монтирование файловых систем (/etc/fstab), установка часов системного времени, включение своп-раздела, присвоение имени хоста и т.д. Далее будет вызван следующий скрипт, который переведёт нас на "уровень запуска" по умолчанию. Это подразумевает просто некоторый набор демонов, которые должны быть запущены.

Syslogd (/etc/init.d/syslogd) – скрипт, отвечающий за запуск и остановку системного логгера (система журнальной регистрации событий SYSLOG, позволяет записывать системные сообщения в файлы журналов /var/log).

Xined –Демон Интернет-служб, управляет сервисами для интернета. Демон прослушивает сокеты и если в каком-то из них есть сообщение определяет какому сервису принадлежит данный сокет и вызывает соответствующую программу для обработки запроса.

crond – Демон cron отвечает за просмотр файлов crontab и выполнение, внесенных в него команд в указанное время для опредленного пользователя. Програма crontab(1) спілкується з crond через файл cron.update, який повинен знаходитись разом з рештою файлів каталогу crontab, як правило - /var/spool/cron/crontabs.

Последним важным действием init является запуск некоторого количества getty. Mingetty – виртуальные терминалы, назначением которых является слежение за консолями пользователей.

getty запускает программу login – начало сеанса роботы пользователя в системе. Задача login"а – регистрация пользователя в системе. А уже после успешной регистрации чаще всего грузиться командный интерпретатор пользователя (shell), например, bash, вернее после регистрации пользователя грузится программа, указанная для данного пользователя в файле /etc/passwd (в большинстве случаев это bash).

2. Запуск процессов

Существует два пути запуска процессов в зависимости от типа процесса.

Для пользовательских процессов запуск осуществляется в интерактивном режиме путем ввода произвольной команды или запуска произвольного скрипта. Для системных процессов и демонов используются инициализационные скрипты (init-скрипты). Данные скрипты используется процессом init для запусков других процессов при загрузке ОС. Инициализационные скрипты хранятся в каталоге /etc. В данном каталоге существуют вложенные каталоги, именуемые rcO.d - rc6.d, каждый из которых ассоциирован с определенным уровнем выполнения (runlevel). В каждом из этих каталогов находятся символьные ссылки на инициализационные скрипты, непосредственно находящиеся в каталоге /etc/rc.d/init.d .

Следует заметить, что в каталоге /etc/init.d присутствуют жесткие ссылки на скрипты каталога /etc/rc.d/init.d, поэтому при изменении скриптов в этих каталогах измененные данные отображаются одинаково вне зависимости от пути к файлу скрипта.

Просмотр init-скриптов

Bee init-скрипты возможно повторно запускать или останавливать, тем самым управляя статусом сервиса, к которому они принадлежат. Запуск данных скриптов осуществляется из командной строки и имеет следующий синтаксис:

/etc/init.d/script-name start|stoplrestart|condrestart|status|reload

Здесь в качестве script-name используется конкретное имя init-скрипта, а в качестве аргументов могут выступать следующие значения:

* start (Запуск сервиса); * stop (Остановка сервиса); * restart (Остановка и последующий запуск сервиса); * condrestart (Условная остановка и последующий запуск сервиса); * status (Получение статуса состояния сервиса); * reload (Повторное считывание конфигурационного файла сервиса). Например, для условного перезапуска сервиса sshd используется следующая команда: # /etc/init.d/sshd condrestart Stopping sshd: [ ok ] Starting sshd: [ ok ] Условный перезапуск сервиса sshd.

В случае использования аргумента condrestart перезапуск сервиса будет осуществлен только в том случае, если сервис уже работает в системе.

В ОС Linux для управления сервисами, помимо непосредственного обращения к файлу init-скрипта, существует специальная команда service (второй способ), в качестве аргумента которой необходимо указать аргументы аналогичные тем, что используются при непосредственном запуске демонов через init-скрипты:

# service sshd reload Reloading sshd: [ ok ] Использование команды service.

В данном примере осуществляется повторное считывание конфигурационного файла сервиса sshd.

Однако управлять демонами в большинстве случаем может только root.

Команды запуска процессов

Чтобы завершить какой-нибудь процесс, нужно послать ему сигнал с помощью команды kill. Для этого необходимо узнать Pid процесса с помощью команды ps (например, Pid процесса равен 11839) и послать процессу сигнал на завершение, например сигнал SIGKILL:

или kill –SIGKILL 11839

или kill –KILL 11839

Что же такое сигналы?

Сигналы – это программные прерывания. Сигналы в ОС Linux используются как средства синхронизации и взаимодействия процессов и нитей. Сигнал является сообщением, которое система посылает процессу или один процесс посылает другому. С точки зрения пользователя получение процессом сигнала выглядит как возникновение прерывания. Процесс прекращает свое выполнение, и управление передается механизму обработки сигнала (обработчику). По окончании обработки сигнала процесс может возобновить свое выполнение с той точки, на которой он был прерван.

Прежде всего, каждый сигнал имеет собственное имя и номер. Имена всех сигналов начинаются с последовательности SIG. Например, SIGALRM – генерируется, когда таймер, установленной функцией alarm(), отмерит указанный промежуток времени. Linux поддерживает 31 сигнал (номера от 1 до 31).

Сигналы могут порождаться различными условиями:

1. Генерироваться терминалом, при нажатии определенной комбинации клавиш, например, нажатие Ctrl+C генерирует сигнал SIGINT, таким образом можно прервать выполнение программы, вышедшей из-под контроля;

2. Аппаратные ошибки - деление на 0, ошибка доступа к памяти и прочие – также приводят к генерации сигналов. Эти ошибки обычно обнаруживаются аппаратным обеспечением, которое извещает ядро об их появлении. После этого ядро генерирует соответствующий сигнал и передает его процессу, который выполнялся в момент появления ошибки. Например, сигнал SIGSEGV посылается процессу в случае попытки обращения к неверному адресу в памяти.

3. Другим процессом (в том числе и ядром и системным процессом), выполнившим системный вызов передачи сигнала kill();

4. При выполнении команды kill.

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

В случае получения сигнала процесс может запросить ядро выполнить одну из трех реакции на сигнал:

1. Принудительно проигнорировать сигнал (практически любой сигнал может быть проигнорирован, кроме SIGKILL и SIGSTOP).

2. Произвести обработку сигнала по умолчанию: проигнорировать, остановить процесс, перевести в состояние ожидания до получения другого специального сигнала либо завершить работу.

3. Перехватить сигнал (выполнить обработку сигнала, специфицированную пользователем).

Типы сигналов и способы их возникновения в системе жестко регламентированы. Типы сигналов принято задавать числовыми номерами, в диапазоне от 1 до 31 включительно, но при программировании часто используются символьные имена сигналов, определенные в системных включаемых файлах.

Задача (job) - это просто рабочая единица командного процессора.

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

  • интерактивное задание (foreground job) - Выполняемое в командном процессоре, занимающее сеанс командного процессора, так что вы не можете выполнить другую команду.
  • фоновое задание (background job) - Выполняемое в командном процессоре, но не занимающее сеанс командного процессора, так что вы можете выполнить другую команду в этом же командном процессоре.
  • приостановить (suspend) - Временно приостановить интерактивный процесс.
  • возобновить (resume) - Вернуться к вьполнению приостановленной задачи.

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

Если вы запустили из командного процессора команду в интерактивном режиме и хотите немедленно прекратить выполнение команды , введите Ctrl-С. Командный процессор воспримет нажатие Ctrl-С как "остановить выполнение текущей задачи немедленно". Поэтому, если вы выводите очень длинный файл (скажем, командой cat) и хотите остановить вывод, нажмите Ctrl-С. На самом деле текущей задаче по нажатию Ctrl-С отправится сигнал SIGINT.

Чтобы прекратить выполнение программы, работающей в фоновом режиме, вы можете перевести ее в интерактивный режим с помощью команды fg и затем нажать Ctrl-С, или использовать команду kill.

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

Данная статья в большей мере рассчитана на новичков в системном программировании и тех, кто просто хочет узнать немного больше о том, как работают процессы в Linux.

Всё написанное ниже справедливо к Debian Linux с ядром 4.15.0.

Атрибуты процесса

Процесс в ядре представляется просто как структура с множеством полей (определение структуры можно прочитать ).
Но так как статья посвящена системному программированию, а не разработке ядра, то несколько абстрагируемся и просто акцентируем внимание на важных для нас полях процесса:
  • Идентификатор процесса (pid)
  • Открытые файловые дескрипторы (fd)
  • Обработчики сигналов (signal handler)
  • Текущий рабочий каталог (cwd)
  • Переменные окружения (environ)
  • Код возврата

Жизненный цикл процесса


Рождение процесса

Только один процесс в системе рождается особенным способом - init - он порождается непосредственно ядром. Все остальные процессы появляются путём дублирования текущего процесса с помощью системного вызова fork(2) . После выполнения fork(2) получаем два практически идентичных процесса за исключением следующих пунктов:
  1. fork(2) возвращает родителю PID ребёнка, ребёнку возвращается 0;
  2. У ребёнка меняется PPID (Parent Process Id) на PID родителя.
После выполнения fork(2) все ресурсы дочернего процесса - это копия ресурсов родителя. Копировать процесс со всеми выделенными страницами памяти - дело дорогое, поэтому в ядре Linux используется технология Copy-On-Write.
Все страницы памяти родителя помечаются как read-only и становятся доступны и родителю, и ребёнку. Как только один из процессов изменяет данные на определённой странице, эта страница не изменяется, а копируется и изменяется уже копия. Оригинал при этом «отвязывается» от данного процесса. Как только read-only оригинал остаётся «привязанным» к одному процессу, странице вновь назначается статус read-write.

Пример простой бесполезной программы с fork(2)

#include #include #include #include #include int main() { int pid = fork(); switch(pid) { case -1: perror("fork"); return -1; case 0: // Child printf("my pid = %i, returned pid = %i\n", getpid(), pid); break; default: // Parent printf("my pid = %i, returned pid = %i\n", getpid(), pid); break; } return 0; }

$ gcc test.c && ./a.out my pid = 15594, returned pid = 15595 my pid = 15595, returned pid = 0

Состояние «готов»

Сразу после выполнения fork(2) переходит в состояние «готов».
Фактически, процесс стоит в очереди и ждёт, когда планировщик (scheduler) в ядре даст процессу выполняться на процессоре.

Состояние «выполняется»

Как только планировщик поставил процесс на выполнение, началось состояние «выполняется». Процесс может выполняться весь предложенный промежуток (квант) времени, а может уступить место другим процессам, воспользовавшись системным вывозом sched_yield .

Перерождение в другую программу

В некоторых программах реализована логика, в которой родительский процесс создает дочерний для решения какой-либо задачи. Ребёнок в данном случае решает какую-то конкретную проблему, а родитель лишь делегирует своим детям задачи. Например, веб-сервер при входящем подключении создаёт ребёнка и передаёт обработку подключения ему.
Однако, если нужно запустить другую программу, то необходимо прибегнуть к системному вызову execve(2) :

Int execve(const char *filename, char *const argv, char *const envp);
или библиотечным вызовам execl(3), execlp(3), execle(3), execv(3), execvp(3), execvpe(3) :

Int execl(const char *path, const char *arg, ... /* (char *) NULL */); int execlp(const char *file, const char *arg, ... /* (char *) NULL */); int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp */); int execv(const char *path, char *const argv); int execvp(const char *file, char *const argv); int execvpe(const char *file, char *const argv, char *const envp);
Все из перечисленных вызовов выполняют программу, путь до которой указан в первом аргументе. В случае успеха управление передаётся загруженной программе и в исходную уже не возвращается. При этом у загруженной программы остаются все поля структуры процесса, кроме файловых дескрипторов, помеченных как O_CLOEXEC , они закроются.

Как не путаться во всех этих вызовах и выбирать нужный? Достаточно постичь логику именования:

  • Все вызовы начинаются с exec
  • Пятая буква определяет вид передачи аргументов:
    • l обозначает list , все параметры передаются как arg1, arg2, ..., NULL
    • v обозначает vector , все параметры передаются в нуль-терминированном массиве;
  • Далее может следовать буква p , которая обозначает path . Если аргумент file начинается с символа, отличного от "/", то указанный file ищется в каталогах, перечисленных в переменной окружения PATH
  • Последней может быть буква e , обозначающая environ . В таких вызовах последним аргументом идёт нуль-терминированный массив нуль-терминированных строк вида key=value - переменные окружения, которые будут переданы новой программе.

Пример вызова /bin/cat --help через execve

int main() { char* args = { "/bin/cat", "--help", NULL }; execve("/bin/cat", args, environ); // Unreachable return 1; }

$ gcc test.c && ./a.out Usage: /bin/cat ... ... Concatenate FILE(s) to standard output. *Вывод обрезан*


Семейство вызовов exec* позволяет запускать скрипты с правами на исполнение и начинающиеся с последовательности шебанг (#!).

Пример запуска скрипта с подмененным PATH c помощью execle

#define _GNU_SOURCE #include int main() { char* e = {"PATH=/habr:/rulez", NULL}; execle("/tmp/test.sh", "test.sh", NULL, e); // Unreachable return 1; }

$ cat test.sh #!/bin/bash echo $0 echo $PATH $ gcc test.c && ./a.out /tmp/test.sh /habr:/rulez


Есть соглашение, которое подразумевает, что argv совпадает с нулевым аргументов для функций семейства exec*. Однако, это можно нарушить.

Пример, когда cat становится dog с помощью execlp

#define _GNU_SOURCE #include int main() { execlp("cat", "dog", "--help", NULL); // Unreachable return 1; }

$ gcc test.c && ./a.out Usage: dog ... ... *Вывод обрезан*


Любопытный читатель может заметить, что в сигнатуре функции int main(int argc, char* argv) есть число - количество аргументов, но в семействе функций exec* ничего такого не передаётся. Почему? Потому что при запуске программы управление передаётся не сразу в main. Перед этим выполняются некоторые действия, определённые glibc, в том числе подсчёт argc.

Состояние «ожидает»

Некоторые системные вызовы могут выполняться долго, например, ввод-вывод. В таких случаях процесс переходит в состояние «ожидает». Как только системный вызов будет выполнен, ядро переведёт процесс в состояние «готов».
В Linux так же существует состояние «ожидает», в котором процесс не реагирует на сигналы прерывания. В этом состоянии процесс становится «неубиваемым», а все пришедшие сигналы встают в очередь до тех пор, пока процесс не выйдет из этого состояния.
Ядро само выбирает, в какое из состояний перевести процесс. Чаще всего в состояние «ожидает (без прерываний)» попадают процессы, которые запрашивают ввод-вывод. Особенно заметно это при использовании удалённого диска (NFS) с не очень быстрым интернетом.

Состояние «остановлен»

В любой момент можно приостановить выполнение процесса, отправив ему сигнал SIGSTOP. Процесс перейдёт в состояние «остановлен» и будет находиться там до тех пор, пока ему не придёт сигнал продолжать работу (SIGCONT) или умереть (SIGKILL). Остальные сигналы будут поставлены в очередь.

Завершение процесса

Ни одна программа не умеет завершаться сама. Они могут лишь попросить систему об этом с помощью системного вызова _exit или быть завершенными системой из-за ошибки. Даже когда возвращаешь число из main() , всё равно неявно вызывается _exit .
Хотя аргумент системного вызова принимает значение типа int, в качестве кода возврата берется лишь младший байт числа.

Состояние «зомби»

Сразу после того, как процесс завершился (неважно, корректно или нет), ядро записывает информацию о том, как завершился процесс и переводит его состояние «зомби». Иными словами, зомби - это завершившийся процесс, но память о нём всё ещё хранится в ядре.
Более того, это второе состояние, в котором процесс может смело игнорировать сигнал SIGKILL, ведь что мертво не может умереть ещё раз.

Забытье

Код возврата и причина завершения процесса всё ещё хранится в ядре и её нужно оттуда забрать. Для этого можно воспользоваться соответствующими системными вызовами:

Pid_t wait(int *wstatus); /* Аналогично waitpid(-1, wstatus, 0) */ pid_t waitpid(pid_t pid, int *wstatus, int options);
Вся информация о завершении процесса влезает в тип данных int. Для получения кода возврата и причины завершения программы используются макросы, описанные в man-странице waitpid(2) .

Пример корректного завершения и получения кода возврата

#include #include #include #include #include int main() { int pid = fork(); switch(pid) { case -1: perror("fork"); return -1; case 0: // Child return 13; default: { // Parent int status; waitpid(pid, &status, 0); printf("exit normally? %s\n", (WIFEXITED(status) ? "true" : "false")); printf("child exitcode = %i\n", WEXITSTATUS(status)); break; } } return 0; }

$ gcc test.c && ./a.out exit normally? true child exitcode = 13


Пример некорректного завершения

Передача argv как NULL приводит к падению.

#include #include #include #include #include int main() { int pid = fork(); switch(pid) { case -1: perror("fork"); return -1; case 0: // Child execl("/bin/cat", NULL); return 13; default: { // Parent int status; waitpid(pid, &status, 0); if(WIFEXITED(status)) { printf("Exit normally with code %i\n", WEXITSTATUS(status)); } if(WIFSIGNALED(status)) { printf("killed with signal %i\n", WTERMSIG(status)); } break; } } return 0; }

$ gcc test.c && ./a.out killed with signal 6


Бывают случаи, при которых родитель завершается раньше, чем ребёнок. В таких случаях родителем ребёнка станет init и он применит вызов wait(2) , когда придёт время.

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

Благодарности

Спасибо Саше «Al» за редактуру и помощь в оформлении;

Спасибо Саше «Reisse» за понятные ответы на сложные вопросы.

Они стойко перенесли напавшее на меня вдохновение и напавший на них шквал моих вопросов.

В Linux также имеются команды для управления процессами, например kill, pkill, pgrep и killall. Ниже приведено несколько примеров их использования:

$ pgrep -u tecmint top $ kill 2308 $ pgrep -u tecmint top $ pgrep -u tecmint glances $ pkill glancesми $ pgrep -u tecmint glances

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

Обратите внимание, что с их помощью вы можете завршать зависшие приложения, которые тормозят вашу систему.

Отправка сигналов процессу

Фундаментальный способ управления процессами в Linux - это отправка им сигналов, которых имеется достаточно много. Посмотреть список всех сигналов можно с помощью команды:

$ kill -l

Для отправки сигналов процессу используются описанные выше команды kill, pkill или pgrep. Однако программа ответит на сигнал, только если она запрограммирована распознавать такой сигнал.

Большинство сигналов предназначены для использования системой или программистами при написании кода. Следующие сигналы могут быть полезны пользователю:

SIGHUP 1 – отправляется процессу при закрытии контролирующего его терминала.
SIGINT 2 – отправляется процессу контролирующим его терминалом, если пользователь прерывает работу процесса клавишами .
SIGQUIT 3 – отправляется процессу, если пользователь посылает сигнал выхода из программы .
SIGKILL 9 – этот сигнал немедленно завершает (убивает) процесс без выполнения любых операций очистки файлов, логов и т.д.
SIGTERM 15 – это сигнал завершения работы программы (он по умоланию отправляется командой kill).
SIGTSTP 20 – отправляется процессу контролирующим его терминалом с запросом на остановку (terminal stop); инициируется при нажатии .

Ниже приведены примеры использования команд kill для завершения работы Firefox при его зависании с использованием PID:

$ pidof firefox $ kill 9 2687 OR $ kill -KILL 2687 OR $ kill -SIGKILL 2687

Для завершения программы с использованием ее названия используются команды pkill или killall:

$ pkill firefox $ killall firefox

Изменение приоритета процесса

В Linux все активные процессы имеют определенное значение приоритета (nice). Процессы с более высоким приоритетом обычно получают больше процессорного времени, чем процессы с более низким приоритетом.

Однако пользователь с привилегиями root может менять приоритет с помощью команд nice и renice.
В выводе команды top столбец NI отображает значения nice для процессов.

Вы можете использовать команду nice, чтобы задать значение nice процесса. Не забывайте, что обычный пользователь может присвоить процессу значение nice от 0 до 20, только если это процесс ему принадлежит.
Отрицательные значения nice может использовать только пользователь root.

Для понижения приоритета процесса используется команда renice:

$ renice +8 2687 $ renice +8 2103

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

Команды управления процессами

Чтобы увидеть список запущенных процессов, нужно ввести команду

Ps-A
В результате выполнения данной команды будет выведен список запущенных процессов Linux с указанием имени и PID (персональный идентификатор), используемого процессорного времени.

Ввод этой же команды с ключом

Aux
позволяет получить расширенную информацию о процессах: PID, использование оперативной памяти и процессора, имя пользователя запустившего процесс, команда запустившая процесс и время работы.

Используя различные ключи команды ps, можно получить и другую информацию о запущенных процессах: ID процесса-родителя, значение приоритета, статус (выполняется, спит, состояние подкачки, остановлен, процесс-зомби), доля памяти и процессорного времени в процентах, время старта и т.д.

Ps
выводит информацию о состоянии процессов в текущий момент времени. Для отображения информации о списке запущенных процессов Linux в реальном времени используется команда

Top
Результат вывода команды top похож на вывод команды ps, с той разницей, что информации о процессе постоянно обновляется.

Значение приоритета процесса может изменяться от -20 (высокий) до +20 (низкий). По умолчанию запускаемому процессу выставляется приоритет родительского процесса. Для изменения приоритета запускаемого процесса используется команда

Nice
которая указывает, насколько приоритет нового процесса должен отличатся от приоритета процесс-родителя.

Значения аргумента команды nice от -20 до +19. Команда

Renice
изменяет приоритет запущенных процессов: renice -3522 -uuser– увеличение приоритета на 3 для процессов пользователя user и с идентификатором 522.

Выставление отрицательных приоритетов доступно только суперпользователю.

Для приостановки, завершения, запуска приостановленного процесса, а также для сообщения процессам другой информации, используются сигналы посылаемые процессам с помощью команды kill, например:

  • kill-9 688 – завершение процесса с PID688 в любом случае (сигнал kill);
  • kill -15 688 – завершение процесса с PID688 программно (сигнал term);
  • kill -19 688 – приостановка выполнения процесса с PID688 (сигнал stop).
Другие опции и сигналы команд управления процессами можно узнать, вызвав справку по данных командам, иногда в них встречаются различия, в зависимости от дистрибутива.

«Зомби» в списке запущенных процессов Linux

Частенько в списке запущенных процессов Linux можно увидеть процессы-зомби (в графе статуса таких процессов стоит буква Z). Такие процессы образуются в том случае, если была вызвана команда завершения процесса, родитель которого еще не выполняется и которому может понадобиться обращение к данному процессу. Z-процесс не использует никакие ресурсы.

Просмотреть родителей процессов-зомби можно командой

Ps –lax
Для того, чтобы убрать «зомби» из списка запущенных процессов Linux надо завершить родительский процесс.

Поддержите проект — поделитесь ссылкой, спасибо!
Читайте также
Подключение и настройка сканера штрихкодов Подключение и настройка сканера штрихкодов Скачать прошивку rooted rom версия 4 Скачать прошивку rooted rom версия 4 Емайл почта — регистрация, выбор Эмейл-адреса, как войти в свой ящик и как просмотреть входящие письма на вашей странице Как в хроме добавить закладку на страницу одноклассники и сделать ее ст Емайл почта — регистрация, выбор Эмейл-адреса, как войти в свой ящик и как просмотреть входящие письма на вашей странице Как в хроме добавить закладку на страницу одноклассники и сделать ее ст