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

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

Когда процесс заканчивает выполнение, а его родитель ожидает его завершения (в каком бы порядке это ни произошло), для исполнения последних церемоний вызывается функция cleanup. У нее не слишком много работы. Родительский процесс, прохлаждаюшийся в ожидании в результате вызова wait или waitpid, пробуждается, ему передается PID завершившегося потомка, а также код выхода и состояние сигналов. Память потомка уже освобождена файловой системой, а ядро исключило его из планирования, поэтому все, что осталось сделать для завершения ритуала, - это очистить занимаемую процессом ячейку в таблице процессов.

4.8.4. Реализация системного вызова exec

Код системного вызова exec соответствует алгоритму из раздела 4.7.5. Этот код находится в функции do exec. Сделав несколько простых проверок правильности данных, менеджер памяти извлекает из пользовательского адресного пространства имя файла программы, которая будет запцущена как новый процесс. Перед тем как работать с файлом, файловой системе отправляется специальное сообщение, меняющее текущий каталог, чтобы полученный путь интерпретировался относительно рабочего каталога пользователя, а не каталога менеджера памяти.

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

Хотя все шаги управляются функцией do exec, многие действия вынесены во вспомогательные процедуры в файле ехес.с. Например, функция read header не только считывает заголовок и возвращает размеры сегментов, но и проверяет, является ли файл нормальным исполняемым файлом MINIX для данного типа процессора. Тип процессора учитывается в директивах условной компиляции при компиляции менеджера памяти. Кроме того, read header проверяет, умещаются ли сегменты в оперативной памяти.

Процедура new mem смотрит, достаточно ли доступной памяти для загрузки нового образа. Для этого она ищет свободный блок, достаточно большой для размещения данных и стека в том случае, если код разделяется, а если код не разделяется, ищется блок для размещения текста, данных и стека в совокупности. Этот алгоритм можно улучшить, если отдельно искать свободное место для кода и для стека с данными, так как эти сегменты не обязаны быть расположены вместе. Данное требование было обязательным для ранних версий MINIX. Если нужная память найдена, ранее занятая память освобождается и выделяется новый блок. Если памяти недостаточно, вызов exec завершается неудачей. Выделив



память, new mem обновляет карту памяти (mp seg) и передает ее ядру при помощи вызова библиотечной процедуры sys newmap.

Оставшаяся часть кода new mem связана с обнулением области неинициализированных глобальных переменных (сегмент bss), области зазора и сегмента стека. Многие компиляторы сами генерируют обнуляющий код, но благодаря тому, что очистка выполняется менеджером памяти, MINIX может работать с компиляторами, которые так не поступают. Обнуляется и участок между сегментами данных и стека, чтобы при расширении области данных посредством Ьгк выделяемая память уже содержала нули. Это не только дополнительное удобство для программиста, который может рассчитывать на то, что новые переменные инициализируются нулем, это еще и средство обеспечения защиты информации в многопользовательских системах, так как процесс, ранее занимавший память, мог содержать данные, которые нельзя показывать другим процессам.

Следующая процедура называется patch ptr, ее задача в исправлении значений указателей из формы, показанной на рис. 4.35, б, в форму на рис. 4.35, в. Алгоритм ее работы прост: найти в стеке все указатели и добавить к ним значение базового смещения.

Процедура load seg вызывается при исполнении exec один или два раза, для загрузки сегмента данных и иногда для загрузки сегмента текста (кода). Вместо того чтобы считывать сегмент блок за блоком и копировать блоки в адресное пространство пользователя, здесь используется трюк, при помощи которого файловая система может целиком загрузить сегмент напрямую. В результате вызов интерпретируется файловой системой несколько необычным образом, как будто чтение всего сегмента выполняется самим пользовательским процессом. Знают о том, что здесь что-то неладно, только несколько первых строк кода процедуры чтения из файловой системы. Благодаря такому маневру загрузка заметно ускоряется.

Последняя подпрограмма в файле ехес.с называется find share. Она по значению г-узла, номеру устройства и времени модификации ищет в таблице процессов процесс, с которым можно разделить код. Это простой последовательный поиск подходящего поля в таблице mproc. Конечно, при просмотре необходимо игнорировать сам процесс, для которого поиск заказан.

4.8.5. Реализация вызова Ьгк

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



Выполняет вызов подпрограмма do brk, но большая часть работы делается в процедуре adjust. Последняя проверяет, не пересеклись ли сегмент данных и стек. Если да, вызов Ьгк завершается с ошибкой, но процесс не уничтожается немедленно. При выполнении сравнения к верхней границе области данных добавляется значение фактора безопасности, SAFETY BYTES, поэтому остается надежда на то, что в стеке осталось еще немного места и процесс может поработать еще немного. Управление возвращается процессу, чтобы он хотя бы напечатал соответствующее сообщение и правильно завершился.

Обратите внимание: значение SAFETY BYTES задано в середине процедуры при помощи директивы #define. Это довольно необычно, традиционно подобные объявления размещаются в начале файла или в отдельных заголовочных файлах. Комментарий рядом поясняет, что программист нашел сложным выбор значения фактора безопасности. Без сомнения, описание было расположено таким необычным образом для привлечения внимания и, возможно, чтобы стимулировать дальнейшие эксперименты.

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

Последняя подпрограмма, size ok, проверяет, помещаются ли сегменты заданных размеров в адресном пространстве, как в кликах, так и в байтах. Чтобы не делать отдельную версию функции для 16-битного варианта MINIX, используются директивы условной компиляции. Эта функция вызывается только в двух местах, и замена этих вызовов на первую строку кода из 32-битного варианта даст более компактную программу, поскольку некоторые передаваемые в функцию аргументы не нужны для 32-битной версии.

4.8.6. Реализация сигналов

с сигналами связаны восемь системных вызовов, перечисленных в табл. 4.5. Как сами сигналы, так и эти системные вызовы обрабатываются кодом из файла signal.c. Там же находится код еще одного системного вызова, reboot, из-за того что этот вызов использует сигналы, чтобы завершить все процессы.

Таблица 4.5. Системные вызовы, относящиеся к сигналам

Системный вызов Назначение

sigaction Изменяется реакция на будущий сигнал

sigprocmask Модифицируется набор блокируемых сигналов

kill Отправка сигнала другому процессу

alarm Отправка сигнала ALRM самому себе после задержки

pause Приостановить работу до следующего сигнала

sigsuspend Набор блокируемых сигналов изменяется, затем вызывается pause

sigpending Определить набор текущих (то есть заблокированных) сигналов

sigreturn Восстановление после завершения обработчика сигнала



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