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

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

с архитектурой компьютеров по маломощным микропроцессорам, могли не сталкиваться с подобными офаничениями.

В каталоге с кодами ядра есть еще несколько заголовочных файлов, но мы коснемся только двух из них. Прежде всего, это sconst.h, содержащий константы, требуемые ассемблерным кодом. Все они представляют собой величины смещений для доступа к полям структуры stackframe s, записанные в форме, пригодной для встраивания в ассемблерный код. Поскольку ассемблерный код не обрабатывается компилятором ANSI С, подобные определения проще вынести в отдельный файл. Кроме того, все такие определения зависят от аппаратной платформы, поэтому их изолирование упрощает процесс переноса MINIX на другие системы. Обратите внимание на то, что многие константы в этом файле представлены как предыдущая константа плюс W, где W - величина машинного слова. Это позволяет использовать один и тот же файл как для 16-разрядных, так и для 32-разрядных версий системы.

Здесь есть потенциальная проблема. Назначение заголовочных файлов в том, чтобы один раз записать правильные определения, а затем во многих местах не задумываться о деталях. Очевидно, повторяющиеся определения, как в файле sconst.h, нарушают это правило. Это, конечно, особый случай, но особым случаям и особое внимание. Когда вносятся изменения в этот файл или в procs.h, нужно следить за тем, чтобы все файлы всегда соответствовали друг другу.

Еще для нас представляет интерес файл assert.h. По стандарту POSIX должна быть доступна функция assert, предназначенная для тестирования значений в процессе выполнения и вывода сообщений об ошибке. Фактически POSIX требует наличия файла assert.h в каталоге include/, и там он действительно есть. Зачем же еще один? Причина в том, что, когда что-то не так с пользовательским процессом, операционная система может выполнить такие действия, как вывод сообщения на консоль. Но если неполадки в ядре, на обычные системные ресурсы рассчитывать нельзя. Поэтому ядро предоставляет собственные процедуры для выполнения assert и вывода сообщений, не зависящие от обычных системных библиотек.

В каталоге kernel/ есть несколько файлов, которые мы еще не рассматривали. Они необходимы для поддержки задач ввода/вывода и будут обсуждаться в следующей главе, которая им и посвящена. Тем не менее, прежде чем перейти к рассмотрению исполняемого кода, взглянем на файл table.c, который компилируется в объектный файл, содержащий глобальные переменные ядра. Определения большинства этих структур мы уже видели в файлах glo.h и proc.h. В начале файла, сразу после директив #include, устанавливается макрос TABLE. Как уже было сказано ранее, задание этого макроса приводит к тому, что макрос EXTERN разворачивается в пустую строку, и для всех данных, объявленных с директивой EXTERN, выделяется область памяти. Кроме переменных, декларированных в файлах glo.h и proc.h, здесь выделяется место для еще нескольких глобальных переменных, объявленных в tty.h. Последние необходимы задаче терминала.

В дополнение к переменным, объявленным в заголовочных файлах, есть еще два места, где выделяются глобальные ресурсы. Некоторые из определений делаются непосредственно в table.c, как, например, область стека для каждой из



задач. Вычислить полный объем стека помогают макросы ENABLE XXX (объявленные в include/minix/config.h), соответствующие каждой из необязательных задач. Эти макросы отвечают за то, какие задачи будут представлены в массиве tasktab, состоящем из структур tasktab (src/kernel/type.h). В нем выделяются элементы для каждого из процессов, стартующих в процессе инициализации системы, будь то задачи, серверы или пользовательские процессы (то есть init). Индекс в этом массиве однозначно связывает номер задачи и ассоциированные процедуры запуска. В tasktab также указываются необходимый для каждого из процессов объем стека и строка-идентификатор. Массив tasktab помещен сюда, а не в заголовочный файл, потому что трюк с EXTERN, позволяющий избегать повторных деклараций переменных, не работает для переменных с инициализатором. Другими словами, нельзя где угодно использовать подобный код:

extern int х = 3.

То же относится и к стеку.

Несмотря на все попытки изолировать друг от друга различные настройки в include/minix/config.h, существует возможность допустить ошибку, приводящую к несоответствию размера tasktab и величины NR TASKS. Чтобы убедиться, не допущена ли ошибка, в конце файла table.c делается проверка, в основе которой лежит небольшая хитрость. Объявляется массив dummy tasktab нереализуемого размера, который приводит к ошибке компилятора. Но если этот массив объявляется как extern, место для него здесь не выделяется и ошибки не происходит. Так как других ссылок на массив dummy tasktab больше нигде нет, он ничем не беспокоит компилятор.

Ассемблерный файл mpx386.s - еще одно место, где распределяется память под глобальные переменные. В этом файле (после метки sizes) в самое начало сегмента данных ядра помещается сигнатура (магическое число), необходимая для идентификации работоспособного ядра MINIX. Дополнительное место здесь выделяется при помощи псевдоинструкции .space. Такая практика позволяет физически поместить массив sizes в самом начале сегмента данных ядра, благодаря чему программе boot проще правильно позиционировать свои данные. Монитор загрузки находит сигнатуру и записывает на ее место (то есть в массив sizes) размеры различных частей ОС MINIX. Затем ядро использует эти данные для инициализации. С точки зрения ядра, этот массив уже инициализирован. Но данные, которые ядро в нем находит, недоступны в момент компиляции. Поэтому монитор загрузки помещает в массив sizes корректные значения перед тем, как передать управление ядру. Методика, когда одним программам необходимы знания внутренней структуры других программ, несколько необычна. Но момент времени между появлением питания и загрузкой операционной системы необычен сам по себе и требует необычных решений.

2.6.5. Начальная загрузка MINIX

Итак, сейчас почти что настало время перейти к рассмотрению исполняемого кода. Но перед тем, как мы займемся этим, потратим некоторое время на то, чтобы



понять, как MINIX зафужается в память. Загрузка, конечно же, производится с диска. На рис. 2.16 показано, как устроены дискеты и жесткие диски, разбитые на разделы.



Рис. 2.16. Дисковые структуры, используемые при начальной загрузке: а - диск без разбиения на разделы. Первый сектор является загрузочным блоком; б - диск, разбитый на разделы. В первом секторе находится главная загрузочная запись

При старте системы аппаратное обеспечение (а в действительности программа из ПЗУ) считывает первый сектор зафузочного диска и исполняет считанный код. На дискете, не разбитой на разделы, первый сектор содержит зафузоч-ный блок, который, в свою очередь, загружает программу boot, как показано на рис. 2.16, а. В отличие от дискет жесткие диски разбиты на разделы, и в первом секторе находится программа, которая считывает таблицу разделов (из того же первого сектора), а затем загружает и исполняет программу из первого сектора активного раздела, как это показано на рис. 2.16, б (один и только один сектор должен быть помечен как активный). Раздел, из которого загружается MINIX, имеет такую же структуру, как и загрузочная дискета MINIX, и в первом секторе находится загрузочный блок.

Реальная ситуация может быть сложнее, чем показано на рисунке, так как отдельные разделы могут быть разбиты на подразделы. В этом случае в первом секторе раздела будет находиться еще одна загрузочная запись, со своей таблицей подразделов. Как бы то ни было, в конце концов управление получит профамма из зафузочного сектора на устройстве, которое разделено на разделы. На дискетах, например, загрузочным сектором всегда является первый. 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.
Копирование материалов разрешено исключительно при условии цититирования.