Главная страница Межпроцессное взаимодействие (состязание) Массив переменных окружения НОМЕ = /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. Начинается все с менеджера памяти, который решает, какой процесс
|
© 2000 - 2024 ULTRASONEX-AMFODENT.RU.
Копирование материалов разрешено исключительно при условии цититирования. |