Команда fork в linux

Обновлено: 07.07.2024

pid_t fork(void);

ОПИСАНИЕ

Вызов fork() создаёт новый процесс посредством копирования вызывающего процесса. Новый процесс считается дочерним процессом. Вызывающий процесс считается родительским процессом.

Дочерний и родительский процессы находятся в отдельных пространствах памяти. Сразу после fork() эти пространства имеют одинаковое содержимое. Запись в память, отображение файлов (mmap(2)) и снятие отображения (munmap(2)), выполненных в одном процессе, ничего не изменяет в другом.

Дочерний процесс является точной копией родительского процесса за исключением следующих моментов:

* Потомок имеет свой уникальный идентификатор процесса, и этот PID (идентификатор процесса) не совпадает ни с одним существующим идентификатором группы процессов (setpgid(2)). * Идентификатор родительского процесса у потомка равен идентификатору родительского процесса. * Потомок не наследует блокировки памяти родителя (mlock(2), mlockall(2)). * Счётчики использования ресурсов (getrusage(2)) и времени ЦП у потомка сброшены в 0. * Набор ожидающих сигналов потомка изначально пуст (sigpending(2)). * Потомок не наследует значения семафоров родителя (semop(2)). * Потомок не наследует связанные с процессом блокировки родителя (fcntl(2)) (с другой стороны, он наследует блокировки файловых описаний fcntl(2) и блокировки flock(2)). * Потомок не наследует таймеры родителя (setitimer(2), alarm(2), timer_create(2)). * Потомок не наследует ожидающие выполнения операции асинхронного ввода-вывода (aio_read(3), aio_write(3)) и контексты асинхронного ввода-вывода родителя (см. io_setup(2)).

Все перечисленные атрибуты указаны в POSIX.1. Родитель и потомок также отличаются по следующим атрибутам процесса, которые есть только в Linux:

* Потомок не наследует уведомления об изменении каталога (dnotify) родителя (смотрите описание F_NOTIFY в fcntl(2)). * Настройка PR_SET_PDEATHSIG у prctl(2) сбрасывается, и поэтому потомок не принимает сигнал о завершении работы родителя. * Резервное значение по умолчанию устанавливается равным родительскому текущему резервному значению таймера. Смотрите описание PR_SET_TIMERSLACK в prctl(2). * Отображение памяти, помеченное с помощью флага MADV_DONTFORK через madvise(2), при fork() не наследуется. * Сигнал завершения работы потомка всегда SIGCHLD (см. clone(2)). * Биты прав доступа к порту, установленные с помощью ioperm(2), не наследуются потомком; потомок должен установить все нужные ему биты с помощью ioperm(2).

Также стоит учитывать следующее:

ВОЗВРАЩАЕМОЕ ЗНАЧЕНИЕ

При успешном завершении родителю возвращается PID процесса-потомка, а процессу-потомку возвращается 0. При ошибке родительскому процессу возвращается -1, процесс-потомок не создаётся, а значение errno устанавливается в соответствующее значение.

ОШИБКИ

Возникло системного ограничение на количество нитей. Есть несколько ограничений, которые могут вызвать эту ошибку: был достигнут мягкий ограничитель RLIMIT_NPROC (задаётся с помощью setrlimit(2)), который ограничивает количество процессов и ните для реального ID пользователя; был достигнут ядерный системный ограничитель на количество процессов и нитей, /proc/sys/kernel/threads-max (смотрите proc(5)); был достигнуто максимальное количество PID, /proc/sys/kernel/pid_max (смотрите proc(5)).

EAGAIN Вызывающий работает по алгоритму планирования SCHED_DEADLINE и у него не установлен флаг сброса-при-fork (reset-on-fork). Смотрите sched(7). ENOMEM Вызов fork() завершился с ошибкой из-за невозможности разместить необходимые структуры ядра, потому что слишком мало памяти. ENOSYS Вызов fork() не поддерживается на этой платформе (например, из-за того, что аппаратное обеспечение не содержит блока управления памятью (MMU)).

СООТВЕТСТВИЕ СТАНДАРТАМ

ЗАМЕЧАНИЯ

В Linux, fork() реализован с помощью «копирования страниц при записи» (copy-on-write, COW), поэтому расходы на вызов состоят из времени и памяти, требуемой на копирование страничных таблиц родителя и создания уникальной структуры, описывающей задачу.

Первым шагом в запуске новой программы является вызов fork():

Использование fork() просто. Перед вызовом один процесс, который мы называем родительским, является запущенным. Когда fork() возвращается, имеется уже два процесса: родительский и порожденный (child).

Вот ключ: оба процесса выполняют одну и ту же программу. Два процесса могут различить себя, основываясь на возвращённом fork() значении:

Если была ошибка, fork() возвращает -1, а новый процесс не создается. Работу продолжает первоначальный процесс.

В порожденном процессе fork() возвращает 0.

В родительском процессе fork() возвращает положительный идентификационный номер (PID) порожденного процесса.

Код шаблона для создания порожденного процесса выглядит следующим образом:

if ((child = fork()) < 0)

else if (child == 0)

/* это новый процесс */

/* это первоначальный родительский процесс */

pid_t является знаковым целым типом для хранения значений PID. Скорее всего, это просто int, но специальный тип делает код более понятным, поэтому он должен использоваться вместо int.

На языке Unix, помимо названия системного вызова, слово «fork» является и глаголом, и существительным[88]. Мы можем сказать, что «один процесс ответвляет другой», и что «после разветвления работают два процесса». (Думайте «развилка (fork) на дороге», а не «вилка (fork), нож и ложка».)

9.1.1.1. После fork() : общие и различные атрибуты

9.1.1.1. После fork(): общие и различные атрибуты Порожденный процесс «наследует» идентичные копии большого числа атрибутов от родителя. Многие из этих атрибутов специализированы и здесь неуместны. Поэтому следующий список намеренно неполон. Существенны

10.9. Сигналы, передающиеся через fork() и exec()

10.9. Сигналы, передающиеся через fork() и exec() Когда программа вызывает fork(), ситуация с сигналами в порожденном процессе почти идентична ситуации в родительском процессе. Установленные обработчики остаются на месте, заблокированные сигналы остаются заблокированными и т.д.

Создание процесса

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

Создание нового процесса

Создание нового процесса В операционной системе Unix создание процессов происходит уникальным образом. В большинстве операционных систем для создания процессов используется метод порождения процессов (spawn). При этом создается новый процесс в новом адресном пространстве,

Создание нового процесса

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

Создание процесса

Создание процесса Как уже обсуждалось, в UNIX проведена четкая грань между программой и процессом. Каждый процесс в конкретный момент времени выполняет инструкции некоторой программы, которая может быть одной и той же для нескольких процессов.[39] Примером может служить

4.7. Функции fork и exec

7.1 СОЗДАНИЕ ПРОЦЕССА

7.1 СОЗДАНИЕ ПРОЦЕССА Единственным способом создания пользователем нового процесса в операционной системе UNIX является выполнение системной функции fork. Процесс, вызывающий функцию fork, называется родительским (процесс-родитель), вновь создаваемый процесс называется

9.1.2.1 Выгрузка при выполнении системной функции fork

9.1.2.1 Выгрузка при выполнении системной функции fork В описании системной функции fork (раздел 7.1) предполагалось, что процесс-родитель получил в свое распоряжение память, достаточную для создания контекста потомка. Если это условие не выполняется, ядро выгружает процесс из

9.2.1.1 Функция fork в системе с замещением страниц

9.2.1.1 Функция fork в системе с замещением страниц Как уже говорилось в разделе 7.1, во время выполнения функции fork ядро создает копию каждой области родительского процесса и присоединяет ее к процессу-потомку. В системе с замещением страниц ядро по традиции создает

5.1. Системные вызовы fork() и ехес()

5.1. Системные вызовы fork() и ехес() Процесс в Linux (как и в UNIX) — это программа, которая выполняется в отдельном виртуальном адресном пространстве. Когда пользователь регистрируется в системе, под него автоматически создается процесс, в котором выполняется оболочка (shell),

Создание, завершение и просмотр учетной записи процесса

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

1.5. Действие команд fork, exec и exit на объекты IPC

1.5. Действие команд fork, exec и exit на объекты IPC Нам нужно достичь понимания действия функций fork, exec и _exit на различные формы IPC, которые мы обсуждаем (последняя из перечисленных функций вызывается функцией exit). Информация по этому вопросу сведена в табл. 1.4.Большинство функций

Всем привет! Хочу поделиться своим опытом написания собственной командной оболочки Linux используя Posix API, усаживайтесь поудобнее.

Что должна уметь наша командная оболочка

Запуск процессов в foreground и background режиме

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

Поддержка перемещения по директориям

Как устроена работа командной оболочки

Считывание строки из стандартного потока ввода

Разбиение строки на токены

Создание дочернего процесса с помощью системного вызова fork

Замена дочернего процесса на необходимый с помощью системного вызова exec

Ожидание завершения дочернего процесса (в случае foreground процесса)

Немного про системный вызов fork()

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

Что выведет данная программа:

I'm parent process!
I'm child process!

Что же произошло? Системный вызов fork создал клон процесса, т. е. теперь мы имеем родительский и дочерний процесс.

Чтобы отличить дочерний процесс от родительского в коде достаточно сделать проверку. Если результат функции fork равен 0 - мы имеем дело с дочерним процессом, если нет - с родительским. Это не означает, что в операционной системе id дочернего процесса равен 0 .

Причем, порядок выполнения дочернего и родительского процесса ничем не задекларирован. Все будет зависеть от планировщика операционной системы. Поэтому в конце блока родительского процесса добавлена строчка wait(NULL) , которая дожидается окончания дочернего процесса.

Подробнее про exec()

В документации есть различные вариации системного вызова exec , но они отличаются только способом передачи токенов в параметры функции, смысл от этого не изменяется.

Системный вызов exec заменяет текущий процесс сторонним. Естественно, сторонний процесс задается через параметры функции.

Что выведет данная программа

total 16
-rwxr-xr-x 1 runner runner 8456 Jan 13 07:33 main
-rw-r--r-- 1 runner runner 267 Jan 13 07:33 main.c

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

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

Перейдем к полноценной реализации

Часть 1. Чтение строки с консоли

Изначально нам надо уметь считывать строку из командной строки. Думаю, с этим не возникнет сложностей.

В данной функции происходит чтение строки с применением функции getline . После чего, если в конце строки имеется символ переноса строки, удаляем его.

Обратите внимание, что при чтении могут возникнуть ошибки, одна из них - нажатие сочетания клавиш ctrl-D . Однако это штатный случай завершения работы командной оболочки, следовательно в данном случае не должна выводиться ошибка.

Часть 2. Разбиение строки на токены

В данной части будет представлена реализация разбиения строки на массив токенов.

Определения начальной длины массива и разделителей строки.

Код выглядит довольно громоздким, однако в нем нет ничего сложного.

Очередной токен получается с использованием функции strtok . После чего данный токен копируется в массив токенов. Если в массиве токенов не достаточно места, массив увеличивается в 2 раза.

Завершается всё добавлением завершающего токена равного NULL , т. к. функция exec() ожидает наличие данного завершающего токена.

Часть 3. Выполнение процессов

Структура хранения списка запущенных процессов

Напишем определения структур для foreground и background процессов, fg_task и bg_task . А также определение структуры для хранения всех процессов tasks .

Создадим в коде глобальную переменную типа tasks , которая и будет хранить все наши запущенные процессы.

Вспомогательные функции добавления процессов

Установка foreground процесса выглядит банально и не нуждается в комментировании.

Добавление background процесса выглядит посложнее.

На деле же все проще. Смотрим есть ли место в массиве для хранения информации о процессе. Если его недостаточно - увеличиваем длину массива в 2 раза. После чего происходит добавление структуры bg_task в массив и последующее заполнение полей структуры информацией о процессе.

Данная функция возвращает -1 в случае неудачи.

Вспомогательные функции для завершения процессов

Добавим функцию экстренного завершения foreground процесса. Данная функция с помощью системного вызова kill с параметром SIGTERM завершает процесс по id процесса.

Также добавим функцию для завершения background процесса.

Данная функция принимает в себя массив токенов вида ", NULL> . После чего преобразует токен индекса background задачи в число. Убивает background задачу посредством системного вызова kill .

Непосредственно запуск процессов

Для удобства введем функцию is_background , определяющую является ли задача фоновым процессом. Данная функция просто проверяет наличие & в конце.

Введем функцию launch которая будет запускать background процесс если в конце присутствует токен & , иначе будет запускаться foreground процесс.

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

Создается дубликат процесса с помощью системного вызова fork

Заменяем дочерний процесс на требуемый с помощью системного вызова exec

Определяем является ли процесс фоновым

Если процесс фоновый - просто добавляем его в список bacground задач

Если процесс не фоновый - дожидаемся окончания выполнения процесса

В функции присутствует неизвестная функция quit . Ее мы разберем в следующем блоке.

Вспомогательные функции для командной оболочки.

Введем функцию execute , которая в зависимости от первого токена выбирает нужное действие.

Данная функция пропускает действие, если первый токен NULL . Смена директории, если первый токен cd . Вывод справки об использовании, если первый токен help . Завершение работы командной оболочки, если первый токен quit . Вывод списка background задач, если первый токен bg . Завершение процесса по индексу, если первый токен term .

Во всех других случаях запускается процесс.

Реализация вспомогательных функций

Значение CONTINUE означает дальнейшее исполнение главного цикла командной оболочки. Значение EXIT прерывает выполнение главного цикла программы.

Функция quit отключает все callback функции по событию SIGCHLD - т. е. функции, выполняющиеся когда дочерний элемент был завершен. После этого завершает все активные процессы.

Основные цвета командной оболочки.

Часть 4. Главный цикл командной оболочки

Здесь и происходит вся магия. Взгляните на следующие строки. С помощью функции signal задаются callback функции на заданные события.

Событие SIGINT - срабатывает при нажатии комбинации ctrl-C , которое в дефолтном поведении завершает работу программы. В нашем же случае мы переназначаем его на завершение foreground процесса.

Событие SIGCHLD - срабатывает при завершении дочернего процесса созданyого с помощью системного вызова fork . В нашем случае мы переопределяем его на пометку фоновой задачи как выполненной с помощью функции mark_ended_task .

Все что описано в главном цикле командной оболочки можно описать словами:

Вывод информации о пользователе и текущей директории с помощью функции display

Чтение строки из стандартного потока ввода

Разбиение строки на токены

Выполнение ранее описанной функции execute , которая в зависимости от массива токенов выполняет нужное нам действие.

Нам осталось реализовать одну оставшуюся функцию display . Которая получает информацию о текущей директории с помощью функции getcwd и имя пользователя с помощью функции getpwuid .

Часть 5. Итоговый результат

Надеюсь, данный материал полезен. Если вам есть чем дополнить данный материал, буду рад услышать ваши мысли в комментариях.

Здесь весь мой канал Old Programmer . Здесь: Программирование. Тематическое оглавление моего Zen-канала (Old Programmer) . А здесь собраны все ссылки по C и C++. А здесь перечень ссылок на ресурсы, посвященные многозадачности.

Сегодня продолжаем многозадачную тему. Будем рассматривать функцию fork() . Начало темы здесь .

О системной функции fork()

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

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

Пример использования функции fork() в Linux

Рассмотрим программу multi1010.c . Компилируется она обычным образом:

gcc multi1010.c

Интерпретация выполнения этой программы как раз позволяет нам понимать, как работает функция fork() . Суть ситуации заключается в том, что в родительском процессе переменная t получает значение, равное идентификатору дочернего процесса , а в дочернем процессе переменная t равна нулю . Вот это и позволяет в коде "определять" поведение дочернего и родительского процессов. Условной конструкцией if(!t) мы и разделяем обработку для родительского и дочернего процессов.

Ну и, наконец, две функции getpid() - получить идентификатор текущего процесса, getppid() - получить идентификатор родительского процесса. Соответственно, идентификатор getpid() в родительском процессе, совпадает с идентификатором getppid() в дочернем процессе. Ну а значение getpid() в дочернем процессе совпадет со значением переменной t в родительском. Такие дела.

Результат выполнения программы:

До 89
После 89
Родительский 16773
Дочерний 16774
После 89
Дочерний 16774
Родительский 16773

При использовании функции fork необходимо отслеживать дочерние процессы. Когда дочерний процесс завершается, связь его с родителем сохраняется, пока родительский процесс не завершится или не вызовет функцию wait . Т.е. дочерний процесс остается в системе, являясь "зомби" - процессом.

Читайте также: