Управление памятью
Динамическая загрузка и динамическая линковка
Под динамической загрузкой понимается загрузка подпрограммы в память при первом обращении к ней из пользовательской программы. Это весьма полезный принцип, если требуется сэкономить память, поскольку никакой "лишний" код в этом случае в память не загружается. При статической линковке объем исполняемого кода может оказаться очень большим, именно за счет того, что к файлу бинарного кода добавлен полностью код всех используемых библиотек. При динамической загрузке никакой специальной поддержки от ОС не требуется на этапе разработки программы.
С динамической загрузкой вызываемых подпрограмм тесно связан другой родственный механизм – динамическая линковка: линковка во время исполнения программы. Разумеется, это не означает, что во время выполнения область кода программы расширяется, и к ней добавляется код динамически линкуемой подпрограммы. Используется иная схема. В коде программы размещается заглушка для исполнения (execution stub) – небольшой фрагмент кода, выполняющий системный вызов модуля ОС, размещающего в памяти код динамически линкуемой библиотечной подпрограммы. При первом вызове заглушка заменяет себя на код обращения по адресу динамически размещенной в памяти подпрограммы. Операционная система при вызове динамически линкуемого модуля должна проверить, размещен ли его код в адресном пространстве процесса. Очевидно, что динамическая линковка наиболее целесообразна для библиотек. Файл бинарного кода динамически линкуемой библиотеки имеет в системе UNIX расширение имени .so ( аббревиатура термина shared object ), в системе Windows – расширение имени .dll (аббревиатура от dynamically linked library ).
Возникает, однако, вовсе не философский вопрос: каково должно быть оптимальное соотношение статической и динамической линковки в системе? Следует ли ограничиваться только статической или только динамической загрузкой и линковкой? На наш взгляд, следует соблюдать "золотую середину". В операционных системах прошлых лет в этом отношении принимались подчас самые экзотические решения. В ОС "Эльбрус", например, разработчики пошли по чересчур радикальному, на наш взгляд, пути – вообще исключили статическую линковку и все независимые программы загружали только динамически (с помощью механизма ПРОГР, который в "Эльбрусе" назывался открытием программы,или динамическим знакомством ). К чему это привело на практике, хорошо помнят мои коллеги из СПбГУ – разработчики математических пакетов прикладных программ, которые мы с ними в 1980-х гг. переносили с ЕС ЭВМ на "Эльбрус". Они быстро освоили новую конструкцию ПРОГР и обращения ко всем независимо компилируемым модулям оформили именно таким образом. В результате очень сильно замедлилось суммарное время выполнения программы. Это и понятно: реализация каждой математической функции как динамически загружаемой программы – слишком "дорогая" операция, требующая вмешательства ОС, по крайней мере, при первом обращении к каждой такой программе, по сравнению с обычным обращением, например, к функции sin как к подпрограмме (процедуре), элементу статически линкуемой библиотеки, обычной машинной командой вызова процедуры.
Оверлейная структура программы
Как мы уже отмечали во вводных лекциях, в ранних ОС, в особенности – для персональных компьютеров, для пользовательского процесса были вынужденно введены очень жесткие ограничения по памяти, - например, в MS DOS – не более 640 килобайт. При таком дефиците основной памяти, если программа оказывается настолько велика, что полностью не помещается в память максимально разрешенного объема, необходимо предпринимать специальные меры при разработке программы, чтобы разбить ее на непересекающиеся группы модулей, такие. что в каждой группе модули логически взаимосвязаны и должны присутствовать в памяти одновременно, модули же разных групп не обязательно должны вместе загружаться в память. Во время исполнения такой программы должен использоваться специальный системный механизм, называемый оверлейная структура ( overlay ,дословно – наложение ), обеспечивающий поочередную загрузку в одну и ту же область памяти то одной, то другой исполняемой группы модулей. Простая программа, которая выполняет эти действия, называется драйвер оверлея ( overlay driver ).Интегрированная среда разработки Турбо Паскаль обеспечивала специальные опции компилятора, которые позволяли явно указывать модули, входящие в каждый оверлей.
Типичный для ранних компьютеров и ОС пример программы с оверлейной структурой – двухпросмотровый ассемблер. На первом просмотре он преобразует исходный ассемблерный код в промежуточное представление, которое программа второго просмотра ассемблера получает на входе. Полностью весь ассемблер (оба просмотра) в память не помещался, и пришлось применить оверлейную структуру. Данный пример иллюстрируется на рис. 15.3.
Ключевые термины
ld ( linker and loader) – редактор связей и загрузчик в системе UNIX.
Бинарный образ программы в памяти – файл, содержащий образ программы для ее считывания в память и запуска, формируемый загрузчиком.
Время (фаза) загрузки, или линковка (linking) - фаза вызова редактора связей и загрузчика для получения бинарного образа программы в памяти.
Время (фаза) компиляции – этап обработки программы, на котором исходный код программы компилируется в объектный модуль.
Входная очередь – совокупность процессов на диске, ожидающих размещения в памяти для выполнения своих программ.
Динамическая загрузка - загрузка подпрограммы в память при первом обращении к ней из пользовательской программы.
Динамическая линковка - линковка во время исполнения программы.
Динамически линкуемые библиотеки (dymanically linked libraries) – библиотеки подпрограмм, загружаемые в память во время исполнения, при первом обращении к ним из пользовательской программы.
Драйвер оверлея (overlay driver) – системная программа, выполняющая поочередную загрузку в одну и ту же область памяти то одной,то другой группы модулей из пользовательской программы.
Заглушка для исполнения (execution stub) – фрагмент кода в бинарном коде программы, выполняющий системный вызов модуля ОС, размещающего в памяти код динамически линкуемой библиотечной подпрограммы.
Загрузчик (loader) – системная программа, которая получает на вход загрузочный модуль и файлы с бинарными кодами системных библиотек, используемых программой и, объединяя код программы с кодами системных библиотек, создает бинарный образ программы в памяти.
Исходный код (source code) – код программы (в виде текстового файла) на языке высокого уровня или на языке ассемблера.
Линковка ( linking ) – то же, что и редактирование связей и загрузка.
Логический адрес - адрес, генерируемый процессором при выполнении машинной команды.
Объектный модуль – файл бинарного кода программы, генерируемый компилятором, содержащий выполняемые машинные команды и таблицу символов.
Оверлейная структура (overlay ) – организация программы при недостаточном объеме основной памяти, при которой система выполняет поочередную загрузку в одну и ту же область памяти то одной, то другой исполняемой группы модулей программы.
Перемещаемый код (relocatable code) – код, в котором адресация происходит относительно значения регистра перемещения,и адрес в памяти равен сумме значения регистра перемещения и адреса, вычисляемого в команде.
Регистр перемещения (relocation register) – регистр, содержащий начальный адрес области памяти, отведенной операционной системой для загруженной в память программы.
Редактирование связей (linking) – этап обработки программы, на котором редактор связей формирует из объектных модулей загрузочный модуль,разрешая все межмодульные ссылки.
Редактор связей (linkage editor) – систeмная программа, формирующая из объектных модулей загрузочный модуль,разрешая все межмодульные ссылки.
Редактор связей и загрузчик (linker and loader) - системная программа, в которой объединены функции редактора связей и загрузчика.
Связывание адресов во время загрузки (load-time) – схема адресации, при которой генерируемый код адресует данные и программу относительно регистра перемещения, значение которого определяется при загрузке программы.
Связывание адресов во время исполнения (runtime),или динамическое (позднее) связывание – гибкая схема адресации, при которой абсолютный адрес программы или данных определяется только во время выполнения программы; для реализации используется аппаратная поддержка отображения адресов – например, регистры базы и границы.
Связывание адресов во время компиляции (compile-time) - если адреса программы и данных в памяти априорно известны, генерация компилятором кода с абсолютными адресами.
Cтатически линкуемая библиотека – библиотека подпрограмм, код которой объединяется с кодом использующих ее объектных модулей в загрузочный модуль на этапе редактирования связей и загрузки.
Таблица символов – таблица в объектном модуле или загрузочном модуле, содержащая символы (переменных, процедур и др.), определенные или используемые в данном модуле кода.
Устройство управления памятью (Memory Management Unit – MMU) – модуль аппаратуры, выполняющий адресацию памяти и связанный с процессором и другими устройствами системной шиной; преобразует логические адреса в физические адреса.
Физический адрес – реальный адрес в памяти, который "видит" и "понимает" устройство управления памятью.