1.Абсолютный загрузчик
2.Начальная загрузка
3.Перемещаюший загрузчик
4.Управляющие секции и связывание
модулей
5. Структура программ
6. Машинно-независимые средства
ассемблера
Абсолютный загрузчик - это системная программа, которая предназначена для загрузки абсолютного модуля в ОП и передачи управления ему сразу после загрузки.
Типовая команда ввода-вывода имеет поля: КОп : Напр. передачи : Кол. Байтов : Адр. на ВЗУ : Адр. ОП. Таким образом передача с ВЗУ заданного количества байтов в заданную область памяти может быть выполнена всего одной командой ввода-вывода.
Напомним структуру АМ. Он состоит из записей трёх типов - H, T и E. Их форматы:
H<ИМ (6)><НАМ (6)><ДМ (6)>
T<АЗТ (6)><КБТ (2)><Текст (2*КБТ)>
E<АТВ (6)>
Каждая запись начинается с обозначения
типа записи (ТЗ). Обозначения: ИМ - имя модуля, НАМ -
начальный адрес модуля, ДМ - длина модуля, АЗТ -
адрес загрузки текста, КБТ - количество байтов
текста, АТВ - адрес точки входа. В круглых скобках
указаны длины полей.
repeat Чт. ТЗ; case ТЗ of 'T': begin Чт. АЗТ и КБТ; Чт. КБТ байтов Т и их размещение по адр. АЗТ; end 'E': Чт. АТВ и безусл. переход по этому адресу; 'H': ; end until False;
Пример. 1) Исходный модуль
inc start 0 lda data add ed sta data hlt data resb 3 ed word 1 end inc
2) Размещение программы inc в ОП
0 1 2 3 4 5 6 7 8 9 A B C D E F 10 ... 00 00 0A 18 00 0D OC 00 0A FF ?? ?? ?? 00 00 01 lda add sta hlt data ed
3) АМ программы inc
H^inc ^000000^000010 T^000000^0A^00000A^18000D^0C000A^FF T^00000D^000001 E^000000
Здесь и далее знак ^ используется только
для наглядности и в АМ не присутствует.
Каким образом загружается сам АЗ? На заре ВТ вводили вручную с пульта ЦВМ. Потом для этой цели приспособили ПЗУ. Но оказалось, что у этого способа есть недостаток - невозможность изменения самого АЗ. В этой связи в архитектуре ЦВМ появилась команда первоначальной загрузки (КПЗ), которая загружает с заданного места ВЗУ всего несколько байтов (24..512) и передаёт управление только-что загруженному коду. В ходе его выполнения загружается продолжение загрузчика и так далее. Далее выполняется загрузка ОС.
Этот метод получил название метода раскрутки. Это один из фундаментальных методов СПО!
В ПК роль первой порции данных, вводимых при загрузке, выполняет первый сектор нулевой дорожки на нулевой стороне МД. Его называют Boot-сектор.
С появлением в 60-е годы
мультипрограммного принципа решения задач
оказалось необходимым перемещать те или иные
программные модули. Перемещение -
термин, определяющий процесс преобразования
программы с целью создания возможности её
выполнения в желаемой области памяти. Очевидный
способ - переассемблирование. Есть ли более
эффективный метод?
Пример.
100 + 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 ... 00 00 0A 18 00 0D OC 00 0A FF ?? ?? ?? 00 00 01 lda add sta hlt data ed 00 01 0A 18 01 0D OC 01 0A FF ?? ?? ?? 00 00 01 * ** * ** * **
Здесь звёздочками отмечены адресозависимые поля.
В АМ надо включить информацию об
адресозависимых полях. Можно предложить такой
формат записи для этой цели:
M<СМП (6)><ДМП (2)>,
где СМП - смещение модифицируемого поля, то есть
адрес относительно начала модуля, ДМП - длина
модифицируемого поля в полубайтах.
Предполагается, что модифицируемая часть всегда
находится в конце слова со смещением СМП.
В примере:
M^000000^03 M^000003^03 M^000006^03
Записи модификации разумно разместить в модуле после записей текста, так как необходимость в них возникает после загрузки текста в ОП.
repeat Чт. ТЗ; case ТЗ of 'H': begin Чт. НАМ и ДМ; Обращение к ОС с запросом о выделении непрерывного участка ОП длиной ДМ байтов и получение адреса загрузки модуля (АЗМ); П := АЗМ - НАМ; {П - перемещение} end 'T': begin Чт. АЗТ и КБТ; А := АЗТ + П; Копирование Т в область ОП с адресом А; end 'M': begin Чт. СМП и ДМП; А := АЗМ + СМП; W[A] := W[A] + П; {Сложение с учётом ДМП} end 'E': begin Чт. АТВ; А := АТВ + П; Передача управления по адресу А; end end until False;
Модуль, включающий информацию о
перемещении, принято называть объектным (ОМ).
Выводы:
- Необходимо переработать ассемблер - он должен
формировать ОМ, а не АМ.
- Целесообразно изменить архитектуру ЦВМ, чтобы
упростить перемещение программ. С этой целью
была введена относительная адресация, при
которой адресная часть команды состоит из двух
полей B : D . Целевой адрес ta = R[B] + D, где R - блок
регистров общего назначения процессора. Целевой
адрес зависит от значения в R[B]! Для перемещения
модуля достаточно изменить значение одного
регистра.
В частном случае вместо РОН используется PC: ta = Addr
+ PC, где Addr - адресная часть команды..
Если переделать учебную машину и ввести этот
метод адресации, то код нашего примера станет
таким:
0 1 2 3 4 5 6 7 8 9 A B C D E F 10 ... 00 00 07 18 00 07 OC 00 01 FF ?? ?? ?? 00 00 01 lda add sta hlt data ed
В архитектуре x86 эту функцию выполняет сегментация, которая одновременно решает и проблему расширения адресного пространства процессора. Полностью же избавиться от адресозависящих объектов в программе не удаётся, но их количество сокрашается на несколько порядков. Введение относительной адресации резко повысило эффективность перемещения, но надо помнить, что для этого прищлось усложнить архитектуру ЦВМ и увеличить цикл выполнения команды на время вычисления целевого адреса.
При увеличении программы мы приходим к идее её построения из модулей и реализации следующей схемы обработки программ:
ИМ1 -> А -> ОМ1 ->| ... |-> СЗ -> ЗМ ИМn -> А -> ОМn ->| БОМ1 БОМ2 ... БОМn ->|
На схеме: А - ассемблер, СЗ
- связывающий загрузчик, ИМi - i-ый ИМ, ОМi - i-ый ОМ,
ЗМ - загрузочный модуль в ОП. При такой схеме
обработки сокращается время на ассемблирование
при отладке программы и возникает возможность
использования библиотек ОМ. С другой стороны
увеличивается сложность СПО.
Управляющая секция - ИМ, который
транслируется в отдельный ОМ, который может
загружаться и перемещаться независимо от других
частей программы. УС, которой передаётся
управление после загрузки, называется главной.
Введём новые понятия. Внешнее имя - имя, которое определено в данном модуле так, что на него можно ссылаться в других модулях. Внешняя ссылка - имя, которое используется в данном модуле для обрашения к объекту определённому в другом модуле программы.
Для определения этих понятий на уровне языка ассемблера:
main csect sub csect extref inc extdef inc lda data ed word 1 jsub inc inc add ed sta data rsub hlt end data resb 3 end main
Размешение в ОП:
main: 0 1 2 3 4 5 6 7 8 9 A B C D ... 00 00 0A 48 00 00 0C 00 0A FF ?? ?? ?? lda jsub sta data sub: 0 1 2 3 4 5 6 7 ... 00 00 01 18 00 00 4C ed add rsub
Обратите внимание на значение адресной части команды jsub в модуле main: ассемблер, не имея никакой информации относительно значения внешнего имени data, ставит там 000.
extdef: D{<Вн. имя (6)><Отн. адр (6)>}
extref: R{<Вн. ссылка(6)>}
M<СМП (6)><ДМП (2)>+<Имя вн. ссылки (6)>
Пример:
H^main ^000000^00000D R^inc ^ T^000000^0A^00000A^480000^0C000A^FF M^000000^03 M^000003^03+inc M^000006^03 E 000000 H^sub ^000000^000007 D^inc ^000003 T^000000^07^000001^180000^4C M^000003^03 E
Общая идея состоит в построении плана ЗМ в ходе
первого прохода по всем ОМ программы, и их
загрузки, перемещения и связывания в ходе
второго просмотра..
1-ый проход:
1. Просматриваются записи H, R, D и E всех модулей.
Полученные данные организуются в две таблицы:
План ЗМ
Имя секции | Длина секции | Относительный адрес в ЗМ | Перемещение секции |
main | D | 0 | 100 |
sub | 7 | D | 10D |
Таблица внешних символов
Имя | Значение | Модуль определения |
main | 0 | main |
sub | 0 | sub |
inc | 3 | sub |
entry | 0 | main |
entry - стандартное обозначение точки входа в главную секцию.
2. Определяется длина ЗМ - 14h(20) байтов.
2-ой проход:
1. Формируется запрос к ОС на выделение участка
памяти. Пусть АЗП = 100h
2. Последовательно загружаются и перемещаются
секции. Адресозависящие поля модуля,
соответствующие внешним ссылкам, перемещаются
особым образом: для каждой записи модификации
вида
M<СМП><ДМП>+name
выполняется алгоритм
A := СМП + <адрес загрузки модуля>
W[А] := W[А] + <Значение name> + <Перемещение модуля
определения name>
100 + 0 1 2 3 4 5 6 7 8 9 A B C D E F 10 11 12 13 14 ... 00 00 0A 48 00 00 0C 00 0A FF ?? ?? ?? 00 00 01 18 00 00 4C lda jsub sta data ed add rsub * ** * ** * ** * ** +1 00 + 3 +1 00 +1 0D ---- 1 0D ---- ---- 1 0A ---- 1 0A 1 10 1 10
Обратите внимание на особенности определения значения адресной части команды jsub.
Логика работы СЗ похожа на логику работы ассемблера, если принять в качестве аналога команды в ассемблере объектный модуль в связывающем загрузчике.
Ещё одна системная программа - редактор
связей (компоновщик) - в
отличие от СЗ размещает ЗМ на ВЗУ, который в
последующем загружается в основную память с
помощью более простого загрузчика. Такая схема
обработки позволяет уменьшить время подготовки
программы к выполнению, но требует больших
затрат дисковой памяти.
Пример, в котором секция состоит из одних данных.
data csect resb 3 end main scect extref data ldx adata lda 0,x sta res hlt adata word data res resb 3 end main
Программы простой структуры полностью помещаются в основной памяти. Для создания возможности выполнения программ, размеры которых превышают размеры основной памяти, были созданы специальные механизмы, потребовавшие в свою очередь придания программам соответствующей структуры. Наиболее известны из них программы с перекрытиями (оверлейные) и программы динамической структуры.
Многие программы имеют структуру дерева. Если к тому же выполняется условие, что никакие два модуля из разных ветвей программы не взаимодействуют друг с другом, то появляется возможность загрузки модулей в ОП по мере необходимости и одновременно в ОП могут находиться только модули одной ветви дерева программы. См. рисунок.
В этом примере в ОП может находиться только одна из трёх комбинаций модулей - ACE, ACD или AD. Очевидно, что объём ОП, необходимый для выполнения такой программы, может быть существенно меньше, чем общая длина всех модулей программы.
Структуру перекрытий легко представить в символической форме, например так: ( A (B) ( C (D) (E) ) ). Модуль A, который должен всегда находиться в ОП, принято называть корневым. Область ОП, предназначенную для выполнения программ с перекрытиями, удобно представить состоящей из четырёх частей: модуля управления перекрытиями, области передачи управленя, корневого модуля и области перекрытий. Постоянное место размещения модулей пользователя - файл перекрытий на НМД. Они в нём могут храниться в виде образов в ОП. Подготовка всех модулей программы с перекрытиями это задача оверлейного редактора связей.
Общая идея управления программой с перекрытиями состоит в том, что необходимо выполнять перехват всех передач управления из одного модуля в другие. В ходе перехвата необходимо убедиться в том, что модуль, к которому происходит передача управления, находится в ОП. Если это не так, то модуль должен быть загружен в ОП. Перехват должен завершаться передачей управления в желаемую точку.
Область передачи управления и предназначена для организации перехвата управления. Пусть "j dest" - команда передачи управления из некоторого модуля в другой. Заменим адрес dest в команде модуля адресом dest' из области передачи управления. При входе в область передачи управления достаточно выполнить оператор addr := dest, после чего можно выполнить анализ состояния программы и принять решение о необходимости загрузки нужного модуля. В конце выполнения передачи управления достаточно выполнить команду "j addr", чтобы управление было передано туда, куда нужно.
Модуль управления перекрытиями должен
содержать следующую информацию:
- адреса загрузки всех модулей,
- дисковые адреса всех модулей,
- точки входов для каждлго модуля,
- адреса передачи управления для каждой точки
входа,
- индикаторы наличия модулей в памяти.
В тех случаях, когда заранее трудно спланировать порядок вызова модулей, возникает задача их динамической загрузки. В этом случае объектный модуль загружается, перемещается и связывается с остальной программой непосредственно в ходе её выполнения. Оказалось, что накладные расходы на эти операции можно уменьшить, если использовать специальный формат повторно редактируемых загрузочных модулей. Они сохраняют способность к перемещению за счёт присутствия в них специальной секции перемещений. Ярким примером могут служить DLL - библиотеки динамического связывания ( Dynamic Link Library), но их обсуждение уместнее вести в рамках курса по операционным системам.
Достижения в области загрузки программ приводят к необходимости пересмотреть некоторые положения в организации ассемблера. Мы уже рассмотрели вопрос организации многомодульных программ на ассемблере. Также разумеется, что обычным выходом ассемблера должен быть ОМ, способный к перемещению и связыванию. Полагая, что пути достижения этих целей достаточно очевидны, остановимся ещё на некоторых характерных машинно-независимых свойствах ассемблера.
Пусть, например, имя data представляет некоторое данное в программе на ассемблере. Тогда выражение data+6 можно интерпретировать как обращение к данному, адрес которого на 6 байтов больше значения имени data. Оказалось, что использование подобных адресных выражений довольно удобно и поэтому ассемблеры обычно предоставляют достаточно развитые средства для их записи, включая все арифметические операции и даже функции.
По своей способности изменять значение при перемещении модуля, для которого определено адресное выражение, принято различать абсолютные и перемещаемые выражения. Абсолютные выражения своего значения при перемещении модуля не изменяют. Перемещаемые же выражения изменяют значения при перемещении модуля и их в свою очередь подразделяют на простые, которые изменяют своё значение на величину перемещения модуля, и составные.
Приведём примеры. Пусть A и B - имена полей одного модуля, а C - другого. Тогда B - A - абсолютное выражение, A + 12 - простое перемещаемое выражение, 2 * A + B и A - C - составные выражения.
Больших проблем при реализации адресных выражений не возникает - достаточно обеспечить вычисление соответствующих значений при обработке операндов. Однако в случае, когда имена являются внешними необходимо решение затрагивающее организацию ОМ. Пусть A и B - два имени, которые определены в разных модулях. И пусть в третьем модуле требуется располагать значением B - A. Формально это значение можно получить с помощью адресной константы
extref A,B ... D word B-A
Так как в ходе ассемблирования этого
модуля ничего нельзя сказать о значении адресной
константы, то его разумно положить равным нулю,
возложив задачу его определения на связывающий
загрузчик. По сути ничего не остаётся как
передать ему всё выражение. Это можно сделать в
составе записи модификации:
M<Адрес мод. поля><Длина мод.
поля>+B-A .
Литералы или самоопределённые термы - это специальные обозначения констант, встретив которые в операнде команды, ассемблер автоматически генерирует для них соответствующие псевдокоманды word. Например конструкция
add c7 ... c3 word 7
при возможности использования литералов может быть записана в таком виде
add =f'7'
Здесь знак "=" может рассматриваться как признак литерала, а "f" - тип константы.
Реализация литералов основана на построении в ходе первого просмотра таблицы литералов, подобной таблице символов. Размещение самих констант из таблицы литералов может быть выполнено последовательно после первого просмотра с байта, непосредственно следующего за последним объектом программы. Далее легко определить адреса всех констант для использования их при генерации команд в ходе второго просмотра.
По некоторым причинам удобно текстуальный порядок следования операторов в программе сделать относительно независимым от их логического порядка. Это позволяют сделать блоки - специальным образом обозначенные участки программы. Приведём пример программы с использованием блоков.
Программа с блоками | и её логический эквивалент |
prg csect use a c1 word 1 va resb 3 vb resb 3 use lda c1 add va sub vb sta vd hlt use a vd resb 3 end prg |
prg csect lda c1 add va sub vb sta vd hlt c1 word 1 va resb 3 vb resb 3 vd resb 3 end prg |
Псевдокоманда use имеет синтаксис: use ИмяБлока . При первом появлении с заданным именем блока псевдокоманда use начинает (открывает) блок, а при последующих - продолжает его. Считается, что один блок, блок без имени, начинается сразу за псевдокомандой csect или start. Для его продолжения достаточно записать псевдокоманду use без имени. Логически блоки размещаются друг за другом в порядке их открытия, начиная, естественно, с блока без имени.
Реализация блоков основана на ведении отдельного счётчика адреса для каждого блока. Записи таблицы символов имеют три поля - имя, имя блока, значение. Оказывается, что, собранная в ходе первого просмотра информация в такой форме, вместе с значениями длин блоков вполне достаточна для определения логического адреса любого объекта программы.
История развития ассемблеров хранит огромное количество всевозможных изобретений подобного уровня. Мы же ограничимся приведёнными примерами, как наиболее существенными и поучительными.
Copyright г Барков Валерий Андреевич, 2000