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

Дерево как структура было выбрано по нескольким причинам:
Процессы имеют только одного создателя - структура уже выходит иерархической, поэтому дерево самое подходящее отображение такой структуры
Обработка ошибок
Когда дочерний процесс умирает по своей причине или из-за исключения, кто-то должен прочитать его код выхода, сделать дамп памяти и другое, чтобы пользователь узнал, что не так случилось в ходе работы
Распространение сигналов
Для общения между процессами используются сигналы. Дерево позволяет сделать отправку одного сигнала группе процессов намного проще, например, сигнала SIGHUP (от SIGnal Hang UP) для завершения процесса и их детей
Первый процесс - это процесс init или systemD, который является корнем одного поддерева процессов
Для создания процессов могут использоваться три системных вызова:
pid_t fork(void) делает копию процесса. Новый процесс имеет те же указатель на таблицу страниц память (то есть ту же память, что и родитель), те же файловые дескрипторы, те же обработчики сигналов, и те же права доступа, что и родитель, но получает новый идентификатор
Для того, чтобы не копировать все адресного пространство, копируют лишь таблицы, а сами страницы помечают флагом “только для чтения”. При попытке записи родителем или ребенком конкретной страницы, возникнет прерывание отказа страницы, вследствие которого ядро скопирует страницу для ребенка. Такой подход называется “копия при записи” (Copy-on-Write)
fork() возвращает -1, если копирование не удалось, 0, если процесс является родителей, и идентификатор дочернего процесса в другом случае:
pid_t pid = fork();
switch (pid) {
case -1:
perror("Копия не удалась");
case 0:
puts("Этот процесс - ребенок");
default:
puts("Этот процесс - родитель");
printf("PID дочернего процесса - %jd\n", (intmax_t) pid);
}
Источник: https://www.man7.org/linux/man-pages/man2/fork.2.html
exec - семейство вызовов, которые заменяют код программы текущего процесса на выбранный в параметрах и удаляет память этого процесса (по сути дает новый указатель на таблицу страниц)
Обычно вызов exec делают сразу же после fork
Всего в этом семействе есть 7 системных вызовов:
int execl(const char *path, const char *arg, ... /*, (char *) NULL */)int execlp(const char *path, const char *arg, ... /*, (char *) NULL */)int execle(const char *file, 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[]);int execve(const char *path, char *const _Nullable argv[], char *const _Nullable envp[])Буквы после exec обозначают тип принимаемых значений:
l - аргументы принимаются как список указателей, оканчивающийся нуль-терминаторомv - аргументы принимаются как указатель на массив указателей, оканчивающийся нуль-терминаторомe - новые переменные окружения для процесса указаны в аргументе envpp - если указанный путь до исполняемого файла не содержит /, то он ищется в директориях, указанных в переменной окружения PATHВсе эти вызовы, кроме execve, являются обертками над execve
execve("/bin/ls", newargv, newenviron);
/* Если ошибки не возникло, то исполнение
* не перейдет на строки ниже */
puts("Ошибка");
Источник: https://www.man7.org/linux/man-pages/man2/execve.2.html
clone() - более мощная версия fork(), которая дает больше контроля над тем, что разделяют родительский процесс и ребенок. Сигнатура у clone() такая:
int clone(typeof(int (void *_Nullable)) *fn,
void *stack,
int flags,
void *_Nullable arg, ...
/* pid_t *_Nullable parent_tid,
void *_Nullable tls,
pid_t *_Nullable child_tid */ );
Здесь передает указатель на функцию fn (процесс уничтожается, если функция возвращает значение), указатель на стек stack для дочернего процесса, флаги flags - результат побитового “ИЛИ” констант
Среди интересных есть флаги:
CLONE_PARENT - делает родителем нового процесса родителя текущего процесса, таким образом, делая их братьямиCLONE_SIGHAND - делает так, что ребенок и родитель имеют одну таблицу обработчиков сигналовCLONE_STOPPED - делает так, что ребенок находится в состоянии “Остановлен”CLONE_VM - делает так, что ребенок и родитель разделяют между собой адресное пространствоCLONE_THREAD - делает так, что ребенок помещен в ту же группу потоков, что и родительКак можно заметить, системный вызов clone() делает создание потоков намного проще
В результате clone() возвращает идентификатор потока в случае успеха
Помимо clone() есть также вызов __clone2() для архитектуры IA64 (на ней основана линейка процессоров Intel Itanium) и clone3(), принимающий структуру cl_args, благодаря которой возможно более гибкое создание процесса
Источник: https://www.man7.org/linux/man-pages/man2/clone.2.html
У процесса в Linux есть состояние в зависимости от того, исполняется ли он прямо сейчас, ждет потока ввода/вывода или находится вне оперативной памяти. Состояние помогает при управлении планировании исполнения процессов:

Подробнее о состояниях процессов описано в курсе “Операционные системы”
После того, как процесс окончил исполнения (то есть вызвал системный вызов _exit(status_code)), процесс переходит в состояние “Зомби”. В нем он будет находится до тех пор, пока родительский процесс не прочитает код выхода процесса. Родитель может сделать это несколькими способами:
Синхронно с помощью системных вызовов wait, waitid, waitpid
Эти системные вызовы приостанавливают исполнение до тех пор, пока дочерний процесс не окончит исполнение. wait ждет смерти любого дочернего процесса, в вызове waitpid можно выбрать идентификатор дочернего процесса, а waitid позволяет обрабатывать смену некоторых состояний дочернего процесса
Источник: https://www.man7.org/linux/man-pages/man2/wait.2.html
Асинхронно с помощью обработчика сигнала SIGCHLD:
signal(SIGCHLD, handler);
void handler(int sig) {
int status;
waitpid(-1, &status, WNOHANG);
}
Отдельное место в Linux занимают демоны. Демон (Daemon) - это фоновый процесс, который работает непрерывно и независимо от пользовательской сессии. Он ожидает событий или запросов и обрабатывает их без участия пользователя
Название “демон” происходит из греческой мифологии — демон был фоновым духом, действующим от имени других
Стандартные потоки ввода, вывода и ошибок у демонов перенаправлены в /dev/null. Вместо них демоны используют системный журнал или собственный файл с логами. Также демоны записывают свои идентификаторы процессов, чтобы другие процессы знали, как к ним обратиться
Первый демон, появляющийся при запуске Linux, - это поток [kthreadd] с идентификатором 2. Он является корнем поддерева фоновых потоков ядра, таких как [kswapd0] - демон для страничного обмена, [migration/0] - демон для переноса процессов между ядрами и так далее
Для создания демонов можно:
В терминале остановленный процесс (например, с помощью Ctrl+Z) можно обратно вывести в “Готовность” с помощью команды fg (от foreground), а можно вывести в “Готовность” в качестве демона с помощью команды bg
Альтернативно терминал позволяет запустить процесс в фоновом режиме, если после команды написать &:
python3 script.py &
В таком случае у демона будет стандартный поток вывода, который перенаправлен в терминал
Также после закрытия терминала дочерние фоновые процессы также завершаются. Чтобы этого избежать, можно применить команду disown:
python3 script.py &
disown %1
Или команду nohup (от no hang up):
nohup python3 script.py &
На языке C можно:
fork()_exit()setsid()SIGHUP на пустойfork() и завершить текущийumask(), сменить директорию на корень chdir("/"), закрыть наследованные файловые дескрипторы и перенаправить стандартные потоки в /dev/nullБолее современный способ состоит из создания службы - демона, управляемого другим процессом
В большинстве дистрибутивах (Ubuntu, Debian, Fedora, Arch Linux, CentOS, Red Hat Enterprise Linux) роль менеджера служб играет система systemd
Чтобы создать демон, управляемый systemd, нужно создать файл объявления:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target # запустить после запуска демона networkd
[Service]
Type=simple
User=myappuser # запустить от имени myappuser
ExecStart=/usr/bin/myapp # исполняемый файл
Restart=on-failure # перезапустить при падении
StandardOutput=journal # записывать логи в журнал systemd
Далее он управляется с помощью команд:
systemctl start myapp # запустить сейчас
systemctl stop myapp # остановить
systemctl enable myapp # запускать при старте системы
systemctl status myapp # статус службы
systemctl restart myapp # перезапустить
Критика вокруг systemd заключается в том, что кодовая база systemd чересчур большая, система нарушает философию Unix “одна программа делает одно дело”, так как systemd пишет логи, занимается монтированием и так далее. Поэтому используют альтернативы:
SysV init, использующийся в Unix и дистрибутиве Slackware, является простой системой, но демоны запускаются последовательноrunit - минималистичная система, где сервис создается символической ссылкой /etc/sv/myapp/run на исполняемый файл, используется в Void Linux и Docker-контейнерахOpenRC - улучшенный SysV init, используется в Gentoo Linux и Alpine Linux