Skip to content

Latest commit

 

History

History
203 lines (140 loc) · 11.2 KB

File metadata and controls

203 lines (140 loc) · 11.2 KB

11. Работа с процессами

Мы можем запускать новые процессы из программы, есть несколько видов работы с процессами из программы.

Функция execl()

Находится в заголовке <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():

  1. Путь к исполняемому файлу - "/usr/bin/touch"
  2. Имя исполняемого файла - "touch"
  3. Аргумент для передачи программе touch, ей требуется имя файла, который нужно создать или обновить его последнюю дату изменения. - передаем ей 2й аргумент нашей программы argv[1].
  4. Аргумент указывающий на конец аргументов, так как нам больше не нужны аргументы указываем - (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 !

Функция fork()

Вызов execl() заменяет процесс на другую программу, а вызов fork() - создает копию текущего процесса и делает этот процесс дочерним.

Выполнение дочернего процесса начинается именно с того места, где был вызван fork() !

Без возможности делать fork'и своих процессов, мы были бы ограничены только одним процессом.

Если мы хотим запустить новую программу, из уже запущенной, не удаляя оригинал, то мы должны делать fork().

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

Это основа параллелизма на процессах.

Терминал, например bash, делает так:

  1. Вы пишите в ввод имя программы, которую хотите запустить, например touch, жмете enter.
  2. bash делает fork() себя.
  3. В этом форке запускается непосредственно программа через 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>
  1. <stdio.h> - для функций printf() и perror() .
  2. <stdlib.h> - для функции exit() .
  3. <unistd.h> - для функции fork() , getpid() и sleep() .
  4. <sys/types.h> - для типа pid_t .
  5. <sys/wait.h> - для функции waitpid() .
  6. <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).
Это имеет такие же риски как и зомби-процессы, потому что они могут забить таблицу процессов.
Также это указывает на баги в программе, так как такого быть не должно.

Следующая статья