Мы можем запускать новые процессы из программы, есть несколько видов работы с процессами из программы.
Находится в заголовке <unistd.h>. При успешном вызове execl() процесс, вызывающий эту функцию, заменяется на процесс созданный этой функцией. Звучит немного запутанно, но на примере будет проще разобраться.
process_exec.c:
Ссылка
Эта программа вызывает системную утилиту touch, которая создает файлы. Программа получает от пользователя второй аргумент - имя файла, и передает этот аргумент утилите touch.
Первым параметром для функции execl() нужен путь к исполняемому файлу, который мы хотим запустить в новом процессе. В нашем случае это touch:
const char* bin_to_execute = "/usr/bin/touch";Дальше идет непосредственно вызов функции execl():
int exec_result = execl(bin_to_execute, "touch", argv[1], (char*) NULL);Список параметров этого вызова функции execl():
- Путь к исполняемому файлу -
"/usr/bin/touch" - Имя исполняемого файла -
"touch" - Аргумент для передачи программе
touch, ей требуется имя файла, который нужно создать или обновить его последнюю дату изменения. - передаем ей 2й аргумент нашей программыargv[1]. - Аргумент указывающий на конец аргументов, так как нам больше не нужны аргументы указываем -
(char*) NULL.
По сути все. Если у нас выполнение функции execl() прошло успешно, то дальше код нашей программы не вызывается.
if( exec_result == -1 )
{
perror("executing program failed");
exit(1);
}
printf("DONE!\n");
exit(0);То есть мы не увидим сообщение DONE!, если все прошло хорошо.
Если все пошло плохо, то вызываем perror() из заголовка <stdio.h>, чтобы понять что произошло.
Пример выполнения программы:
./process_exec.out giga.txtЭто равносильно вызову:
touch giga.txtСоздаем пустой файл giga.txt или обновляем его дату изменения, если он уже существует.
Это ключевой момент - текущий процесс нашей программы заменяется на новый процесс программы touch !
Вызов execl() заменяет процесс на другую программу, а вызов fork() - создает копию текущего процесса и делает этот процесс дочерним.
Выполнение дочернего процесса начинается именно с того места, где был вызван fork() !
Без возможности делать fork'и своих процессов, мы были бы ограничены только одним процессом.
Если мы хотим запустить новую программу, из уже запущенной, не удаляя оригинал, то мы должны делать fork().
Таким образом мы можем строить дерево процессов, от родительского, самого первого, создавать его дочерние процессы, а они в свою очередь, создают свои дочерние процессы и так далее, в глубину.
Это основа параллелизма на процессах.
Терминал, например bash, делает так:
- Вы пишите в ввод имя программы, которую хотите запустить, например
touch, жметеenter. - bash делает
fork()себя. - В этом форке запускается непосредственно программа через
execl(), форк подменяется процессомtouch.
process_fork.c:
Ссылка
При запуске программы, создается дочерний процесс из fork(), затем родитель ожидает его окончания.
./process_fork.out
root PID: 4805
parent process started!
child PID: 4806
waiting for child ends...
child process started!
child process done!
child done work! return status of child: 0
parent process done.
Необходимые заголовки
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdbool.h><stdio.h>- для функцийprintf()иperror().<stdlib.h>- для функцииexit().<unistd.h>- для функцииfork(),getpid()иsleep().<sys/types.h>- для типаpid_t.<sys/wait.h>- для функцииwaitpid().<stdbool.h>- для типаbool(да, да, его нет изначально).
Рассмотрение программы
Сперва выводим на экран PROCESS ID (pid) изначального процесса, который мы вызвали командой из bash. Для получения своего идентификатора процесса (pid) используется функция getpid():
printf("root PID: %d\n", getpid());Потом делаем fork() для создания дочернего процесса. Функция возвращает одно из трех значений:
pidнового дочернего процесса0для определения, родительский ли процесс сейчас или дочерний (так как код одной программы, нам нужно затем проверять где мы, в дочке или в родителе)-1в случае сбоя.
pid_t forked_pid = fork();Для дальнейшей проверки в коде, в каком именно процессе мы находимся, нужно проверить pid , где мы? :
bool is_child = forked_pid == 0;
bool is_parent = forked_pid > 0;Проверяем, если мы внутри дочернего процесса, то делаем в нем какую-то работу, в данном случае работа симулируется функцией sleep() со значением 10.
if( is_child )
{
printf("\t\tchild process started!\n");
sleep(sleep_seconds);
printf("\t\tchild process done!\n");
}Далее, если мы в родительском процессе, то нужно подождать дочерний процесс, пока он закончит свою работу, потом поспать 2 секунды:
if( is_parent )
{
int return_child_status = -1;
printf("parent process started!\n");
printf("\tchild PID: %d\n", forked_pid);
printf("\twaiting for child ends...\n");
waitpid(forked_pid, &return_child_status, 0);
printf("\tchild done work! return status of child: %d\n", return_child_status);
printf("parent process done.\n");
sleep(2);
}Обратите внимание, что с помощью функции waitpid() мы можем получать статус завершения процесса , и проверять как исполнились наши дочки, успешно или со сбоем (те самые return X или exit(X), а как еще проверять?).
Процессы-Зомби (Zombie Process)
Если не сделать ожидание конца работы дочерних процессов из родительского функцией
waitpid(), то есть риск, что дочерние процессы остануться висеть мертвым грузом до конца работы родительского процесса, такие процессы называются ЗОМБИ !
Зомби процессы не жрут ресурсы, на то они и зомби, однако они забивают таблицу процессов системы, главная их опасность что их может быть много, и они исчерпают лимиты на процессы.
И тогда при создании процесса можно получить ошибкуEAGAIN.В мониторинге процессов (например
ps), эти зомбари имеют статусZ+.
Процессы-Сироты (Orphan Process)
Если родительский процесс завершился раньше, чем дочерний процесс, то дочерний процесс, уже потерявший родителя (он закончил работу раньше) становится - сиротой . Так как у всех процессов должен быть родитель, этот процесс-сирота мгновенно становится дитём главного процесса (
PID =1).
Это имеет такие же риски как и зомби-процессы, потому что они могут забить таблицу процессов.
Также это указывает на баги в программе, так как такого быть не должно.