Объединение модулей.
Свою работу компоновщик начинает с того, что считывает в оперативную память заголовки всех ОМ, указанных в приказе LINK. Далее он определяет, в каком порядке будут располагаться в окончательной программе сегменты из ее модулей и фиксирует эту информацию в общей таблице сегментов
(ОТС).
Эта таблица строится на основе таблиц сегментов (ТС) из модулей. Пусть, к примеру, в программе имеются два модуля М1 и М2 с такими ТС:
модуль М1: модуль М1:
сегмент нач.адрес размер класс сегмент нач.адрес размер класс
(в модуле) (в модуле)
------------------------------------ ------------------------------------
S1 0 1000 STACK Q1 0 22 DATA
S2 1000 8 DATA Q2 30 103 CODE
S3 1010 625 CODE
Тогда ОТС будет иметь следующий вид:
сегмент модуль начальный адрес размер класс
в модуле в пр-ме
----------------------------------------------------
S1 M1 0 0 1000 STACK
S2 M1 1000 1000 8 DATA
Q1 M2 0 1010 22 DATA
S3 M1 1010 1040 625 CODE
Q2 M2 30 1670 103 CODE
-------------------------------------------
длина программы: 1670 + 103 = 1773
В каждой строке ОТС собирается информация об одном сегменте, в ней указывается: имя сегмента, имя его модуля, его начальный адрес в модуле и в единой программе, его длина и класс. Вся эта информация, кроме адреса в программе, берется из ТС соответствующего модуля.
Первое, что должен сделать компоновщик, - это так расположить сегменты, чтобы сегменты одного класса оказались рядом. Решается эта задача следующим образом. Из заголовка ОМ, указанного в приказе LINK первым (у нас это M1), берется ТС и информация из нее переносится в ОТС (колонка "нач. адрес в программе" пока пуста). Затем берется ТС из ОМ, указанного в приказе LINK вторым (у нас - из M2), и последовательно рассматриваются перечисленные в ней сегменты. Первым идет сегмент Q1; смотрится, какого он класса и нет ли уже в ОТС сегмента того же класса. Есть, это S2. Значит, S2 и Q1 должны располагаться рядом. Поэтому строка с S3
сдвигается вниз, а после S2
записывается информация о Q1. Берется следующий сегмент Q2; он того же класса, что и сегмент S3; значит, S3 и Q2 должны быть расположены рядом. Однако в данном случае ничего в ОТС сдвигать не надо и информация о Q2 записывается в конец ОТС. Поскольку больше модулей и сегментов нет, то получившееся расположение сегментов является окончательным, именно так они и будут расположены в объединенной программе.
Итак, манипулируя строками из ТС разных модулей, компоновщик расположил сегменты этих модулей так, чтобы сегменты одного класса оказались рядом. При этом сами сегменты, т.е. их машинные коды, никак не переставляются, не переписываются с места на место (их вообще пока не в ОП), что было бы долго. Перестановки идут только на уровне строк таблиц сегментов, а это делается просто.
Далее компоновщик пересчитывает начальные адреса сегментов. Дело в том, что в ТС модулей указаны адреса сегментов относительно начала модулей, а теперь нужны адреса сегментов относительно начала всей программы. Такой пересчет делается просто. Первый сегмент S1, естественно, получает смещение 0. Поскольку он занимает 1000h байтов, то адрес первой свободной ячейки за ним равен 1000h. Это адрес кратен 16, поэтому с него можно начинать размещать следующий сегмент, поэтому этот адрес становится начальным адресом сегмента S2. Этот сегмент занимает 8 байтов, поэтому первый свободный адрес за ним - 1008h, но этот адрес не кратен 16 и с него нельзя начинать сегмент. Компоновщик берет ближайший адрес, кратный 16 (у нас это 1010h), и именно его делает начальным адресом следующего сегмента Q1. Аналогично по адресу и длине сегмента Q1
определяется начальный адрес для сегмента S3
(это 1040h), а по адресу и длине сегмента S3
определяется начальный адрес сегмента Q2
(это 1670h). Тем самым адреса всех сегментов относительно начала программы установлены.
Отметим попутно, что если сложить начальный адрес последнего сегмента (1670h) и длину этого сегмента (103h), то мы получим размер всей программы (1773h). Это число запоминается, оно еще пригодится.
Составив ОТС и тем самым определив, как внутри программы должны располагаться сегменты, компоновщик далее считывает с диска машинные коды (тела) ОМ в оперативную память и размещает их сегменты согласно указанному в ОТС порядку. Делается это так. В какое-то свободное место ОП считываются весь машинный код модуля M1, а затем сегменты этого модуля переносятся в соответствующие места той части ОП, где формируется машинный код всей программы (сколько переписывать, откуда и куда - все это определяется по ОТС). В нашем случае первые 1000h байтов из M1
переписываются в программу по адресу 0, затем 8 байтов из M1 начиная с его адреса 1000h переписываются в программу по адресу 1000h и, наконец, переписываются 625h байтов из M1 начиная с его адреса 1010h переписываются в программу по адресу 1040h. Затем аналогично поступают с сегментами из модуля M2:
M1 M M2
0 -----¬ 0 -----¬ 0 -----¬
¦ S1 ¦ ----> ¦ S1 ¦ ------- ¦ Q1 ¦
1000 ¦----¦ 1000 ¦----¦ ¦ 30 ¦----¦
¦ S2 ¦ ----> ¦ S2 ¦ ¦ ----- ¦ Q2 ¦
1010 ¦----¦ 1010 ¦----¦ ¦ ¦ L-----
¦ S3 ¦ -¬ ¦ Q1 ¦ <-- ¦
L----- ¦ 1040 ¦----¦ ¦
L--> ¦ S3 ¦ ¦
1670 ¦----¦ ¦
¦ Q2 ¦ <----
L-----
Собранные таким образом машинные коды сегментов и образуют машинный код единой программы. На этом этап объединения модулей завершен. После некоторой корректировки этот код станет телом ЗМ.