Главная » Программирование для UNIX » Управление процессами – fork и wait

0

Следующий шаг – это восстановление управления после того, как программа выполнена с помощью execlp  или  execvp. Поскольку эти  программы просто накладывают новую  программу поверх старой, то чтобы сохранить первичную программу, необходимо сначала разделить ее на  две копии; одна  из  них  может быть  перезаписана, в то время как вторая ожидает окончания новой,  наложенной программы. Разделение осуществляется системным вызовом fork:

proc_id  = fork();

программа разделяется на две копии, каждая из которых продолжает выполняться. Единственное различие между ними заключается в зна чении, возвращаемом fork,– это идентификатор процесса. Для  одного из этих  процессов (дочернего) proc_id равен нулю. Для другого (роди5 тельского) proc_id  нулю  не равен. Таким образом, элементарный способ вызвать другую программу и вернуться из нее выглядит так:

if (fork() == 0)

execlp("/bin/sh", "sh", "–c", commandline,  (char  *)  0);

И  этого  вполне достаточно,  если  не  считать  обработки ошибок. fork создает две копии программы. Для дочернего процесса значение, возвращаемое fork, равно  нулю, поэтому вызывается execlp, который выполняет командную строку commandline и умирает. Для родительского процесса fork возвращает ненулевую величину, поэтому execlp пропускается. (В случае наличия ошибок fork возвращает –1.)

Чаще всего родительская программа ждет, пока закончится дочерняя, прежде чем продолжить свое собственное выполнение. Это реализуется при помощи системного вызова wait:

int status;

if (fork() == 0)

execlp(…);                 /*  дочерняя  */ wait(&status);                     /*  родительская  */

Здесь  еще не обрабатываются никакие аномальные обстоятельства, такие  как сбой  в  работе  execlp  или  fork  или  возможность  наличия нескольких  одновременно  выполняющихся  дочерних  процессов (wait возвращает идентификатор  процесса для  закончившегося  дочернего процесса, так  что можно сравнить его со значением, возвращаеммым fork). Наконец, этот  фрагмент не имеет  никакого отношения к обработке сколь бы то ни было  странного поведения со стороны дочернего процесса. Однако эти три строки – основа стандартной функции system.

Значение переменной status, возвращаемое wait, содержит в младших восьми битах представление системы о коде завершения для дочернего процесса; ноль  означает нормальное  завершение, а ненулевое значение  указывает,  что  возникли какие-либо проблемы.  Следующие по старшинству восемь бит берутся из аргумента функции exit или опера тора return в main, вызвавшего завершение дочернего процесса.

Когда  программа вызывается оболочкой, создаются три  дескриптора: 0, 1 и 2, которые указывают на соответствующие файлы, а все остальные  файловые дескрипторы доступны  для использования. Когда  эта программа вызывает другую, правила этикета предписывают убедиться,  что выполняются те же  условия. Ни fork, ни exec никоим образом не влияют на открытые файлы; оба процесса – и родительский, и дочерний, имеют один и тот же набор открытых файлов. Если  родительская программа буферизует вывод, который должен появиться рань ше,  чем вывод  дочерней, то родительская программа должна сбросить содержимое буфера на  диск  до вызова execlp. И наоборот, если  роди тельская программа буферизует входящий поток, то дочерняя потеряет информацию, прочитанную родителем. Вывод  может быть сброшен на диск, но невозможно вернуть назад ввод. Эти соображения возникают,  если  ввод  или  вывод  осуществляется при  помощи  стандартной

библиотеки ввода-вывода, описанной в главе 6, так как она обычно  буферизует и ввод, и вывод.

Наследование дескрипторов файлов в execlp может «подвесить» систе му: если вызывающая программа не имеет  своего стандартного ввода и вывода, связанного с терминалом, то и вызываемая программа не будет его иметь. Возможно, что это как раз  то,  что нужно; например, в скрипте для  редактора ed ввод для команды, запущенной с восклицательным знаком !, вероятно, будет производиться из скрипта. Даже в этом случае ed должен читать ввод посимвольно, чтобы избежать проблем с буферизацией.

Однако для интерактивных программ, таких  как p,  система должна повторно подключить стандартный ввод-вывод к терминалу. Один  из способов сделать это – подключить их к /dev/tty.

Системный вызов dup(fd) дублирует дескриптор файла fd в незанятый дескриптор файла с наименьшим номером, возвращая новый дескриптор, который ссылается на тот же самый открытый файл. Этот код свя зывает стандартный ввод программы с файлом:

int fd;

fd  =  open("file",  0); close(0);

dup(fd); close(fd);

Вызов  close(0) освобождает дескриптор 0 (стандартный ввод),  но, как всегда, не влияет на родительскую программу.

Приведем версию system  для   интерактивных  программ,  использующую  для  сообщений  об  ошибках progname.  Можете пока  не  обращать внимания на части функции, относящиеся к обработке сигналов; вернемся к ним в следующем разделе.

/*

* Более  надежная версия system  для интерактивных  программ

*/

#include <signal.h>

#include <stdio.h>

system(s)   /*  выполнить  командную  строку s  */ char  *s;

{

int status, pid,  w, tty;

int  (*istat)(),  (*qstat)(); extern  char  *progname;

fflush(stdout);

tty  =  open("/dev/tty",  2); if  (tty  ==  –1)  {

fprintf(stderr, "%s: can’t  open  /dev/tty\n", progname); return –1;

}

if  ((pid  =  fork())  ==  0)  { close(0);  dup(tty); close(1);  dup(tty); close(2);  dup(tty); close(tty);

execlp("sh",  "sh", "–c",  s,  (char *)  0); exit(127);

} close(tty);

istat =  signal(SIGINT,  SIG_IGN); qstat  =  signal(SIGQUIT,  SIG_IGN);

while  ((w  = wait(&status))  !=  pid  &&  w   !=  –1)

;

if (w ==  –1) status  =  –1;

signal(SIGINT,  istat);

signal(SIGQUIT,  qstat); return  status;

}

Обратите внимание на то, что /dev/tty открывается в режиме 2 (чтения и записи), а затем дублируется для  создания стандартного ввода  и вывода.  Именно так  система  назначает  стандартные устройства ввода, вывода и ошибок при  входе  в нее.  Поэтому можно выводить данные в стандартный ввод:

$ echo hello  1>&0

hello

$

Это означает, что  можно было  скопировать  файловый дескриптор 2, чтобы повторно подключить стандартный ввод-вывод, но корректнее и надежнее будет открыть /dev/tty. Даже у этого варианта system есть потенциально  возможные проблемы: файлы, открытые в  вызывающей программе, как, например, tty в программе ttyin в p, будут  переданы дочернему процессу.

Урок не в том, что именно представленная в этом разделе версия system должна использоваться во всех ваших программах (она,  например, не подошла бы для  неинтерактивного ed), а в том, чтобы  понять, как управлять процессами и  как корректно использовать базовые возможности; значение слова  «корректно» зависит от конкретного приложения и может не совпадать со стандартной реализацией system.

Источник: Керниган Б., Пайк Р., UNIX. Программное окружение. – Пер. с англ. – СПб: Символ-Плюс, 2003. – 416 с., ил.

По теме:

  • Комментарии