Главная страница  Межпроцессное взаимодействие (состязание) 

1 2 3 4 5 6 7 8 9 [ 10 ] 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

пускает дочерний процесс, ждет, пока дочерний процесс выполнит команду, и читает следующую команду после завершения работы дочернего процесса. Ожидая, пока дочерний процесс закончит работу, родительский процесс выполняет системный вызов waitpid, который ожидает завершения дочернего процесса (или всех дочерних процессов, если их на данный момент несколько). Waitpid может ждать окончания какого-либо определенного дочернего процесса или любого дочернего процесса, для этого нужно задать первый параметр вызова равным -1. Когда waitpid выполнен, указатель, задаваемый вторым параметром statloc, будет установлен на статус завершения дочернего процесса (нормальное или аварийное завершение и выходное значение). Третий параметр определяет различные необязательные настройки.

Теперь рассмотрим, как вызов fork используется оболочкой. Когда печатается команда, оболочка создает дочерний процесс, который должен выполнить команду пользователя. Он делает это с помощью системного вызова exec, заменяющего весь его образ памяти файлом, названным в первом параметре. (Фактически самим системным вызовом является exec, но несколько различных библиотечных процедур вызывают его с разными параметрами и незначительно отличающимися именами. Мы здесь воспользуемся ими как системными вызовами.) Весьма упрощенная оболочка, иллюстрирующая использование команд fork, waitpid и exec, показана в листинге 1.1.

Листинг 1.1. Усеченная оболочка

#define TRUE 1

while (TRUE) { /* вечный цикл */

type prompt( ); /* печать приглашения на экране */

read coniiiand(cofflmand. parameters): /* читать входные данные с терминала */ if (fork( ) !- 0) { /* запускает дочерний процесс */

/* текст родительского процесса */ waitpid(-l, &status. 0): /* ждать окончания дочернего процесса */

} else {

/* текст дочернего процесса */

execve(coiimand. parameters. 0): /* выполнение command */

В самом общем случае у команды exec есть три параметра: имя файла, который будет выполняться, указатель на массив аргументов и указатель на массив переменных окружения. Эти параметры мы кратко обсудим в дальнейшем. Различные библиотечные программы, включая execl, execv, execle и execve, разрешают пропускать параметры или определять их другими способами. В книге мы воспользуемся названием exec для того, чтобы представить системный вызов, вызываемый всеми этими процедурами.

Рассмотрим следующую команду:

ср filel file2

которая используется для копирования файла filel в файл fileZ. После создания оболочкой дочернего процесса последний находит и исполняет файл ср и передает ему имена исходного и целевого файлов.

На протяжении всей книги значение константы TRUE предполагается равным 1.



Основной модуль программы ср (как и большинство других головных программ на С) содержит определение:

iTiain(argc. argv, envp)

в котором в параметр urge входит количество записей в командной строке, включая имя программы. Например, для строки вверху argc равен 3.

Второй параметр argv является указателем на массив указателей. Элемент i массива указывает на г-ю запись в командной строке. В нашем примере argv[0] должен указывать на слово ср, а argv[l] и argv[2] - на слова filel и file2 соответственно.

Третий параметр функции main, envp, является указателем на массив строковых переменных окружения вида имя = величина, которые используются для передачи программе такой информации, как тип терминала или имя домашнего каталога. В листинге 1.1 третий параметр равен нулю, поскольку ничего не передается дочернему процессу.

Если команда exec кажется сложной, не огорчайтесь, потому что это один из наиболее сложных системных вызовов в POSIX. Все остальные намного проще. В качестве еще одного примера рассмотрим exit, процессы должны использовать его при завершении работы. У него есть всего один параметр, статус выхода, изменяющийся от О до 255. Он возвращается родительскому процессу через переменную status в системных вызовах wait и waitpid. Младший байт этой переменной содержит значение статуса выхода, который равен О при нормальном завершении работы и ненулевому значению при завершении по ошибке. Старший байт содержит статус завершения дочернего процесса. Например, если родительский процесс выполнит оператор:

n=wanpid(-l. &status. options):

то его работа будет приостановлена до завершения дочернего процесса. Если дочерний процесс завершится, скажем, через exit с параметром 4, то, когда родительский процесс продолжит работу, п будет содержать PID дочернего процесса, а status - значение 0x0400 (в языке С принято писать Ох перед шестнадцатерич-ной записью чисел, это соглашение всегда будет использоваться в книге).

В MINIX под процессы отводится часть памяти, которая, в свою очередь, делится на три сегмента: текстовый (то есть код программы), сегмент данных (переменные) и сегмент стека. Сегмент данных растет снизу вверх, а стек увеличивается сверху вниз, как показано на рис. 1.9. Между ними существует часть неиспользованного адресного пространства. Стек автоматически занимает такую часть этого участка памяти, какую необходимо, но расширение сегмента данных выполняется явным образом. Для этого используется специальный системный вызов Ьгк, задающий новый адрес для границы сегмента данных. Этот адрес может быть как больше текущего значения (сегмент растет), так и меньше (сегмент уменьшается). Но, конечно же, этот адрес должен быть меньше, чем указатель стека, так как в противном случае данные и стек могут перекрываться, что запрещено.

Для удобства программиста предлагается библиотечная процедура sbrk, также меняющая размер сегмента данных. Ее единственный параметр указьшает, на



сколько должен быть увеличен размер сегмента (чтобы уменьшить сегмент, нужно передавать отрицательные значения). Процедура работает так: с помощью Ьгк определяется текущий размер сегмента, затем вычисляется новый размер, после чего делается еще один системный вызов, запрашивающий требуемое количество байт. Как Ьгк, так и sbrk не являются частью стандарта POSIX и зависят от реализации.

Следующий системный вызов для работы с процессами является заодно и простейшим из них, это GETPID. Этот вызов просто возвращает идентификатор вызвавшего его процесса. Обратите внимание на то, что при вызове FORK значение идентификатора дочернего процесса получает только родительский процесс. Если дочернему процессу потребуется узнать собственный PID, ему придется использовать GETPID. Вызов GETGRP возвращает идентификатор группы процессов, в которую входит процесс, сделавший вызов. Вызов SETSID создает новую сессию и устанавливает PID группы процессов равным PID процесса, сделавшего вызов. Сессии относятся к необязательной возможности POSIX, называемой управлением задачами, которая не реализована в MINIX и не будет беспокоить нас в дальнейшем.

Последний системный вызов из этой группы, PTRACE, используется отладчиками для управления работой отлаживаемой программы. Он позволяет отладчику обращаться к памяти отлаживаемого процесса, а также управлять им другими способами.

Стек Промежуток Даннь

Текст

Адрес FFFF

0000

Рис. 1.9. Под процессы отводится три сегмента: текст, данные и стек

1.4.2. Системные вызовы для управления сигналами

Хотя обычно все взаимодействие между процессами запланировано, существуют ситуации, когда требуется незапланированное взаимодействие. Например, если пользователь случайно потребовал от текстового редактора показать содержимое очень большого файла и заметил свою ошибку. Должен существовать способ прервать работу редактора. В MINIX пользователь может нажать на клавиатуре клавишу Del, которая пошлет текстовому редактору соответствующий сигнал. Поймав его, редактор прекращает распечатку. Сигналы могут использоваться и для того, чтобы отслеживать некоторые аппаратные исключения, например



1 2 3 4 5 6 7 8 9 [ 10 ] 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187

© 2000 - 2024 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования.