Что такое дочерний процесс
Зачем нужны дочерние процессы
При разработке приложения часто бывает нужно, чтобы какую-то операцию выполнял другой блок кода. Поэтому приходится постоянно вызывать функции или подпрограммы. Но вызов функции приводит к приостановке выполнения основного кода программы до возврата из вызванной функции.
Альтернативный способ — передать выполнение какой-то операции другому потоку в пределах данного процесса (поток, разумеется, нужно сначала создать). Это позволит основному коду программы продолжить работу в то время, как дополнительный поток будет выполнять другую операцию. Прием весьма удобный, но, когда основному потоку потребуется узнать результаты работы другого потока, Вам не избежать проблем, связанных с синхронизацией.
Часто дочернему процессу необходимы данные, содержащимися в адресном пространстве родительского процесса. Было бы неплохо, чтобы он работал исключительно в своем адресном пространстве, а из родительского просто считывал нужные ему данные, тогда он не сможет что-то испортить в адресном пространстве родительского процесса. В Windows предусмотрено несколько способов обмена данными между процессами.
Один из самых удобных способов, обеспечивающих совместный доступ
Если Вы хотите создать новый процесс, заставить его выполнить какие-либо операции и дождаться их результатов, напишите примерно такой код:
PROCESS_INFORMATION pinfo;
DWORD dwExitCode;
// порождаем дочерний процесс
BOOL success = CreateProcess(. &pinfo>;
// закрывайте описатель потока, как только
// необходимость в нем отпадает!
CloseHandle(pinfo.hThread);
// приостанавливаем выполнение родительского процесса,
// пока не завершится дочерний процесс
WaitForSingleObject(pinfo.hProcess, INFINITE);
// дочерний процесс завершился; получаем код его завершения
GetExitCodeProcess(pinfo.hProcess, &dwExitCode);
// закрывайте описатель процесса, как только
// необходимость в нем отпадает!
CloseHandle(pinfo.hProcess);
В этом фрагменте кода создается новый процесс и, если это прошло успешно, вызывается функция WaitForSingleQbject :
DWORD WaitForSingleObject(HANDLE hObject, DWORD dwTimeOut);
Обращение к CloseHandle в приведенном выше фрагменте кода заставляет систему уменьшить значения счетчиков объектов «поток» и «процесс» до нуля и тем самым освободить память, занимаемую этими объектами.
Очень часто приложение создает другие процессы как обособленные. Это значит, что после создания и запуска нового процесса родительскому процессу нет нужды с ним взаимодействовать или ждать, пока тот закончит работу.
Чтобы обрубить все пуповины, связывающие с дочерним процессом, нужно вызовом CloseHandle закрыть свои описатели, связанные с новым процессом и его первичным потоком. Приведенный ниже фрагмент кода демонстрирует, как, создав процесс, сделать его обособленным:
// разрешаем системе уничтожить обьекты ядра «процесс» и «поток»
// сразу после создания дочернего процесса
CloseHandle(pinfo.hThread);
CloseHandle(pinfo.hProcess);
Дата добавления: 2019-07-15 ; просмотров: 204 ; Мы поможем в написании вашей работы!
Наследование
Дочерний процесс может наследовать несколько свойств и ресурсов от своего родительского процесса. Кроме того, дочерний процесс может не допустить наследования свойств родительского процесса. Может быть унаследовано следующее:
Дочерний процесс не наследует следующие:
Наследование дескрипторов
Дочерний процесс может наследовать некоторые его дескрипторы родителя, но не наследовать другие. Чтобы вызвать наследование маркера, необходимо выполнить два действия:
Унаследованный обработчик ссылается на тот же объект в дочернем процессе, что и в родительском процессе. Он также имеет то же значение и привилегии доступа. Таким образом, когда один процесс изменяет состояние объекта, изменение влияет на оба процесса. Чтобы использовать маркер, дочерний процесс должен получить значение Handle и «знает» объект, к которому он относится. Как правило, родительский процесс передает эти сведения дочернему процессу через ее командную строку, блок среды или некоторую форму межпроцессного взаимодействия.
Используйте функцию сесандлеинформатион для управления наследованием существующего маркера.
Наследование переменных среды
По умолчанию дочерний процесс наследует переменные среды родительского процесса. Однако CreateProcess позволяет родительскому процессу указывать другой блок переменных среды. Дополнительные сведения см. в разделе переменные среды.
Наследование текущего каталога
Изучаем процессы в Linux. Управление процессами
Обновл. 12 Июл 2021 |
Процесс — это экземпляр запущенной программы. Всякий раз, когда в терминале выполняется какая-нибудь команда (например, команда pwd ), система создает/запускает новый процесс.
Типы процессов
В Linux существует три основных типа процессов:
Процессы переднего плана (или «интерактивные процессы») — они инициализируются и управляются с помощью терминального сеанса. Другими словами, необходимым условием для запуска таких процессов является наличие пользователя, подключенного к системе; они не запускаются автоматически как часть системных функций/служб. Когда команда/процесс выполняется на переднем плане, то они полностью занимают запустивший их терминал. Вы не сможете использовать другие команды, т.к. приглашение оболочки будет недоступно, пока данный процесс выполняется на переднем плане.
Фоновые процессы (или «автоматические процессы») — это процессы, не подключенные к терминалу; они не ожидают пользовательского ввода данных. Таким образом, другие процессы могут выполняться параллельно с процессом, запущенным в фоновом режиме, поскольку им не нужно ждать его завершения.
Демоны (англ. «daemons») — это особый тип фоновых процессов, которые запускаются при старте системы и продолжают работать в виде службы; они не умирают. Такие процессы запускаются как системные задачи (службы). Однако при этом они могут управляться пользователем через init-процесс (о котором мы поговорим чуть позже). Например, к демонам относится служба электронных сообщений sendmail и sshd — служба, принимающая от клиентов запросы на соединения по протоколу ssh. За исключением процесса init и некоторых других, процессы демонов обычно имеют окончание d в своем имени.
Как Linux идентифицирует процессы?
Поскольку Linux является многопользовательской системой, разные пользователи могут запускать различные программы, при этом каждый запущенный экземпляр программы должен быть однозначно идентифицирован ядром.
Процессы запущенной программы имеют уникальный пятизначный номер — PID (сокр. от «Process IDentificator» — «идентификатор процесса»), а также PPID (сокр. от «Parent Process IDentificator» — «идентификатор родительского процесса»). В связи с этим процессы дополнительно разделяют на две группы:
Родительские процессы — это процессы, которые во время своего выполнения создают другие процессы.
Дочерние процессы — эти процессы, создаваемые другими процессами во время своего выполнения.
Прародителем всех процессов в системе является процесс init (от англ. «initialization») — первая программа, которая выполняется при загрузке Linux и управляет всеми другими процессами в системе. init запускается самим ядром и всегда имеет PID = 1, поэтому у него в принципе нет родительского процесса.
Примечание: В любой момент времени в системе не существует двух процессов с одинаковым PID. Вновь создаваемому процессу может быть назначен ранее использованный свободный PID.
Состояния процесса в Linux
Когда процесс передает ядру запрос, который не может быть исполнен сразу же, то процесс «погружается в сон/ожидание» и «пробуждается», когда запрос может быть удовлетворен. В связи с этим, в зависимости от текущей ситуации, процесс, во время своего выполнения, может переходить из одного состояния в другое:
Рассмотрим основные состояния процесса:
Выполнение — процесс либо запущен (текущий процесс в системе), либо готов к запуску (ожидает передачи на выполнение процессору).
Ожидание — процесс ожидает наступления некоторого события (пользовательского ввода, сигнала от другого процесса и т.п.) или выделения системных ресурсов. Кроме того, ядро также различает два типа ожидающих процессов:
прерываемые ожидающие процессы — могут быть прерваны сигналами;
непрерываемые ожидающие процессы — процессы ожидают непосредственно на аппаратном уровне и не могут быть прерваны каким-либо событием/сигналом.
Завершен — процесс был остановлен, как правило, путем получения сигнала штатного завершения работы exit().
Зомби — иногда, когда родительский процесс убивается до завершения дочернего процесса, дочерние процессы становятся «осиротевшими», при этом в качестве нового родителя (с соответствующим изменением PPID) им назначается процесс init. Убитые процессы, но при этом все еще отображающиеся в таблице процессов, называются процессами зомби (они мертвы и не используются).
Как получить идентификатор (PID) процесса
Для отображения идентификатора нужного вам процесса можно использовать команду pidof, например:
$ pidof init
$ pidof bash
$ pidof systemd
Чтобы вывести PID и PPID текущей оболочки, выполните:
Запуск интерактивного процесса в Linux
Как только вы выполните какую-нибудь команду или программу (например, firefox ), она создаст в системе соответствующий процесс. Вы можете запустить процесс переднего плана (он будет подключен к терминалу, ожидая пользовательского ввода) следующим образом:
Запуск фонового процесса в Linux
Запуск процесса в фоновом режиме полезен только для программ, которые не нуждаются в пользовательском вводе (через оболочку). Перевод задания в фоновый режим обычно выполняется, когда ожидается, что выполнение задания займет много времени.
Кроме этого, в оболочку встроена утилита управления заданиями jobs, которая позволяет легко управлять несколькими процессами, переключая их между передним планом и фоновым исполнением. Также, с помощью jobs процессы могут быть сразу запущены в фоновом режиме.
Чтобы запустить процесс в фоновом режиме, используйте символ & после имени запускаемой программы. В этом случае процесс не будет принимать пользовательский ввод, пока не переместится на передний план:
$ firefox #После Enter нажмите Ctrl+Z
$ jobs
Чтобы продолжить выполнение вышеупомянутой приостановленной команды в фоновом режиме, используйте команду bg (от англ. «begin»):
Чтобы отправить фоновый процесс на передний план, используйте команду fg (от англ. «foreground») вместе с идентификатором задания следующим образом:
Отслеживание активных процессов
Существует несколько различных инструментов для просмотра/перечисления запущенных в системе процессов. Двумя традиционными и хорошо известными из них являются команды ps и top:
Команда ps
Отображает информацию об активных процессах в системе, как показано на следующем скриншоте:
UID — идентификатор пользователя, которому принадлежит процесс (тот, от чьего имени происходит выполнение).
PID — идентификатор процесса.
PPID — идентификатор родительского процесса.
C — загрузка CPU процессом.
STIME — время начала выполнения процесса.
TTY — тип терминала, связанного с процессом.
TIME — количество процессорного времени, потраченного на выполнение процесса.
CMD — команда, запустившая этот процесс.
Есть и другие опции, которые можно использовать вместе с командой ps :
-a — показывает информацию о процессах по всем пользователям;
-x — показывает информацию о процессах без терминалов;
-u — показывает дополнительную информацию о процессе по заданному UID или имени пользователя;
-e — отображение расширенной информации.
Если вы хотите вывести вообще всю информацию по всем процессам системы, то используйте команду ps –aux :
Обратите внимание на выделенный заголовок. Команда ps поддерживает функцию сортировки процессов по соответствующим столбцам. Например, чтобы отсортировать список процессов по потреблению ресурсов процессора (в порядке возрастания), введите команду:
Если вы ходите выполнить сортировку по потреблению памяти (в порядке убывания), то добавьте к имени интересующего столбца знак минуса:
Еще один очень популярный пример использования команды ps — это объединение её и команды grep для поиска заданного процесса по его имени:
Команда top
Команда top отображает информацию о запущенных процессах в режиме реального времени:
PID — идентификатор процесса.
USER — пользователь, которому принадлежит процесс.
PR — приоритет процесса на уровне ядра.
VIRT — общий объем (в килобайтах) виртуальной памяти (физическая память самого процесса; загруженные с диска файлы библиотек; память, совместно используемая с другими процессами и т.п.), используемой задачей в данный момент.
RES — текущий объем (в килобайтах) физической памяти процесса.
SHR — объем совместно используемой с другими процессами памяти.
S (сокр. от «STATUS») — состояние процесса:
S (сокр. от «Sleeping») — прерываемое ожидание. Процесс ждет наступления события.
I (сокр. от «Idle») — процесс бездействует.
R (сокр. от «Running») — процесс выполняется (или поставлен в очередь на выполнение).
Z (сокр. от «Zombie») — зомби-процесс.
%CPU — процент используемых ресурсов процессора.
%MEM — процент используемой памяти.
TIME+ — количество процессорного времени, потраченного на выполнение процесса.
COMMAND — имя процесса (команды).
Также в сочетании с основными символами состояния процесса (S от «STATUS») вы можете встретить и дополнительные:
— процесс с высоким приоритетом;
N — процесс с низким приоритетом;
l — многопоточный процесс;
Примечание: Все процессы объединены в сессии. Процессы, принадлежащие к одной сессии, определяются общим идентификатором сессии — идентификатором процесса, который создал эту сессию. Лидер сессии — это процесс, идентификатор сессии которого совпадает с его идентификаторами процесса и группы процессов.
Команда glances
Команда glances — это относительно новый инструмент мониторинга системы с расширенными функциями:
Примечание: Если в вашей системе отсутствует данная утилита, то установить её можно с помощью следующих команд:
$ sudo apt-get update
$ sudo apt-get install glances
Управление процессами в Linux
Также в Linux присутствуют некоторые команды для управления процессами:
kill — посылает процессу сигнал завершения работы;
pkill — завершает процесс по его имени;
pgrep — ищет процесс по его имени (и, опционально, по имени запустившего его пользователя);
killall — завершает все активные процессы.
Ниже приведены несколько основных примеров их использования:
Отправка сигналов процессам
Основополагающим способом управления процессами в Linux является отправка им соответствующих сигналов. Для перечисления списка всех доступных сигналов, введите команду:
Большинство сигналов предназначены для внутреннего использования системой или для программистов, когда они пишут код. Ниже приведены наиболее полезные сигналы:
SIGHUP (1) — отправляется процессу, когда его управляющий терминал закрыт.
SIGINT (2) — отправляется процессу управляющим терминалом, когда пользователь прерывает процесс нажатием клавиш Ctrl+C.
SIGQUIT (3) — отправляется процессу, если пользователь посылает сигнал выхода Ctrl+D.
SIGKILL (9) — этот сигнал немедленно завершает (убивает) процесс, и процесс не будет выполнять никаких операций очистки за собой.
SIGTERM (15) — сигнал завершения программы (отправляется командой kill по умолчанию).
SIGTSTP (20) — отправляется процессу управляющим терминалом с запросом на остановку; инициируется пользователем нажатием клавиш Ctrl+Z.
Ниже приведены примеры команды kill для уничтожения приложения firefox с помощью PID, после его зависания:
Изменение приоритета процесса
В системе Linux все активные процессы имеют определенный приоритет выполнения, задаваемый так называемым nice-значением. Процессы с более высоким приоритетом обычно получают больше процессорного времени, чем процессы с более низким приоритетом. Однако пользователь с root-правами может повлиять на это с помощью команд nice и renice.
Узнать значение приоритета команды можно по выводу команды top (столбец NI):
Чем больше nice-значение, тем меньшим приоритетом будет обладать процесс. Например, вы можете задать приоритет для запускаемого процесса следующим образом:
Чтобы изменить приоритет уже запущенного процесса, используйте команду renice следующим образом:
$ renice +8 5547
$ renice +8 1151
На данный момент это всё! Если у вас есть какие-либо вопросы или дополнительные идеи, вы можете поделиться ими с нами с помощью комментариев.
Поделиться в социальных сетях:
Изучаем процессы в Linux
В этой статье я хотел бы рассказать о том, какой жизненный путь проходят процессы в семействе ОС Linux. В теории и на примерах я рассмотрю как процессы рождаются и умирают, немного расскажу о механике системных вызовов и сигналов.
Данная статья в большей мере рассчитана на новичков в системном программировании и тех, кто просто хочет узнать немного больше о том, как работают процессы в Linux.
Всё написанное ниже справедливо к Debian Linux с ядром 4.15.0.
Содержание
Введение
Системное программное обеспечение взаимодействует с ядром системы посредством специальных функций — системных вызовов. В редких случаях существует альтернативный API, например, procfs или sysfs, выполненные в виде виртуальных файловых систем.
Атрибуты процесса
Процесс в ядре представляется просто как структура с множеством полей (определение структуры можно прочитать здесь).
Но так как статья посвящена системному программированию, а не разработке ядра, то несколько абстрагируемся и просто акцентируем внимание на важных для нас полях процесса:
Жизненный цикл процесса
Рождение процесса
Состояние «готов»
Сразу после выполнения fork(2) переходит в состояние «готов».
Фактически, процесс стоит в очереди и ждёт, когда планировщик (scheduler) в ядре даст процессу выполняться на процессоре.
Состояние «выполняется»
Перерождение в другую программу
В некоторых программах реализована логика, в которой родительский процесс создает дочерний для решения какой-либо задачи. Ребёнок в данном случае решает какую-то конкретную проблему, а родитель лишь делегирует своим детям задачи. Например, веб-сервер при входящем подключении создаёт ребёнка и передаёт обработку подключения ему.
Однако, если нужно запустить другую программу, то необходимо прибегнуть к системному вызову execve(2) :
или библиотечным вызовам execl(3), execlp(3), execle(3), execv(3), execvp(3), execvpe(3) :
Как не путаться во всех этих вызовах и выбирать нужный? Достаточно постичь логику именования:
Семейство вызовов exec* позволяет запускать скрипты с правами на исполнение и начинающиеся с последовательности шебанг (#!).
Есть соглашение, которое подразумевает, что argv[0] совпадает с нулевым аргументов для функций семейства exec*. Однако, это можно нарушить.
Любопытный читатель может заметить, что в сигнатуре функции int main(int argc, char* argv[]) есть число — количество аргументов, но в семействе функций exec* ничего такого не передаётся. Почему? Потому что при запуске программы управление передаётся не сразу в main. Перед этим выполняются некоторые действия, определённые glibc, в том числе подсчёт argc.
Состояние «ожидает»
Некоторые системные вызовы могут выполняться долго, например, ввод-вывод. В таких случаях процесс переходит в состояние «ожидает». Как только системный вызов будет выполнен, ядро переведёт процесс в состояние «готов».
В Linux так же существует состояние «ожидает», в котором процесс не реагирует на сигналы прерывания. В этом состоянии процесс становится «неубиваемым», а все пришедшие сигналы встают в очередь до тех пор, пока процесс не выйдет из этого состояния.
Ядро само выбирает, в какое из состояний перевести процесс. Чаще всего в состояние «ожидает (без прерываний)» попадают процессы, которые запрашивают ввод-вывод. Особенно заметно это при использовании удалённого диска (NFS) с не очень быстрым интернетом.
Состояние «остановлен»
В любой момент можно приостановить выполнение процесса, отправив ему сигнал SIGSTOP. Процесс перейдёт в состояние «остановлен» и будет находиться там до тех пор, пока ему не придёт сигнал продолжать работу (SIGCONT) или умереть (SIGKILL). Остальные сигналы будут поставлены в очередь.
Завершение процесса
Состояние «зомби»
Сразу после того, как процесс завершился (неважно, корректно или нет), ядро записывает информацию о том, как завершился процесс и переводит его в состояние «зомби». Иными словами, зомби — это завершившийся процесс, но память о нём всё ещё хранится в ядре.
Более того, это второе состояние, в котором процесс может смело игнорировать сигнал SIGKILL, ведь что мертво не может умереть ещё раз.
Забытье
Код возврата и причина завершения процесса всё ещё хранится в ядре и её нужно оттуда забрать. Для этого можно воспользоваться соответствующими системными вызовами:
Передача argv[0] как NULL приводит к падению.
После того, как родитель забрал информацию о смерти ребёнка, ядро стирает всю информацию о ребёнке, чтобы на его место вскоре пришёл другой процесс.
Благодарности
Спасибо Саше «Al» за редактуру и помощь в оформлении;
Спасибо Саше «Reisse» за понятные ответы на сложные вопросы.
Они стойко перенесли напавшее на меня вдохновение и напавший на них шквал моих вопросов.