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

Предыдущая  Содержание  Следующая V*D*V

Создание процесса в Linux осуществляется с помощью системного вызова fork(). fork() создаёт для вызывающей стороны новый дочерний процесс. Как только fork возвращается, порождающий и порождённый становятся двумя независимыми субъектами, имеющими разные PID-ы. Теоретически, то, что необходимо сделать fork(), это создать точную копию всех структур данных родительского процесса, включая страницы памяти родительского процесса, доступные только ему. В Linux это дублирование страниц памяти родительского процесса является отложенным. Вместо этого порождающий и дочерний процессы совместно используют одни и те же страницы в памяти, пока один из них не попытается изменить общие страницы. Такой подход называется COW (Copy on Write, копирование при записи). Теперь давайте посмотрим, как fork() достигает этого. Мы обсудим  реализацию fork в Linux 2.6. В Linux 2.4 API называются по другому, но функциональность осталась прежней.

 

1.Создаётся новая структура задачи процесса для дочернего процесса.
 
p = dup_task_struct(current);
 
Это создаст новую структуру задачи и скопирует некоторые указатели из current.
 

2.Получается PID для дочернего процесса.
 
p->pid = alloc_pidmap();
 

3.Из родительского в дочерний процесс копируются файловые дескрипторы, обработчики сигналов, политики планировщика и так далее.
 
/* копируем всю информацию процесса */
 …
 …
// Копируем файловые дескрипторы
if ((retval = copy_files(clone_flags, p)))
 goto bad_fork_cleanup_semundo;
if ((retval = copy_fs(clone_flags, p)))
 goto bad_fork_cleanup_files;
 
// Копируем обработчики сигналов
if ((retval = copy_sighand(clone_flags, p)))
 goto bad_fork_cleanup_fs;
 
// Копируем информацию о сигналах
if ((retval = copy_signal(clone_flags, p)))
 goto bad_fork_cleanup_sighand;
 
// Копируем страницы памяти
if ((retval = copy_mm(clone_flags, p)))
 goto bad_fork_cleanup_signal;
 …
 …
 

4.Порождённый процесс добавляется в очередь планировщика задач и выполняется возврат.
 
 …
 …
/* Выполняем относящуюся к планировщику настройку */
sched_fork(p);
 …
 …
 
if (!(clone_flags & CLONE_STOPPED))
 wake_up_new_task(p, clone_flags);
else
 p->state = TASK_STOPPED;
 …
 …
 
Это изменит состояние процесса на TASK_RUNNING и включит данный процесс в список процесс работоспособных процессов, содержащийся в планировщике задач.

 

Как только fork возвращается, дочерний процесс становится работоспособным процессом и будет запланирован в соответствии с политикой планирования родительского процесса. Обратите внимание, что в fork копируются только структуры данных родительского процесса. Его сегменты текста, данных и стека не копируются. fork пометила такие страницы как COW для последующего выделения памяти по требованию.

На шаге 3 функция copy_mm() по существу помечает страницы родителя как общие только читаемые между родителем и потомком. Атрибут "только чтение" гарантирует, что содержимое памяти не может быть изменено, пока оно является общим. Всякий раз, когда любой из двух процессов пытается записать в эту страницу, обработчик ошибки страницы определяет COW страницу с помощью специальной проверки дескрипторов страницы. Страница, соответствующая ошибке, дублируется и помечается как доступная для записи в процессе, который пытался записать. Исходная страница остаётся защищённой от записи, пока другой процесс не попытается выполнить запись, в течение которой эта страница помечается доступной для записи только после проверки, что она уже не используется каким-то другим процессом.

Как показано выше, процесс дублирования в Linux, осуществляемый через COW, реализован с помощью обработчика ошибки страницы и, следовательно, uClinux не поддерживает fork(). Также для родителя и потомка невозможно иметь аналогичное виртуальное адресное пространство, как это ожидается от fork. Вместо использования для создания дочернего процесса fork(), разработчики uClinux предлагают использование vfork() вместе с exec(). Системный вызов vfork() создаёт дочерний процесс и блокирует выполнение родительского процесса, пока потомок не завершит работу или не выполнит новую программу. Это гарантирует, что родительскому и дочернему процессам не требуется иметь общий доступ к страницам памяти.

 

Предыдущая  Содержание  Следующая