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

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

которая запускается после того, как ядро закончит свою инициализацию. Она расположена в файле main.с. В ней менеджер памяти сначала выполняет собственную инициализацию (функция mmjnit), а затем входит в цикл. В этом цикле сначала, чтобы дождаться входящего сообщения, делается вызов get worl<. Затем вызывается одна из функций do XXX, адрес которой берется из массива calLvec, а далее, при необходимости, отправляется ответное сообщение. Такая конструкция должна быть вам уже знакома, по тому же принципу работают задачи ввода/вывода.

Соответственно, две следующие функции, get work и reply, отвечают за реальные прием и отправку сообщений.

Последняя процедура в main.c - mmjnit, она инициализирует менеджер памяти. После того как система заработала, эта процедура больше не нужна. При помощи вызова get map в mmjnit запрашивается информация об использовании памяти ядром. В следующем далее цикле заполняются все записи задач и серверов в таблице процессов, после чего подготавливается запись, соответствующая процессу init. Затем менеджер памяти ждет сообщения от файловой системы. Как уже говорилось при обсуждении тупиков, это единственный случай, когда файловая система отправляет сообщение без предварительного запроса. Сообщение говорит о том, сколько памяти было задействовано под RAM-диск. Дальше при помощи вызова memjnit инициализируется список свободных блоков памяти ( дыр ), и вот теперь все готово к нормальной работе с памятью. Последний вызов также присваивает значения переменным total clicks и free clicks, после чего менеджер памяти может напечатать сообщение с информацией об общем объеме памяти, использовании памяти ядром, размере RAM-диска и объеме свободной памяти. Сделав это, менеджер памяти отправляет файловой системе ответ, выводя ее из состояния останова. В завершение задаче памяти передается адрес принадлежащей ядру части таблицы процессов, для того чтобы без проблем пользоваться командой ps.

4.8.3. Системные вызовы fork, exit и wait

Системные вызовы fork, exit и wait реализуются процедурами dojork, do mm exit и do wait соответственно, из состава файла forkexit.c. Процедура dojork руководствуется последовательностью действий, перечисленных в разделе 4.7.4. Обратите внимание, что второй вызов procsJn Lise резервирует для суперпользователя несколько последних ячеек в таблице процессов. При вычислении необходимой потомку памяти в сумму включается промежуток между данными и стеком, но не сегмент кода. Так делается потому, что сегмент кода либо разделяемый, либо, если адресные пространства кода и данных процесса объединены, его размер равен нулю. После того как нужный объем памяти подсчитан, чтобы получить ее, делается вызов alloc mem. Если память успешно выделена, базовые адреса родителя и потомка преобразуются из кликов в байты и, чтобы выполнить копирование, вызывается sys copy, которая отправляет сообщение задаче системы.

Теперь, когда память предоставлена, в таблице процессов ищется свободная ячейка. Более ранняя проверка, затрагивающая переменную procsJn use, гаран-



тирует, что такая ячейка найдется. Найденная ячейка заполняется, для этого в нее сначала копируются данные родительского процесса, а затем обновляются поля mp parent, mpjlags, mp seg, mp exitstatus и mp sigstatus. Некоторые из этих полей требуют специальной подготовки. Так, в поле mp flags обнуляется бит TRACED, поскольку потомок не наследует от родителя состояние трассировки. Поле mp seg является массивом, элементы которого соответствуют сегментам кода, данных и стека, и если обнаруживается, что у процесса адресные пространства разделены, ячейка, соответствующая сегменту кода, не меняется и продолжает указывать на код родительского процесса.

На следующем шаге процессу назначается идентификатор - PID. В выборе PID система отталкивается от значения переменной next pid, но тем не менее в обозримой вероятности не исключается одна проблема. После того как некому очень долгоживущему процессу назначен идентификатор (скажем, 20), может быть создано и уничтожено более 30 ООО процессов, в результате чего next pid вновь примет значение 20. Назначение процессу занятого идентификатора привело бы к сбою (представим, что позже кто-нибудь попытается отправить процессу под номером 20 сигнал), поэтому, чтобы удостовериться, что выбранный PID не занят, сканируется вся таблица процессов.

Вызовы sys fork и telLfs информируют ядро и файловую систему о рождении нового процесса, чтобы они могли обновить свои структуры таблицы процессов. (Все процедуры, имена которых начинаются с sys , служат для отправки задаче системы сообщений, запрашивающих различные услуги согласно табл. 3.20.) Создание или убиение процесса всегда инициируется менеджером памяти и только потом в число участников этого таинства допускаются ядро и файловая система.

Ответное сообщение процессу-потомку отправляется точно в конце кода do fork. Ответ родителю, содержащий идентификатор нового процесса, посылается из главного цикла в main, как обычный ответ на запрос.

Следующим системным вызовом, который выполняется менеджером памяти, является exit. Вызов принимает процедура do mm exit, но большую часть черновой работы делает mm exit, несколькими строками позже. Причина такого разделения труда в том, что функция mm exit также востребована, когда необходимо позаботиться о процессе, завершаемом по сигналу. Действия в этом случае те же самые, но другие аргументы, и такое разделение делается, по сути, для удобства.

Прежде всего mm exit останавливает таймер, если у процесса он активен. Затем файловой системе и ядру сообщается, что этот процесс более не может быть запущен на выполнение. Вызов функции sys xit отправляет задаче системы сообщение, по получении которого она помечает процесс, исключая его из планирования. Затем освобождается память. Функция find share определяет, разделяется ли сегмент кода с другими программами. Если нет, сегмент кода освобождается вызовом free mem. Следом за этим аналогичный вызов освобождает память, занимаемую стеком и данными. Иногда всю память можно освободить одним вызовом free mem, но выгода того не стоит. Если родитель процесса ожидает, то, чтобы освободить ячейку, вызывается cleanup. Если нет, процесс становится зомби, это индицируется флагом HANGING в поле mp flags. Полностью уничтожив



процесс или превратив его в зомби, менеджер памяти ищет в таблице процессов его потомков. Если таковые обнаруживаются, они делаются потомками процесса init. Когда init в ожидании и один из его потомков становится зомби, для этого потомка вызывается cleanup. Таким образом обрабатываются ситуации, подобные показанной на рис. 4.36, а. На этом рисунке мы видим процесс 12, который собирается завершиться, и его родителя, процесс 7, находящегося в ожидании. Чтобы избавиться от процесса 12, для него будет вызвана cleanup, в результате процессы 52 и 53 станут потомками init. Эта ситуация продемонстрирована на рис. 4.36, б. В результате оказывается, что процесс 53, который уже завершился, является потомком процесса, выполняющего wait. Следовательно, записи о нем будут корректно очищены.


Ожидание

Ожидание

Завершение

Зомби


Рис. 4.3в. 8 - процесс 12 собирается завершиться; б - ситуация после его завершения

Когда родитель делает вызов wait или waitpid, управление передается следующей функции, do waitpid. Параметры этих двух системных вызовов различны, ожидаемые действия также различны, но благодаря специальной настройке значений двух внутренних переменных (options и pidarg) функция do waitpid может выполнять оба вызова. После того как присвоены значения внутренним переменным, стартует цикл, в котором просматривается вся таблица процессов, с целью выяснить, есть ли вообще у процесса потомки. Если есть, проверяется, есть ли среди них зомби, которых теперь можно добить. Когда обнаруживается зомбированный процесс, он уничтожается и do waitpid возвращает управление (второй оператор if внутри цикла). Так как ответное сообщение отправляется из функции cleanup, а не из цикла в main, устанавливается флаг dont reply. Если обнаруживается трассируемый потомок, do waitpid передает управление назад, отправив предварительно ответное сообщение, говорящее, что процесс остановлен. Флаг dont reply при этом также устанавливается, ради предотвращения отправки второго ответного сообщения из main.

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



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 - 2018 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования.