КАТЕГОРИИ:
АстрономияБиологияГеографияДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРиторикаСоциологияСпортСтроительствоТехнологияФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника
|
Управление процессами с помощью системных функций. ⇐ ПредыдущаяСтр 4 из 4 Основные системные функции для управления процессом: 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 размером слово:
е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. Дочерний процесс: Родительский процесс: exit (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
Если дочерний процесс уже завершил свою работу, а родительский процесс не ожидает этого события, то дочерний процесс переводится в так называемое состояние зомби – т.е. приостанавливается. А когда родительский процесс наконец обратился к waitpid, дочерний процесс завершился. Несколько системных вызовов относятся к сигналам:
Например, при длительном вызове редактора можно нажать DEL или CTRL+С, в результате чего редактору посылается сигнал, он его перехватывает и завершает работу. Для перехвата сигнала процесс может воспользоваться системным вызовом sigaction. s = sigaction (sig, &act, &oldact) sig – сигнал, который необходимо перехватить. &act – указатель на структуру, а в ней есть: указатель на процесс обработки+биты+флаги &oldact – указатель на структуру, в которой хранится информация о текущем обрабатываемом сигнале. Обработчики сигналов обычно короткие и они по своему окончанию возвращаются к точке программы, в которой она была прервана сигналом. sigaction может использоваться и для игнорирования сигнала, и для действия по умолчанию – уничтожение процесса. Системный вызов kill позволяет процессу послать сигнал любому родственному процессу. Но по большей части он служит не для уничтожения, а для перехвата. Системный вызов alarm служит для прерывания процесса в определенный момент времени, чтобы сделать что-либо (например, послать пользователю сообщение – напоминание, послать заново возможно потерянный пакет по надежной линии связи и т.п.). alarm(second) по истечении этого срока процессу посылается сигнал ALARM. У процесса в каждый момент времени может быть только один будильник. Т.е. каждое следующее обращение к alarm отменит предыдущее. Если alarm(0), то обменяются все сигналы будильника. Сигнал будильника SIGALARM должен быть перехвачен, иначе он по умолчанию прервет процесс (это бессмысленно). Чтобы занимать процессорное время при вынужденных простоях (например, в обучающих программах, в которых ожидаются ответы пользователя), лучше использовать системный вызов pause, т.к. в это время могут работать фоновые процессы. А сам процесс сможет активизироваться когда прибудет следующий сигнал (чаще всего от клавиатуры).
Контекст и дескриптор процесса (из учеб. пособия Мохова) В основе UNIX лежит концепция процесса - единицы управления и единицы потребления ресурсов. Процесс представляет собой программу в состоянии выполнения. При управлении процессами, каждый из которых на протяжении своего существования может быть многократно прерван и продолжен, ОС использует два основных типа информационных структур: дескриптор и контекст процесса. Идентификация отдельных вычислительных процессов выполняется на основании их дескрипторов, а возобновление выполнения – на основании контекстов. Дескриптор П – информационная структура, однозначно идентифицирующая отдельный процесс с его характеристиками (идентификатор процесса, его состояние, данные о степени привилегированности, место нахождения кодового сегмента и др.). Дескриптор процесса содержит информацию о процессе, которая необходима ядру в течение всего жизненного цикла процесса (состояние процесса, расположение его образа в памяти, глобальный приоритет - идентификатор пользователя, создавшего процесс и др.) Контекст П – информационная структура, описывающая состояние процесса на момент прерывания (состояние регистров, программного счетчика, режим работы процессора, указатели на открытые файлы, информация о незавершенных операциях ввода-вывода, коды ошибок, выполняемые данным процессом системные вызовы и т.д.). Контекст П содержит информацию о процессе, необходимую для возобновления выполнения процесса с прерванного места (содержимое регистров процессора, информацию об открытых данным процессом файлах и др.). Контекст, так же как и дескриптор процесса, доступен только программам ядра, то есть находится в виртуальном адресном пространстве ОС.
Для управления П в ОС применяются очереди вычислительных процессов, представляющие собой списки дескрипторов отдельных П.
Следует отметить, что программный код только тогда начнет выполняться, когда ОС создаст для него процесс. Создать процесс – это значит: 1) Создать дескриптор и контекст. 2) Включить дескриптор нового процесса в очередь готовых процессов. 3) Загрузить кодовый сегмент процесса в оперативную память. Для выполнения процессов предусмотрены два режима: привилегированный режим - «системный» и обычный режим - «пользовательский». В системном режиме выполняются программы ядра, а в режиме пользователя - оболочка, утилиты (команды) и прикладные программы. В пользовательском режиме запрещено выполнение действий, связанных с управлением ресурсами системы.
|