Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лек_1_11_Процессы_пользовательский аспект.doc
Скачиваний:
4
Добавлен:
21.09.2019
Размер:
129.02 Кб
Скачать

Управление процессами с помощью системных функций.

Основные системные функции для управления процессом: fork, exec, waitpid, exit.

рid = fork();

pid = waitpid (pid, &status, opts);

s = execve (name, argv, envp);

exit (status);

fork(2) – единственный способ создания нового процесса в UNIX. Он создает точную копию процесса. Сразу после выполнения fork(2) значение всех соответствующих переменных окружения в родительский и дочерний процессах одинаково, у обоих процессов одни и те же описатели файлов, регистры и т.д. дочернему процессу fork(2) возвращает 0, а родительскому PID дочернего процесса. В большинстве случаев дочерний процесс должен выполнить программу, отличающуюся от программы родительского процесса.

Например, оболочка считывает команду с терминала; с помощью fork создает новый процесс; выполняет введенную команду; затем ждет и считывает новую команду.

Для ожидания завершения дочернего процесса служит системная функция waitpid с тремя параметрами:

pid = waitpid (pid, &statloc, opts)

pid – PID процесса, завершение которого ожидается. Если вместо N указатель -1, то системный вызов ожидает завершения любого дочернего процесса.

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

оpts – будет ли родительский процесс блокирован до завершения дочернего процесса или получит управление сразу после обращения к waitpid.

Сильно упрощенная оболочка:

while (true) { /*бесконечный цикл*/

type_promt (); /*вывод приглашения*/

read_command (command, params); /*прочитать с клавиатуры команду*/

pid = fork (); /*создать дочерний процесс*/

if pid>0

{ printf (“Невозможно создать процесс”);

сontinue;

}

if pid !=0

{ waitpid (-1, &status; 0);} /*родительский процесс ждет завершения дочернего процесса*/

else

{ execve (command, params, 0); } /*дочерний процесс выполняет работу*/

}

Файлы, открытые до обращения к fork, становятся общими для обоих процессов и имеют общий указатель чтения-записи. В частности именно таким образом передаются файлы стандартного ввода/вывода. Все остальные ресурсы родительского и дочернего процесса – разные (свои стеки, свои переменные,...). Если родительский процесс ожидает завершения дочернего, то проблем с файлами не возникает. Однако, если они выполняются параллельно, следует определить соглашения о том, как, кем, когда используются эти файлы (это не в рамках СПО, а параллельно программе).

waitpid(2).

После создания дочернего процесса (с помощью fork) родительский процесс может:

  1. исполняться с ним параллельно.

  2. ждать его завершения.

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

int status; /*статус завершения*/

int childpid; /*идентификатор нужного потомка*/

while (waitpid (-1, &status, 0)!=childpid) ; /*пока не вернется нужный childpid – ничего не делать; */

Ожидать завершения работы порожденных процессов может только их родитель. Если родитель завершает работу раньше, то его потомки наследуются PID=1?

Если хотя бы один из порожденных процессов завершился, то возврат из waitpid (-1,…) выполняется немедленно. Если нет ни одного дочернего процесса, то происходит возврат из waipid с кодом ошибки -1. при нормальном возврате pid переменная status размером слово:

ст.байт

мл.байт

мл.б. статуса завершения дочернего процесса (параметр системной функции exit)

дополнительная системная информация о завершении процесса.

0 – при нормальном завершении, код ошибки – при аварийном завершении.

еxec(2)

Tак называется сам системный вызов, но такого процесса нет, а существуют его различные варианты execl, execv, execle, execve, execlp, execvp, в которых опускаются некоторые параметры или указываются различными способами. Хотя все они в конце концов к одному и тому же системному вызову exec.

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

int execv (char*name, char*argv[]); /*среда передается в неизменном виде */

int execl (name, arg0, arg1, arg2,…,argn, 0); /*среда передается в неизменном виде */

/* параметры задаются явным образом */

здесь: name – имя файла; argv – вектор указателей на параметры – цепочки литер, которые будут переданы в программу, вектор завершается нулевым указателем; argv[0] – имя вызываемой программы.

Наиболее вероятные ошибки при выполнении exec:

  1. файл с именем name не существует или не выполним.

  2. исполнение ‘*’ для порождения имен файлов в оболочке может породить слишком большой список параметров (более 5120 байт).

  3. недостаточно адресного пространства.

После выполнения exec(2) открытые файлы остаются открытыми (кроме тех, которые перехватываются – они отбрасываются в 0).

exit(2)

Простой системный вызов, который процесс использует, заканчивая свое исполнение. У него есть один параметр – статус выхода – возвращаемый родительскому процессу в переменной status системного вызова exit (status); статус имеет значение 0-255.

Дочерний процесс: Родительский процесс:

e xit (status); waitpid (pid, &status, opts);

При нормальном завершении мл.б. переменной статус=0, при ошибке он = коду ошибки.

int status, signal;

while (wait (&status!=childpid);

if (status & 0200) {…/*образован файл core*/}

if (status==0177) {/*доч. процесс приостановлен, но м.б. возобновлен; используется для олдадч.*/}

signal = status &0177;

if (signal==0) { rc=(status >>∞) 80377;}else {…/*аварийное завершение по сигналу*/}

здесь rc – статус завершения; процесс завершился нормально.

else … - процесс завершился аварийно по сигналу.

Например: если дочерний процесс завершился со значением 4, то родительский процесс получит его PID и значение статуса 0*0400:

переменная status функции waitpid

ст.б.

мл.б.

04

00

значение, полученное от системного exit.

сигнал

Если дочерний процесс уже завершил свою работу, а родительский процесс не ожидает этого события, то дочерний процесс переводится в так называемое состояние зомби – т.е. приостанавливается. А когда родительский процесс наконец обратился к waitpid, дочерний процесс завершился.

Несколько системных вызовов относятся к сигналам:

s = sigaction (sig, &act, oldact)

s = sigreturn (&context)

s = sigpocmask (how, &set, &old)

s = sigpending (set)

s = sgsuspend (sigmask)

s = kill (pid, sig)

residual=alarm(seconds)

s = pause ()

Например, при длительном вызове редактора можно нажать DEL или CTRL+С, в результате чего редактору посылается сигнал, он его перехватывает и завершает работу.

Для перехвата сигнала процесс может воспользоваться системным вызовом sigaction.

s = sigaction (sig, &act, &oldact)

sig – сигнал, который необходимо перехватить.

&act – указатель на структуру, а в ней есть: указатель на процесс обработки+биты+флаги

&oldact – указатель на структуру, в которой хранится информация о текущем обрабатываемом сигнале.

Обработчики сигналов обычно короткие и они по своему окончанию возвращаются к точке программы, в которой она была прервана сигналом.

sigaction может использоваться и для игнорирования сигнала, и для действия по умолчанию – уничтожение процесса.

Системный вызов kill позволяет процессу послать сигнал любому родственному процессу. Но по большей части он служит не для уничтожения, а для перехвата.

Системный вызов alarm служит для прерывания процесса в определенный момент времени, чтобы сделать что-либо (например, послать пользователю сообщение – напоминание, послать заново возможно потерянный пакет по надежной линии связи и т.п.).

alarm(second) по истечении этого срока процессу посылается сигнал ALARM.

У процесса в каждый момент времени может быть только один будильник. Т.е. каждое следующее обращение к alarm отменит предыдущее. Если alarm(0), то обменяются все сигналы будильника. Сигнал будильника SIGALARM должен быть перехвачен, иначе он по умолчанию прервет процесс (это бессмысленно).

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