Книга Чистая архитектура. Искусство разработки программного обеспечения, страница 22. Автор книги Роберт Сесил Мартин

Разделитель для чтения книг в онлайн библиотеке

Онлайн книга «Чистая архитектура. Искусство разработки программного обеспечения»

Cтраница 22
Краткая история компонентов

На заре разработки программного обеспечения программисты сами определяли организацию памяти в своих программах. В первых строках кода часто присутствовала инструкция origin, объявлявшая начальный адрес в памяти для загрузки программы.

Взгляните не следующую простую программу для PDP-8. Она состоит из подпрограммы с именем GETSTR, которая принимает ввод с клавиатуры в виде строки и сохраняет его в буфер. В ней также имеется короткий модульный тест для проверки GETSTR.

        *200

        TLS

START,   CLA

        TAD BUFR

        JMS GETSTR

        CLA

        TAD BUFR

        JMS PUTSTR

        JMP START


BUFR,   3000


GETSTR, 0

        DCA PTR

NXTCH,  KSF

        JMP -1

        KRB

        DCA I PTR

        TAD I PTR

        AND K177

        ISZ PTR

        TAD MCR

        SZA

        JMP NXTCH


K177,   177

MCR,    -15

Обратите внимание на команду *200 в начале программы. Она сообщает компилятору, что сгенерированный им код будет загружаться в память, начиная с адреса 2008 (в восьмеричной системе счисления).

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

Как осуществлялся доступ к библиотечным функциям в те дни? Это иллюстрирует предыдущий пример. Программисты включали исходный код библиотек в свои программы и компилировали их как одно целое [26]. Библиотеки хранились в исходном коде, а не в двоичном.

Проблема такого подхода в ту эпоху состояла в том, что устройства были медленными, а память стоила дорого, и поэтому ее объем был ограничен. Компиляторам требовалось выполнить несколько проходов по исходному коду, но памяти было недостаточно, чтобы уместить в ней весь исходный код. Как следствие, компилятору приходилось несколько раз читать исходный код, используя медленные устройства.

Это требовало много времени, и чем больше была библиотека, тем дольше работал компилятор. Компиляция большой программы могла длиться часами.

Чтобы сократить время компиляции, программисты отделяли исходный код библиотек от приложений. Компилировали эти библиотеки отдельно и загружали готовый двоичный код в известный адрес — например, 20008. Они создавали таблицу символов для библиотеки и компилировали ее со своим прикладным кодом. Когда им требовалось запустить приложение, они загружали двоичный код библиотеки [27], а затем загружали приложение. Память была организована, как показано на рис. 12.1.


Чистая архитектура. Искусство разработки программного обеспечения

Рис. 12.1. Организация памяти на заре программирования


Такой прием прекрасно работал, пока приложение умещалось в объем между адресами 00008 и 17778. Но если размер приложения оказывался больше отведенного адресного пространства, программисту приходилось разбивать программу на два сегмента, располагавшихся по обеим сторонам сегмента с библиотекой (рис. 12.2).


Чистая архитектура. Искусство разработки программного обеспечения

Рис. 12.2. Деление приложения на два сегмента


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

Совершенно понятно, что с этим нужно было что-то делать.

Перемещаемость

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

Теперь программист мог указать загрузчику начальный адрес для загрузки библиотеки и адрес для загрузки приложения. Фактически загрузчик мог принять несколько фрагментов двоичного кода и просто загрузить их в память друг за другом, корректируя адреса ссылок в них. Это позволило программистам загружать только необходимые функции.

В компилятор также была встроена возможность, позволяющая генерировать имена функций в виде метаданных. Если программа вызывала библиотечную функцию, компилятор определял ее имя как внешнюю ссылку. Если программа объявляла библиотечную функцию, компилятор определял ее имя как внешнее определение. После этого, зная адреса загрузки функций, загрузчик связывал внешние ссылки с внешними определениями.

Так родился связывающий загрузчик.

Компоновщики

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

Вход
Поиск по сайту
Ищем:
Календарь
Навигация