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

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

символов предваряет знаком подчеркивания, и компоновщику при связывании различных файлов требуются именно такие имена. Так как при использовании ассемблера никаких дополнительных знаков к именам не приписывается, здесь к именам всех функций на С необходимо явно добавлять знак подчеркивания, иначе компоновщик не сможет правильно связать объектные файлы. В свою очередь, cstart вызывает функцию для инициализации таблицы глобальных дескрипторов (Global Descriptor Table), центральной структуры данных, используемой 32-разрядными процессорами Intel для защиты памяти, и таблицы дескрипторов прерываний (Interrupt Descriptor Table), с помощью которой выбирается обработчик для каждого конкретного прерывания. После возврата из cstart сформированные таблицы активизируются при помощи команд Igdt и lidt. Следующая инструкция:

jmpbf CS SELECTOR:csinit

на первый взгляд выглядит как пустая операция, так как она передает управление точно в ту же точку, куда оно бы попало после серии инструкций пор. Но в действительности это важный элемент процесса инициализации, поскольку переход принуждает к использованию только что инициализированных структур данных. Далее следует ряд манипуляций с регистрами процессора, после чего выполняется переход к точке входа ядра main в файле main.с (именно переход, а не вызов). К этому моменту код инициализации в mpx386.s завершил свою работу. Остальная часть ассемблерного кода ответственна за запуск или перезапуск процессов и задач, обработку прерываний и прочие действия, которые для эффективности должны быть написаны на языке ассемблера. Мы вернемся к этим процедурам в следующем разделе.

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

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

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



дать О, настройки будут возвращены в исходное состояние. Кроме того, вызов intrjnit происходит в два щага, чтобы все произошедшие ранее прерывания не вызвали никаких побочных эффектов. Сначала в каждый контроллер прерываний записывается байт, блокирующий любую реакцию контроллера на внешние данные. Затем обработчиком всех прерываний устанавливается функция, которая просто печатает на экран сообщение о том, что запрещенное прерывание все же произошло. Позже записи в таблице прерываний будут одна за другой заменены указателями на обработчики прерываний по мере того, как запускаются задачи ввода/вывода. Установив обработчик, каждая из задач передает соответствующему контроллеру байт, разрешающий обработку прерываний.

Далее вызывается функция memjnit. Она инициализирует массив, содержащий информацию обо всех блоках памяти, доступных системе. Как и в случае с прерываниями, детали этого процесса зависят от аппаратуры, поэтому функция вынесена в отдельный файл, что освобождает main от непереносимого кода.

Большая часть кода main занимается созданием таблицы процессов, деятельностью, направленной на то, чтобы в момент начала работы планировщика регистры и карты памяти первых задач были бы установлены правильно. Все ячейки таблицы процессов помечаются как свободные, а цикл заполняет массив pproc addr, введенный для ускорения доступа к процессам. Следующий код:

(pproc addr + NR TASKS)[t] = гр:

можно было бы записать просто как

pproc addr[NR TASKS + t] = гр;

поскольку в с запись a[i] в точности эквивалентна записи *(a+i), и поэтому нет большой разницы, прибавлять ли смещение к адресу массива или к индексу. Но некоторые компиляторы генерируют немного лучший код, если постоянное смещение будет прибавляться к адресу массива.

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

Код всех задач находится в том же файле, что и ядро, а информация о требуемом для них размере стека имеется в массиве tasktab, описанном в файле table.c. Так как сами задачи скомпонованы в один файл с ядром и могут обращаться к коду и данным ядра, действительный размер задач не имеет большого смысла, поэтому поле размера задачи заполняется размером самого ядра. В массиве sizes хранятся размеры (в кликах) текста и данных ядра, менеджера памяти, файловой системы и init. Эта информация вписывается в поля данных ядра программой boot еще до того, как ядро получает управление, и ядро обращается к ним, как если бы эти значения были константами, заданными при компиляции. Первые два элемента массива содержат размеры текста и данных ядра, следующие



два относятся к менеджеру памяти и т. д. Если какая-либо из программ не различает пространства текста и данных, то размер текста устанавливается равным О, а сегменты склеиваются вместе как данные. В поле sizeindex таблицы процессов сначала записывается значение О, а в следующих строках в это поле помещается индекс соответствующей записи в таблице sizes.

Компьютеры IBM PC разработаны так, что область ПЗУ расположена в верхней части имеющегося адресного пространства, объем которого для процессоров 8088 составляет 1 Мбайт. У более современных компьютеров памяти почти всегда больше, чем у оригинальных IBM PC, но для совместимости область ПЗУ размещается у них в том же месте - между 640 Кбайт и 1 Мбайт. Таким образом, доступная для записи область памяти не является непрерывной. Монитор загрузки пытается по возможности зафузить серверы и init в верхнюю область памяти, чтобы оставался свободным большой блок доступных адресов. Код в директивах условной компиляции обеспечивает то, что использование верхней области памяти отражается в таблице процессов.

Две ячейки в таблице процессов заняты процессами, которые не должны планироваться обычным путем. Это процессы IDLE и HARDWARE. Первый - IDLE, это просто пустой цикл, который выполняется тогда, когда нет других процессов, готовых к выполнению. Процесс HARDWARE нужен для подсчета процессорного времени - время, которое процессор обслуживал прерывания, присваивается этому процессу. Все остальные процессы причисляются к соответствующим очередям планировщика. Вызываемая при этом функция, lock ready, устанавливает значение переменной ограничения доступа (семафора), switching, перед изменением очереди планировщика. В данном месте использование семафоров необязательно, но это стандартный метод и нет никаких причин писать здесь специальный код.

Последний этап инициализации - вызов функции aUoc segments. Она принадлежит к числу системных задач, но, конечно, никаких задач в этот момент не запущено. Это машинно-зависимая процедура, заполняющая поля положения, размера и ограничения доступа у каждого из сегментов. Для более старых процессоров Intel, не поддерживающих защищенный режим, определяется только положение сегмента. Если систему нужно будет перенести на процессор, выделяющий память иначе, процедура alloc segments должна быть переписана.

После инициализации в таблице процессов записей для всех задач, серверов и процесса init система почти готова к работе. В переменной bill ptr хранится ссылка на процесс, которому в текущий момент передается процессор. Эту переменную необходимо инициализировать каким-то значением, для чего подходит ссылка на процесс IDLE. Впоследствии это значение может быть изменено следующей вызванной функцией, lock pick proc. Итак, теперь все задачи готовы к запуску, и ссылка на пользовательский процесс, когда он запустится, будет помещена в переменную bilLptr. Еще одно назначение функции Lock pick ptr - поддерживать такое состояние переменной proc ptr, чтобы она всегда указывала на следующий процесс, который будет запущен. В данном случае, после вызова функции, в переменную proc ptr будет занесена ссылка на задачу консоли, которая всегда первой получает управление.



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