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

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

Массив переменных окружения

НОМЕ = /usr/ast

Массив аргументов

->0С ->f.c -►-1

\0 t S а 52 48 44 40 36 32 28 24 20 16 12 8 4 О

\0 t S а

8178

8174

8170

8167

8164

8188 8184 8180 8176 8172 8168 8164 8160 8156 8152 8148 8144 8140 8136

envp argv argc

\0 t s а

8178

8174

8170

8167

8164

8156

8136

return

8188 8184 8180 8176 8172 8168 8164 8160 8156 8152 8148 8144 8140 8136 8132 8128 8124 8120

Рис. 4.34. a - массивы, передаваемые подпрограмме execve; б - стек, построенный execve; а - стек после перемещения менеджером памяти; г - стек, который процедура увидит в начале выполнения

Когда стек в конце концов попадает в пользовательский процесс, он не помещается по нулевому виртуальному адресу. Вместо этого он записывается в конец выделенной области памяти, размер которой определяется по заголовку исполняемого файла. Для примера, предположим, что общий размер составляет 8192 байт, то есть последний доступный программе байт имеет адрес 8191. Тогда менеджер памяти так располагает в стеке указатели, что стек будет выглядеть, как показано на рис. 4.34, в.

Когда системный вызов exec завершится и программа начнет работу, стек придет в состояние, показанное на рис. 4.34, в, а указатель стека станет содержать значение 8136. Тем не менее остается другая нерешенная проблема. Главная процедура исполняемого файла, скорее всего, объявлена примерно так:

main (argc. argv. envp).

Поскольку рассматривается компилятор С, main является всего лишь одной из функций. Компилятор не знает о ее особой роли, поэтому строит ее код так, как если бы аргументы передавались ей по стандартному соглашению вызова языка С, когда последний аргумент идет в стеке первым. Поскольку значениями аргументов являются одно целое число и два указателя, ожидается, что они займут три слова, предшествующие адресу возврата. Естественно, показанный на рис. 4.34, в стек выглядит совершенно иначе.

Решение в том, чтобы выполнение программы не начиналось с main. Вместо нее управление сначала получает маленькая стартовая ассемблерная подпро-



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

Если программист в конце кода main не сделал вызов exit, то после ее завершения управление передается обратно в С runtime. Опять же, с точки зрения компилятора, main - обычная функция, и для возврата из нее компилятор генерирует стандартный код. Большая часть кода 32-битной версии crtso приведена в листинге 4.1. Комментарии поясняют выполняемые действия. Не вошел в этот листинг лишь код, загружающий из стека помещенные туда регистры, и несколько строк, устанавливающие флаг наличия/отсутствия математического сопроцессора.

Листинг 4.1. Ключевая часть С runtime, стартовой подпрограммы

push есх push edx push еах call ma1n push еах call exit

hit : Если вызов exit не удался, вызывается прерывание

push environ push argv push argc

татп(агдс. argv. envp) push exit status

4.7.6. Системный вызов brk

При помощи библиотечных подпрограмм brk и sbrk изменяется положение верхней границы сегмента данных. Первая из них берет абсолютное значение нового адреса (в байтах) и передает его системному вызову brk. Вторая вычисляет положительное или отрицательное приращение текущего положения, вычисляет новый размер и вызывает brk. Отдельного системного вызова sbrk в действительности нет.

Интересен вопрос: как sbrk определяет текущий размер, чтобы вычислить новый? Ответом на него является переменная brksize, в которой всегда хранится размер и откуда Ьгк и sbrk всегда могут его считать. Эта переменная инициализируется генерируемым компилятором символом, определяющим либо суммарный размер кода и данных (общее адресное пространство), либо только размер данных (раздельные адресные пространства). Имя и даже само существование такого символа привязаны к компилятору, поэтому вы не найдете его обозначение ни в одном из заголовочных файлов исходных кодов. Он задается в библиотеке, в файле brksize.S. Где именно он расположен, зависит от системы, но он будет в том же каталоге, что и crto.s.

Для менеджера памяти несложно выполнить вызов brk. Все, что ему нужно сделать, - это проверить, что сегмент умещается в адресном пространстве, обновить таблицы и уведомить ядро.



АЛЛ. Обработка сигналов

в главе 1 сигналы были определены как механизм передачи информации процессу, который не обязательно ждет ввода. Задается набор сигналов, и у каждого сигнала есть действие по умолчанию: либо завершить процесс, которому сигнал адресован, либо игнорировать сигнал. Если бы других альтернатив не было, обработку сигналов было бы просто понять и реализовать. Но при помоши системных вызовов процессы способны менять это поведение. Процесс может потребовать, чтобы любой сигнал (за исключением особого случая - SIGKILL) игнорировался. Более того, процесс может поймать сигнал, предписав, чтобы вместо действия по умолчанию был вызван указанный им обработчик сигнала (опять же, это не относится к SIGKILL). Таким образом, с точки зрения программиста есть два этапа работы с сигналами: подготовительная фаза, когда определяется ответная реакция на будущий сигнал, и ответная - когда сигнал генерируется и обрабатывается. Ответным действием может быть выполнение собственной подпрограммы-обработчика. В действительности, есть и третья фаза. Когда пользовательский обработчик завершается, специальный системный вызов восстанавливает нормальную работу получившего сигнал процесса. Программисту об этой третьей фазе знать не нужно, он пишет обработчики сигналов как обычные функции, а заботу о вызове и завершении обработчиков и управлении стеком берет на себя операционная система.

В подготовительной фазе программа вправе в любой момент изменить реакцию на сигнал при помощи нескольких системных вызовов. Самый общий из них - вызов sigaction, посредством которого можно указать, чтобы сигнал игнорировался, обрабатывался (при этом вместо действия по умолчанию для такого сигнала выполняется некоторый заданный пользователем код из самого процесса), или же восставить ответную реакцию по умолчанию. При помощи другого системного вызова, sigprocmask, сигнал можно заблокировать, тогда он будет поставлен в очередь и обработан только тогда, когда процесс разблокирует сигналы этого типа. Эти вызовы можно делать в любой момент даже из самой функции-обработчика. В MINIX действия подготовительной стадии осуществляются исключительно в менеджере памяти, так как все необходимые структуры данных расположены в его части таблицы процессов. Для каждого процесса в этой таблице имеется несколько переменных типа sigset t, в которых за каждый сигнал отвечает определенный бит. Одна из переменных хранит информацию о том, какие сигналы необходимо игнорировать, другая - какие сигналы обрабатываются и т. д. Кроме того, у каждого процесса есть массив структур sigaction, по одной на каждый сигнал. В этой структуре присутствует переменная, хранящая адрес пользовательского обработчика сигнала, а также дополнительное поле типа sigset t, где запоминается информация о сигналах, заблокированных во время исполнения другого обработчика. В поле адреса обработчика вместо адреса пользовательской функции могут храниться специальные данные, обозначающие, что данный сигнал должен быть игнорирован или обработан по умолчанию.

В генерации сигнала участвуют многие компоненты операционной системы MINIX. Начинается все с менеджера памяти, который решает, какой процесс



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