Клонирование процесса
Клонирование процесса
Вызов fork() создает клон (полную копию) вызывающего процесса в точке вызова. Вызов fork() является одной из самых базовых конструкций всего UNIX-программирования. Его толкованию посвящено столько страниц в литературе, сколько не уделено никакому другому элементу API. Синтаксис этого вызова (проще по синтаксису не бывает, сложнее по семантике — тоже):
#include <process.h>
pid_t fork(void);
Действие вызова fork() следующее:
• Порождается дочерний процесс, которому системой присваивается новое уникальное значение PID.
• Дочерний процесс получает собственные копии файловых дескрипторов, открытых в родительском процессе в точке выполнения fork(). Каждый дескриптор ссылается на тот же файл, который соответствует аналогичному дескриптору родителя. Блокировки файлов (locks), установленные в родительском процессе, наследуются дочерним процессом.
• Для дочернего процесса его значения tms_utime, tms_stime, tms_cutime и tms_cstime устанавливаются в значение ноль. Выдержки (alarms) для этих таймеров, установленные к этому времени в родительском процессе, в дочернем процессе очищаются.
Сигнальные маски (подробнее об этом будет рассказано ниже) для дочернего процесса инициализируются пустыми сигнальными наборами (независимо от сигнальных масок, установленных родительским процессом).
Если вызов функции завершился неудачно, функция возвращает -1 и устанавливает errno: EAGAIN — недостаточно системных ресурсов; ENOMEM — процессы требуют большее количество памяти, чем доступно в системе; ENOSYS — функция fork() не реализуется в этой модели памяти, например в физической модели адресации памяти (напомним, что QNX — многоплатформенная ОС и число поддерживаемых ею платформ все возрастает).
А вот с кодом возврата этой функции в случае удачи сложнее и гораздо интереснее. Дело в том, что для одного вызова fork() одновременно имеют место два возврата в двух различных копиях (но в текстуально едином коде!): в копии кода, соответствующей дочернему процессу, возвращается 0, а в копии кода родителя — PID успешно порожденного дочернего процесса. Это и позволяет разграничить в едином программном коде фрагменты, которые после точки ветвления надлежит выполнять в родительском процессе и которые относятся к дочернему процессу. Типичный шаблон кода, использующего fork(), выглядит примерно так:
pid_t pid = fork();
if (pid == -1) perror("fork"), exit(EXIT_FAILURE);
if (pid == 0) {
// ... этот код выполняется в дочернем процессе ...
exit(EXIT_SUCCESS);
}
if (pid > 0) {
// ... этот код выполняется в родительском процессе ...
do { // ожидание завершения порожденного процесса
wpid = waitpid(pid, &status, 0);
} while(WIFEXITED(status) == 0);
exit(WEXITSTATUS(status));
}
Эта схема порождения процесса, его клонирование, настолько широко употребляется, особенно при построении самых разнообразных серверов, что для нее была создана специальная техника, построенная на вызове fork(). Заметьте, что во всех многозадачных ОС обязательно присутствует та или иная техника программного создания нового процесса, однако не во всех существует техника именно клонирования, то есть создания полного дубликата порождающего процесса.
Вот как выглядит простейший ретранслирующий TCP/IP-сервер, заимствованный из нашей более ранней публикации [4] (обработка ошибок полностью исключена, чтобы упростить пример):
Ретранслирующий TCP/IP-сервер[12]
int main(int argc, char* argv[]) {
// создание и подготовка прослушивающего сокета:
int rc, ls = socket(AF_INET, SOCK_STREAM, 0);
setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &rc, sizeof(rc));
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr); // специфика QNX
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); // PORT - константа
addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(ls, (struct sockaddr*)&addr, sizeof(sockaddr));
listen(ls, 25);
while(true) {
rc = accept(ls, NULL, NULL);
pid_t pid = fork();
if (pid < 0) ...; // что-то произошло!
if (pid == 0) {
close(ls);
char data[MAXLINE];
int nd = read(rc, &data, MAXLINE);
if (nd > 0) write(rc, &data, nd);
close(rs);
exit(EXIT_SUCCESS);
}
else close(rs); // единственное действие родителя
}
exit(EXIT_SUCCESS);
}
Приведенный фрагмент может в процессе своей работы породить достаточно много идентичных процессов (один родительский, пассивно прослушивающий канал; остальные — порожденные, активно взаимодействующие с клиентами, по одному на каждого клиента). Все порождаемые процессы наследуют весь набор дескрипторов (в данном случае сокетов), доступных родительскому процессу. Лучшее, что могут сделать процессы (как родительский, так и дочерний), — немедленно после вызова fork() (и это хорошая практика в общем случае) тщательно закрыть все унаследованные дескрипторы, не имеющие отношения к их работе.
Примечание
Операция fork() должна создать не только структуру адресного пространства нового процесса, но и побайтную копию этой области. В операционных системах общего назначения (Win32, Linux, FreeBSD) для облегчения этого трудоемкого процесса используется виртуализация страниц по технологии COW (copy on write), детально описанная, например, применительно к Win32, Джеффри Рихтером. Накладные расходы процесса копирования здесь демпфированы тем, что копирование каждой физической страницы памяти фактически производится только при записи в эту страницу, то есть затраты на копирование «размазываются» достаточно случайным образом по ходу последующего выполнения дочернего процесса (здесь нет практически никакого итогового выигрыша а производительности, есть только сокрытие от пользователя одноразового размера требуемых затрат).
Системы реального времени не имеют права на такую роскошь: непредсказуемое рассредоточение копирующих операций по всему последующему выполнению, а поэтому и использование в них COW вообще, выглядит весьма сомнительно. В [4] мы описывали эксперименты в QNX, когда в код сервера, построенного на fork(), была внесена «пассивная» строка, никак не используемая в программе, но определяющая весьма протяженную инициализированную область данных:
static long MEM[2500000];
При этом время реакции (ответа) сервера (затраты времени на выполнение fork()) возросло в 50 раз и составило 0,12 сек на процессоре 400 МГц. Еще раз, но в другом контексте эта особенность будет обсуждена ниже при сравнении затрат производительности на создание процессов и потоков.
Дополнительным вызовом этого класса (для полноты обзора) является использование функции:
pid_t vfork(void);
В отличие от fork(), этот вызов, впервые введенный в BSD UNIX, делает разделяемым для дочернего процесса адресное пространство родителя. Родительский процесс приостанавливается до тех пор, пока порожденный процесс не выполнит exec() (загружая новый программный код дочернего процесса) или не завершится с помощью exit() или аналогичных средств.
Функция vfork() может быть реализована на аппаратных платформах с физической моделью памяти (без виртуализации памяти), a fork() — не может (или реализуется с большими накладными расходами), так как требует создания абсолютной копии области адресного пространства, что в физической модели повлечет сложную динамическую модификацию адресных полей кода. Тем не менее (при некоторых кажущихся достоинствах) в BSD также подчеркивалось, что vfork() таит в себе серьезную потенциальную опасность, поскольку позволяет одному процессу использовать или даже модифицировать адресное пространство другого, то есть фактически нарушает парадигму защищенных адресных пространств.
Более 800 000 книг и аудиокниг! 📚
Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением
ПОЛУЧИТЬ ПОДАРОКДанный текст является ознакомительным фрагментом.
Читайте также
Клонирование виртуальной машины
Клонирование виртуальной машины После того как вам удалось сформировать удобную и подходящую для решаемых задач конфигурацию ВМ, вы можете ее «размножить».Начиная с версии VMware Workstation 5, появилась возможность в буквальном смысле клонировать виртуальные машины, получая
Клонирование виртуальной машины
Клонирование виртуальной машины После того как вы сформируете удобную и подходящую для решаемых задач конфигурацию ВМ, можете ее «размножить», используя операцию клонирования ВМ.Чтобы клонировать ВМ, выполните следующие действия.1. Выключите клонируемую ВМ и в
Внутри процесса ILE
Внутри процесса ILE В этом разделе мы заглянем внутрь процесса ILE. Структура процесса ILE сложна, и, подобно многим другим затронутым нами темам, ее описание насыщено таким количеством имен, сокращений и терминов, что может загнать в угол любого специалиста по компьютерам. И
Атрибуты процесса
Атрибуты процесса Процесс в UNIX имеет несколько атрибутов, позволяющих операционной системе эффективно управлять его работой, важнейшие из которых рассмотрены
Состояния процесса
Состояния процесса Жизненный цикл процесса может быть разбит на несколько состояний. Переход процесса из одного состояния в другое происходит в зависимости от наступления тех или иных событий в системе. На рис. 3.3 показаны состояния, в которых процесс может находиться с
7.6. Клонирование и восстановление системы
7.6. Клонирование и восстановление системы Клонирование — это создание точной (побитной) копии исходного носителя. Носителем в нашем случае будет корневая файловая система Linux. Клонированная копия называется образом.Если вам нужно установить дистрибутив на несколько
7.3.2. Концепции, касающиеся основных средств производственного процесса организации Основные средства производственного процесса организации (ППО)
7.3.2. Концепции, касающиеся основных средств производственного процесса организации Основные средства производственного процесса организации (ППО) Организация устанавливает и сопровождает набор основных средств производственного процесса, как показано на рис. 4.1. К
Клонирование
Клонирование Набор инструментов, позволяющих выполнять разнообразного вида копирование, лучше рассматривать не на примере белого листа, а какого-либо изображения. Откройте любой рисунок и возьмите инструмент Clone Stamp Tool (Штамп клонирования) (рис. 3.25). Он позволяет нам
Клонирование и сравнение объектов
Клонирование и сравнение объектов Ссылочное присваивание приводит к тому, что две или несколько ссылок присоединяются к одному объекту. Иногда необходима другая форма присваивания, в результате которой мы хотим получить не копию ссылки, а копию объекта. Эта цель
Глубокое клонирование и сравнение
Глубокое клонирование и сравнение Формы копирования и сравнения, реализуемые подпрограммами clone, equal и copy , называются поверхностными, поскольку они работают с объектами только на первом уровне, никогда не пытаясь следовать вглубь по ссылкам. Возникает необходимость для
Клонирование по сетке ("Copy to Grid")
Клонирование по сетке ("Copy to Grid") Клонирование по сетке — самый простой из эффектов, который мы можем применить к выделенному фрагменту изображения. Данный эффект даже не анимирован.Чтобы применить этот эффект к выделенному фрагменту изображения, нужно выбрать пункт Copy to
Профессиональное клонирование, криптозащита, амазиге и тамашек Сергей Голубицкий
Профессиональное клонирование, криптозащита, амазиге и тамашек Сергей Голубицкий Опубликовано 22 февраля 2013 Продолжаем прясть нескончаемую нить нашего софтверно-железного бытия. Чуть позже сегодня — в Битом Пикселе (читатели, надеюсь, уже
«Клонирование» успешных бизнес-моделей: найдётся ли выход? Андрей Гершфельд, инвестиционный менеджер фонда ABRT VentureFund; Николай Митюшин, инвестиционный директор фонда ABRT Venture Fund
«Клонирование» успешных бизнес-моделей: найдётся ли выход? Андрей Гершфельд, инвестиционный менеджер фонда ABRT VentureFund; Николай Митюшин, инвестиционный директор фонда ABRT Venture Fund Опубликовано 22 апреля 2013Российская рыночная экономика насчитывает чуть больше двадцати лет,