Э. Таненбаум - Архитектура компьютера (1127755), страница 148
Текст из файла (страница 148)
Компоновщик находит все команды, которые обращаются к процедурам, и вставляет в них адреса этих процедур. Таблица 7.6 соответствует построенной на первом шаге таблице объектных модулей, представленных на рис. 7.4. В ней даются имя, длина и начальный адрес каждого модуля. На рис. 7.4, б показано, как выглядит адресное пространство после завершения работы компоновщика. Таблица 7.6. Имя, длина и начальный адрес каждого модуля на рис. 7.4 Длина Модуль Начальный адрес 100 400 600 500 1100 600 1600 Структура объектного модуля Объектные модули обычно состоят из шести частей: 1. Идентификация. 2. Таблица точек входа.
3. Таблица внешних ссылок 4. Машинные команды и константы. 5. Словарь перераспределения. 6. Конец модуля. В первой части содержатся имя модуля, некоторая информация, необходимая компоновщику (например, данные о длине различных частей модуля), а иногда дата ассемблирования.
Вторая часть объектного модуля — это список символов, определенных в модуле, вместе с их значениями. К этим символам могут обращаться другие модули. Например, если модуль состоит из процедуры Ь1дЬцд, то элемент таблицы будет содержать символьную строку "ЬздЬид" с соответствующим адресом. Программист, пишущий на языке ассемблера, с помощью директивы РЬВПС указывает, какие символические имена считаются точками входа. Третья часть объектного модуля состоит из списка символических имен, которые применяются в данном модуле, а определены в других модулях. Здесь также имеется еще один список, который показывает, какие именно символические имена используются теми или иными машинными командами.
Второй список нужен для того, чтобы компоновщик мог вставлять правильные адреса в команды, которые используют внешние имена. Процедура может вызывать другие независимо транслируемые процедуры, объявив имена вызываемых процедур внешними. Программист, пишущий на языке ассемблера, с помощью директивы ЕХТЕйй указывает, какие символы нужно объявить внешними. В некоторых компьютерах точки входа и внешние ссылки объединены в одной таблице. Компоновка и загрузка 585 Четвертая часть объектного модуля — машинные команды и константы. Это единственная часть объектного модуля, которая загружается в память для выполнения. Остальные пять частей используются компоновщиком, а затем отбрасываются еще до начала выполнения программы.
Пятая часть объектного модуля — это словарь перераспределения памяти. К командам, которые содержат адреса памяти, должна прибавляться константа перераспределения (см. рис. 7.4). Компоновщик сам не может определить, какие слова в части 4 содержат машинные команды, а какие — константы.
Поэтому в этой таблице содержится информация о том, какие адреса нужно перераспределять. Это может быть битовая таблица, где на каждый бит приходится потенциально перераспределяемый адрес, либо явный список перераспределяемых адресов. Шестая часть содержит указание на конец модуля, а иногда — контрольную сумму для определения ошибок, сделанных во время чтения модуля, и адрес, с которого должно начинаться исполнение. Большинству компоновщиков требуется два прохода. На первом проходе компоновщик считывает все объектные модули и строит таблицу имен и размеров модулей, а также глобальную таблицу символов, которая состоит из всех точек входа и внешних ссылок. На втором проходе модули поочередно считываются, перераспределяются в памяти и компонуются.
Время компоновки и динамическое перераспределение памяти В мультипрограммной системе программу можно считать в основную память, запустить на некоторое время, записать на диск, а затем снова считать в основную память для выполнения. В сложной системе с большим количеством программ трудно быть уверенным, что программа считывается каждый раз в одно и то же место в памяти.
На рис. 7.5 показано, что произойдет, если уже распределенная в памяти программа (см. рис. 7.4, б) будет загружена по адресу 400, а не по адресу 100, куда ее изначально поместил компоновщик. Все адреса памяти оказываются неправильными, и это при отсутствии какой-либо информации о распределении памяти. Даже если эта информация была бы доступна, перераспределять все адреса при каждой загрузке программы было бы расточительно. Проблема перемещения программ, уже скомпонованных и размещенных в памяти, имеет непосредственное отношение к моменту окончательного связывания символических имен с абсолютными адресами физической памяти.
В программе содержатся символические имена для адресов памяти (например, Вк ~). Время, в которое определяется адрес в основной памяти, соответствующий имени ~., называется временем связывания. Существует по крайней мере шесть вариантов времени связывания: 1. Когда программа пишется. 2. Когда программа транслируется. 3. Когда программа компонуется, но еще до загрузки. 586 Глава 7. Уровень ассемблера 4. Когда программа загружается. 5. Когда загружается базовый регистр, который используется для адресации.
6. Когда выполняется команда, содержащая требуемый адрес. Если команда, содержащая адрес, после связьиания перераспределяется в памяти, этот адрес оказывается неправильным (предполагается, что объект, на который происходит ссылка, тоже перемещается в памяти). Если транслятор генерирует исполняемый двоичный код, то связывание происходит во время трансляции, и программа должна быть запущена с адреса, указанного транслятором. При применении метода, описанного в предыдущем подразделе, символические имена в процессе компоновки связываются с абсолютными адресами, и именно по этой причине перемещать программы после компоновки нельзя (см. рис.
7.5). Здесь возникает два вопроса. Во-первых, когда символические имена связываются с виртуальными адресами7 Во-вторых, когда виртуальные адреса связываются с физическими адресами7 Только после двух этих операций процесс связывания можно считать завершенным. Когда компоновщик объединяет отдельные адресные пространства объектных модулей в единое линейное адресное пространство, он фактически создает виртуальное адресное пространство. Перераспределение в памяти и компоновка нужны для связывания символических имен с определенными виртуальными адресами. Это верно независимо от того, используется виртуальная память или нет. Предположим, что адресное пространство, изображенное на рис.
7 4, б, было разбито на страницы. Ясно, что виртуальные адреса, соответствующие символическим именам А, В, С и 1), уже определены, хотя их физические адреса будут зависеть от содержания таблицы страниц. Реально связывание символических имен с виртуальными адресами происходит в исполняемом двоичном коде.
Любой механизм, позволяющий легко изменять отображение виртуальных адресов на адреса основной физической памяти, упростит перемещение программы в основной памяти, даже если они уже связаны с виртуальным адресным пространством. Одним из таких механизмов является разбиение на страницы. Если программа перемещается в основной памяти, потребуется изменить только ее таблицу страниц, а не саму программу.
Второй механизм — применение регистра перераспределения времени исполнения. Компьютер С1)С 6600 и его последователи содержали такой регистр. В машинах, в которых используется эта технология перераспределения памяти, регистр всегда указывает на физический адрес начала текущей программы.
Аппаратно этот регистр добавляется ко всем адресам перед их распределением в памяти. Весь процесс перераспределения памяти является прозрачным для пользовательских программ. Пользовательские программы даже не подозревают, что происходит перераспределение памяти. Если программа перемещается, операционная система должна обновить регистр перераспределения. Такой механизм менее распространен, чем разбиение на страницы, поскольку перемещаться должна вся программа целиком (если есть отдельные регистры перераспределения для кода и данных, как, например, в процессоре 1пге! 8088, то объектами перемещения будут обе части программы). Компоновка и загрузка 887 2100 Обьектный модуль О 2000 1900 1800 1700 Объектный модуль С 1600 1500 1400 1300 1200 Объектный модуль В 1100 1000 900 800 700 Объектный модуль А 600 500 400 Рис.
7.5. Исполняемая программа с рис. 7.4, б, сдвинутая вверх на 300 адресов. Многие команды теперь обращаются по неправильным адресам памяти Третий механизм можно использовать в машинах, в которых реализована возможность обращения к памяти относительно счетчика команд. В этих машинах всякий раз, когда программа перемещается в основной памяти, достаточно обновить 588 Глава 7. Уровень ассемблера только счетчик команд. Программа, все обращения к памяти которой либо связаны со счетчиком команд, либо абсолютны (например, обращения по абсолютным адресам к регистрам устройств ввода-вывода), называется позиционно-независимой. Позиционно-независимую процедуру можно поместить в любом месте виртуального адресного пространства без необходимости перераспределять адреса памяти.
Динамическая компоновка Стратегия компоновки, которую мы обсуждали в подразделе «Задачи компоновгдика», имеет одну особенность: все процедуры, требуемые программе, компонуются до начала работы программы. Однако если мы будем устанавливать все связи до начала работы программы в компьютере с виртуальной памятью, то мы не используем всех возможностей виртуальной памяти.
Многие программы содержат процедуры, которые вызываются только при определенных обстоятельствах. Например, компиляторы содержат процедуры для компиляции редко используемых операторов или исправления редко встречающихся ошибок. Более гибкий способ компоновки раздельно скомпилированных процедур— компоновка каждой процедуры в тот момент, когда она впервые вызывается. Этот процесс называется динамической компоновкой. Впервые он был применен в системе М(П.Т1СЯ. Давайте рассмотрим примеры динамической компоновки в нескольких системах.