...как только они вошли в Бесконечный Лес,
собранный джентельмен стал разбираться на части и принялся выплачивать
арендные деньги. Сначала он отправился к ногозаимодавцам и пришел
туда, где нанял левую ногу; он отдал ее владельцу, и заплатил за аренду,
и запрыгал к хозяину правой ноги; когда он вернул ее и полностью расплатился,
то перевернулся вниз головой и поскакал на руках. А. Тутуола |
Как мы видели в предыдущем разделе, объектные модули и библиотеки содержат
достаточно информации, чтобы собирать программу не только заранее, но
и непосредственно в момент загрузки. Этот способ, безусловно, требует
больших затрат процессорного времени, чем загрузка заранее собранного
кода, но дает и некоторые преимущества.
Программные модули в N9000
В этих архитектурах каждый объектный модуль соответствует одному модулю
в смысле языка высокого уровня Oberon (или NIL— N9000 Instrumental Language).
Далее мы будем описывать архитектуру системы N9000, поскольку автор с
ней лучше знаком.
Модуль может иметь не более 256 процедур, не более 256 переменных и ссылаться
не более чем на 256 других модулей. Код модуля является позиционно-независимым.
Данные модуля собраны в отдельный сегмент, и для каждой используемой копии
модуля, т. е. для каждой программы, которая этот модуль использует, создается
своя копия сегмента данных. В начале сегмента содер.
жится таблица переменных. Строки этой таблицы содержат либо значения_
для скалярных переменных, таких как целое число или указатель, либо адреса
в сегменте данных. Кроме того, сегмент данных содержит ссылку на сегмент
кода. Этот сегмент кода содержит в себе таблицу адресов точек входа всех
определенных в нем функций (рис. 3.13).
Рис. 3.13. Модуль N9000
Ссылки на все внешние модули собраны в таблицу, которая
также содержится в сегменте данных. Внешний модуль определяется началом
его сегмента данных
Все ссылки на объекты в данном модуле осуществляются через индекс в соответствующей
таблице. Ссылки на внешние модули имеют вид индекс
модуля:индекс объекта.
Сегмент данных не может содержать никаких статически инициализованных
данных. Вся инициализация производится специальной процедурой, которая
вызывается при каждом новом использовании модуля. Все эти свойства реализованы
в системе команд, поэтому накладные расходы относительно невелики.
Точнее, они невелики по сравнению с Intel 80286, но уже великоваты по
сравнению с i386, а по сравнению с современными RISC-процессорами или
системами типа транспьютера они становятся недопустимыми. Впрочем, в разд.
Разделяемые библиотеки мы увидим,
как подобная структура используется и на "обычных" процессорах.
Видно, что в системе может существовать несколько программ, обращающихся
к одним и тем же модулям и использующих одну и ту же копию кода модуля.
Проблем с абсолютной/относительной загрузкой вообще не возникает. Операционная
система ТС для N9000 была (автор не уверен, существует ли в настоящее
время хотя бы одна работоспособная машина этой архитектуры) основана на
сборке программ в момент загрузки. В системе имелась специальная команда
load — "загрузить все модули, используемые программой, и разместить
для них сегменты данных, но саму программу не запускать". В памяти
могло находиться одновременно несколько программ; при этом модули, используемые
несколькими из них, загружались в одном экземпляре. Это значительно ускоряло
работу. Например, можно было загрузить в память текстовый редактор, и
запуск его занимал бы доли секунды, вместо десятков секунд, которые нужны
для загрузки с жесткого диска фирмы ИЗОТ.
Любопытно, что когда началась реализация системы программирования на языке
С для этой машины, по ряду причин было решено не связываться с динамической
сборкой, а собирать обычные перемещаемые загрузочные модули.
На практике, подобная архитектура более характерна для байт-кодов — пре-компилированных
представлений программы, предназначенных для дальнейшей обработки интерпретатором
— Java Virtual Machine, интерпретатором Smalltalk и т. д., чем для аппаратно
реализованных систем команд. В таких системах команд порой используются
и более экстравагантные решения.
Архитектура AS/400
Система команд AS/400 (сервер баз данных среднего уровня, производимый
IBM) представляет собой машинно-независимый байт-код. При загрузке программы
этот байт-код компилируется в бинарный код "реального" процессора,
подобно тому, как это делается в большинстве современных реализаций Java
Virtual Machine. Точнее, наоборот, успех AS/400 был одним из важных факторов,
которые подвигли фирму Sun на разработку Java, поэтому правильнее говорить,
что современные JVM основаны на том же принципе компиляции при загрузке,
что и AS/400.
Это решение обеспечивает невысокую стоимость аппаратуры (современные AS/400
основаны на микропроцессорах архитектуры Power PC. Их более высокая по
сравнению с машинами, основанными на процессорах х86, цена обусловлена
более производительными системной шиной и периферией), высокую производительность
и возможность заменять архитектуру "реального" процессора без
перекомпиляции пользовательского программного обеспечения. За время выпуска
машин этой серии такая замена происходила дважды.
С другой стороны, отсутствие необходимости думать о том, как та или иная
возможность может быть реализована аппаратно, позволила принимать весыуа
авангардистские решения, на которые не решался никто из разработчиков
аппаратно реализованных CISC-архитектур, таких как VAX, Eclipse и даже
апофеоза CISC, Intel 432.
AS/400 имеет единое адресное пространство в том смысле, что адресуемыми
объектами являются не только сегменты кода и скалярных данных, но и объекты
реляционной СУБД, такие, как таблицы, индексы, курсоры и т. д.
Фактически, адресации подлежит вся память системы как оперативная, так
и дисковая. Адрес имеет два представления: его сегментная часть может
хранить имя адресуемого объекта (в контексте этой главы это можно уподобить
неразрешенной внешней ссылке) или собственно адрес, 64-битовое бинарное
значение. Перед тем, как обратиться к объекту, адрес-имя надо преобразовать
в бинарный формат, для чего существуют специальные команды [redbooks.ibm.com
sg242222.pdf].
Механизм этого преобразования выполняет работу и файловой системы, и редактора
связей, в том смысле, что и файловый доступ, и сборка программы содержат
важную фазу преобразования имен (соответственно, имен файлов и имен внешних
символов) в адреса, по которым можно осуществлять доступ.
Сборка при загрузке замедляет процесс загрузки программы (впрочем, для
современных процессоров это замедление вряд ли имеет большое значение),
но упрощает, с одной стороны, разделение кода, а с другой стороны — разработку
программ. Действительно, из классического цикла внесения изменения в программу:
редактирование текста — перекомпиляция — пересборка — перезагрузка (программы,
не обязательно всей системы) выпадает целая фаза. В случае большой программы
это может быть длительная фаза. В случае Novell Netware решающим оказывается
первое преимущество (рис. 3.14), в случае систем реального времени одинаково
важны оба.
В большинстве современных ОС, в действительности, сборка в момент загрузки
происходит не из объектных модулей, а из предварительно собранных разделяемых
библиотек. Такие библиотеки отличаются от обсуждавшихся в разд.
Объектные библиотеки, во-первых,
тем, что из них невозможно извлечь отдельный модуль: все межмодульные
ссылки внутри такой библиотеки разрешены, и ее необходимо всегда загружать
как целое; и, во-вторых, тем, что список символов, экспортируемых такой
библиотекой, не является объединением списков экспорта составляющих ее
объектных модулей. При сборке такой библиотеки необходимо указать, какие
из символов будут экспортироваться. Некоторые редакторы связей позволяют
на этом этапе создавать дополнительные символы.
Рис. 3.14. Фрагмент структуры взаимозависимостей между NLM (Netware Loadable Module) сервера Netware 4.11