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

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

Теперь, несмотря на то что финальная стадия отправки сигнала происходит в задаче системы, пора подытожить то, что мы изучили. Когда сигнал должен быть обработан, необходимо действие, во многом сходное с обычным переключением контекстов, которое происходит, когда один процесс переводится в ожидание, а вместо него начинает выполняться другой. Но в таблице процессов есть только одно место, где можно сохранить все регистры процессора, необходимые для восстановления исходного состояния процесса. Достаточно ли этого? Для ответа на вопрос посмотрим на рис. 4.35, а. Здесь изображен в упрощенном виде стек процесса и часть его записи в таблице процессов в тот момент, когда процесс только что был приостановлен по факту прерывания. На время бездействия содержимое всех регистров копируется в запись stackframe этого процесса в части таблицы процессов, принадлежащей ядру. Данная ситуация будет соответствовать времени генерации сигнала, пока источник и приемник сигнала - разные процессы.

В процессе подготовки к обработке сигнала содержимое кадра стека копируется из таблицы процессов в собственный стек процесса в виде структуры sigcontext, тем самым сохраняется информация о состоянии. Затем в стек помещается структура sigframe, содержащая информацию, которая станет нужной sigreturn после завершения обработчика. Кроме того, в ней хранится и адрес самой библиотечной подпрограммы sigrtetrun, это ret addrl, и еще один адрес возврата, ret addr2, - адрес, с которого необходимо продолжить выполнение прерванной программы. Как мы увидим в дальнейшем, при нормальной работе второй адрес использоваться не должен.

Хотя обработчик и пишется программистом как обычная функция, он не вызывается инструкцией call. Чтобы началось исполнение кода обработчика, изменяется значение поля счетчика команд в кадре стека в таблице процессов, в результате, когда restart переводит процесс в состояние исполнения, начинает вьшолняться обработчик. На рис. 4.35, б показана ситуация, когда все эти приготовления завершены и обработчик уже занимается своим делом. Ну, а поскольку обработчик все-таки является обычной процедурой, когда он завершает работу, из стека извлекается адрес ret addrl и запускается sigreturn.

Ситуация, когда исполняется sigreturn, показана на рис. 4.35, в. Оставшаяся часть структуры sigframe используется как локальные переменные sigreturn. Одно из действий этого системного вызова направлено на такое изменение собственного указателя стека, когда при обычном возврате осуществляется переход на адрес ret addr2. Но в действительности sigreturn не завершается как обычные функции. Он, как и другие системные вызовы, передает право решать, какой процесс станет активным, планировщику в ядре. В конечном итоге, в какой-то момент времени получивший сигнал процесс будет выбран на выполнение. Оно продолжится с этого адреса, продублированного в оригинальном кадре стека процесса. Данный адрес помещается в стек для того, чтобы отладчик, проходя по программе, не испытывал проблем со стеком при трассировке обработчика сигнала. Благодаря таким манипуляциям на каждом этапе выполнения стек выглядит как обычный стек процесса, с собственными локальными переменными после адреса возврата.



Адрес возврата

Локальные переменные процесса

Фрейм стека (регистры

процессора) (исходные)

Адрес возврата

Локальные переменные процесса

Адрес возврата 2

Структура sigframe

Адрес возврата 1

Локальные переменные обработчика

Адрес возврата

Локальные переменные процесса

Фрейм стека (регистры

процессора) (исходные)

Адрес возврата 2

Локальные переменные sigreturn

Адрес возврата

Локальные переменные процесса

> Стек

Фрейм стека (регистры

процессора) (исходные)

Исходное состояние

Фрейм стека

(регистры процессора) (изменены, 1р = обработчик)

Во время работы обработчика

Фрейм стека

(регистры процессора) (изменены, ip = обработчик)

Выполняется sigreturn

Фрейм стека (регистры

процессора) (исходные)

Табпица процессов

Возврат в исходное состояние

Рис. 4.35. Стек процесса (сверху) и кадр стека (снизу), соответствующие различным фазам обработки сигнала: а - состояние, в котором процесс приостанавливается; б - состояние на начало исполнения обработчика; а - состояние во время исполнения sigretrun; г - состояние после того, как sigretrun завершил работу

Главное, что должен сделать вызов sigreturn, - это вернуть все на круги своя, к моменту получения сигнала. Важнее всего восстановить кадр стека процесса, для чего и используется его копия, сохраненная в собственном стеке процесса. В результате после завершения sigreturn процесс возвращается в состояние, соответствующее рис. 4.35, г.

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



ся из таблицы процессов. Если родитель пренебрегает своим долгом, процесс становится зомби. Кроме того, для некоторых типов сигналов (например, для SIGQUIT) менеджер памяти записывает в текущий каталог дамп памяти процесса.

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

В MINIX делается следующее: системный вызов завершается с кодом возврата EINTR, поэтому процесс может понять, что он был прерван сигналом. Узнать о том, что процесс заблокирован на системном вызове, не так просто. Менеджеру памяти придется спрашивать об этом файловую систему.

Такое поведение предполагается стандартом POSIX, который позволяет вызову read возвращать байты, считанные до прихода сигнала, хотя и не жестко предписано. Кроме того, возврат с кодом EINTR позволяет легко реализовать таймер и обработать SIGALARM, например, чтобы завершить операцию login и повесить трубку, если пользователь некоторое время (тайм-аут) не отвечает. При помощи синхронных часов эта задача решается с меньшими издержками, но синхронный таймер - новшество MINIX, и не столь поддается переносу, как сигналы. Также он доступен только серверам, обычные пользовательские процессы не вправе им пользоваться.

4.7.8. Прочие системные вызовы

Менеджер памяти отвечает еще за несколько простых системных вызовов. Две библиотечные функции, getuid и geteuid, пользуются одним и тем же системным вызовом getuid, который в ответном сообщении возвращает оба значения. Аналогично, системный вызов getuid также возвращает как реальное, так и действующее значения идентификаторов, нужные соответственно функциям getgid и getegid. Подобно работает и вызов getpid, возвращающий идентификатор самого процесса и его родителя, а при помощи вызовов setuid и setgid можно устанавливать как реальное, так и эффективное значения сразу, одним вызовом. В этой группе есть два дополнительных системных вызова, getpgrp и setsid. Первый возвращает идентификатор группы процессов, а второй устанавливает его равным текущему идентификатору процесса (PID). Эти семь функций - самые простые системные вызовы в MINIX.

Вызовы ptrace и reboot также выполняются в менеджере памяти. Первый из них помогает отлаживать программы. Второй оказывает влияние на многие аспекты системы. Первым действием этот вызов отправляет сигналы, чтобы завершить все процессы, кроме init, поэтому его код помещен именно в менеджер памяти. Чтобы завершить работу после того, как отправлены сигналы, в процесс выполнения вовлекаются файловая система и системная задача.



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