Функция fork в linux

Многозадачность в Linux. Язык программирования C. Статья 2 (функция fork)

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

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

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

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

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

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

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

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

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

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

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

Объясните ка, почему строка ‘ До 89 ‘ появляется в листинге один раз, а строка ‘ После 89 ‘ дважды.

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

Следующая статья о многозадачности здесь .

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

Подписываемся на мой канал Old Programmer .

Источник

Функция fork в linux

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

#include #include pid_t fork(void);

  • сегменты кода, данных и стека программы;
  • таблицу файлов, в которой находятся состояния флагов дескрипторов файла, указывающие, читается ли файл или пишется. Кроме того, в таблице файлов содержится текущая позиция указателя записи-чтения;
  • рабочий и корневой каталоги;
  • реальный и эффективный номер пользователя и номер группы;
  • приоритеты процесса (администратор может изменить их через nice );
  • контрольный терминал;
  • маску сигналов;
  • ограничения по ресурсам;
  • сведения о среде выполнения;
  • разделяемые сегменты памяти.

  • идентификатора процесса (PID, PPID);
  • израсходованного времени ЦП (оно обнуляется);
  • сигналов процесса-родителя, требующих ответа;
  • блокированных файлов (record locking).

Процесс-потомок и процесс-родитель получают разные коды возврата после вызова fork() . Процесс-родитель получает идентификатор (PID) потомка. Если это значение будет отрицательным, следовательно при порождении процесса произошла ошибка. Процесс-потомок получает в качестве кода возврата значение 0, если вызов fork() оказался успешным.

Таким образом, можно проверить, был ли создан новый процесс:

switch(ret=fork()) < case -1: /при вызове fork() возникла ошибка/ case 0 : /это код потомка/ default : /это код родительского процесса/ >
#include #include #include #include #include #include main() < pid_t pid; int rv; switch(pid=fork()) < case -1: perror("fork"); /* произошла ошибка */ exit(1); /*выход из родительского процесса*/ case 0: printf(" CHILD: Это процесс-потомок!\n"); printf(" CHILD: Мой PID -- %d\n", getpid()); printf(" CHILD: PID моего родителя -- %d\n", getppid()); printf(" CHILD: Введите мой код возврата (как можно меньше):"); scanf(" %d"); printf(" CHILD: Выход!\n"); exit(rv); default: printf("PARENT: Это процесс-родитель!\n"); printf("PARENT: Мой PID -- %d\n", getpid()); printf("PARENT: PID моего потомка %d\n",pid); printf("PARENT: Я жду, пока потомок не вызовет exit(). \n"); wait(); printf("PARENT: Код возврата потомка:%d\n", WEXITSTATUS(rv)); printf("PARENT: Выход!\n"); >>

Когда потомок вызывает exit() , код возврата передается родителю, который ожидает его, вызывая wait() . WEXITSTATUS() представляет собой макрос, который получает фактический код возврата потомка из вызова wait() .

Функция wait() ждет завершения первого из всех возможных потомков родительского процесса. Иногда необходимо точно определить, какой из потомков должен завершиться. Для этого используется вызов waitpid() с соответствующим PID потомка в качестве аргумента. Еще один момент, на который следует обратить внимание при анализе примера, это то, что и родитель, и потомок используют переменную rv . Это не означает, что переменная разделена между процессами. Каждый процесс содержит собственные копии всех переменных.

Рассмотрим следующий пример:

#include #include #include int main() < char pid<[>255; fork(); fork(); fork(); sprintf(pid, "PID : %d\n",getpid()); write(STDOUT_FILENO, pid, strlen(pid)); exit(0); >

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

Так называемые процессы-зомби возникают, если потомок завершился, а родительский процесс не вызвал wait() . Для завершения процессов используют либо оператор возврата, либо вызов функции exit() со значением, которое нужно возвратить операционной системе. Операционная система оставляет процесс зарегистрированным в своей внутренней таблице данных, пока родительский процесс не получит кода возврата потомка, либо не закончится сам. В случае процесса-зомби его код возврата не передается родителю, и запись об этом процессе не удаляется из таблицы процессов операционной системы. При дальнейшей работе и появлении новых зомби таблица процессов может быть заполнена, что приведет к невозможности создания новых процессов.

Источник

Функция fork в linux

Вызов 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). Также стоит учитывать следующее: * Процесс потомка создаётся с одиночной нитью — той, которая вызвала fork(). Всё виртуальное адресное пространство родителя копируется в потомок, включая состояние мьютексов, условных переменных и других объектов pthreads; в случае проблем с этим может помочь pthread_atfork(3). * В многонитивой программе после fork(2) потомок может безопасно вызывать только безопасные-асинхронные-сигнальные функции (смотрите signal(7)) до тех пор, пока не вызовет execve(2). * Потомок наследует копии набора открытых файловых дескрипторов родителя. Каждый файловый дескриптор в потомке ссылается на то же описание файла что и родитель (смотрите open(2)). Это означает, что два файловых дескриптора совместно используют флаги состояния открытого файла, текущее смещение файла и атрибуты ввода-вывода, управляемые сигналами (смотрите описание F_SETOWN и F_SETSIG в fcntl(2)). * Потомок наследует копии набора файловых дескрипторов открытых очередей сообщений родителя (смотрите mq_overview(7)). Каждый файловый дескриптор в потомке ссылается на то же описание открытой очереди сообщений что и родитель. Это означает, что два файловых дескриптора совместно используют флаги (mq_flags). * Потомок наследует копии набора потоков открытых каталогов родителя (смотрите opendir(3)). В POSIX.1 сказано, что соответствующие потоки каталогов в родителе и потомке могут совместно использовать позицию в потоке каталога; в Linux/glibc они не могут этого делать.

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

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

ОШИБКИ

EAGAIN Возникло системного ограничение на количество нитей. Есть несколько ограничений, которые могут вызвать эту ошибку: был достигнут мягкий ограничитель 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), поэтому расходы на вызов состоят из времени и памяти, требуемой на копирование страничных таблиц родителя и создания уникальной структуры, описывающей задачу.

Отличия между библиотекой C и ядром

Начиная с версии 2.3.3, вместо того, чтобы вызывать системный вызов fork(), обёрточная функция fork() в glibc, как часть реализации нитей NPTL, вызывает clone(2) с флагами, которые обеспечивают поведение традиционного системного вызова (вызов fork() эквивалентен вызову clone(2), если значение равно flags SIGCHLD). Обёртка в glibc вызывает все обработчики при ветвлении (fork), которые были зарегистрированы с помощью pthread_atfork(3).

Источник

Читайте также:  Shortcuts in linux commands
Оцените статью
Adblock
detector