Сист. прогр. Ч2 (1085771), страница 11
Текст из файла (страница 11)
Объединители делятся на два основных класса. Простейший тип объединителя вырабатывает загрузочный модуль, который очень похож на отдельную колоду абсолютного загрузчика. Это означает, что привязка программы к конкретным адресам памяти выполняется в процессе объединения подпрограмм. Поскольку модуль такого типа выглядит подобно моментальной фотографии, или образу памяти, его называют модулем образа памяти, а соответствующий объединитель получил название построитель образа памяти. Более сложный объединитель, так называемый редактор связей, сохраняет информацию, необходимую для перемещения модуля в памяти, так что результирующий модуль как единое целое может затем настраиваться и загружаться в произвольное место памяти. В этом случае загрузчик модуля наряду с загрузкой должен дополнительно выполнять функции распределения памяти и перемещения, но остается свободным от решения сложной задачи связывания объектных модулей и подпрограмм.
В обоих случаях программы, которые предполагается использовать многократно, обрабатываются только один раз, после чего могут быть загружены тогда, когда это потребуется. Построитель образа памяти относительно прост и обеспечивает высокое быстродействие. Редактор связей несколько более сложен, но обеспечивает большую гибкость при распределении памяти и загрузке.
ДИНАМИЧЕСКАЯ ЗАГРУЗКА
В каждой из рассмотренных схем загрузки предполагалось, что все необходимые подпрограммы загружаются в память одновременно. Если общее количество требуемой всеми этими подпрограммами памяти превышает размер доступной памяти, как это часто случается при использовании больших программ или при работе на вычислительной машине с маленькой памятью, возникают затруднения. Существует ряд аппаратных методов, таких, как страничная организация и сегментация, с помощью которых можно попытаться решить эту задачу. В этом разделе рассматривается традиционная схема динамической загрузки, основанная на последовательном использовании объединителя и загрузчика.
Обычно разные подпрограммы данной программы действительно требуются в разное время; например, первый и второй просмотры ассемблера взаимно исключают друг друга. Используя явное определение того, какая подпрограмма содержит обращения к другим подпрограммам, можно задать так называемую структуру с перекрытием (оверлейную структуру), которая указывает на взаимоисключающие подпрограммы. На рис. 14.4a показана программа, состоящая из пяти подпрограмм (А, В, С, D и Е), для которых в совокупности требуется 100К байтов памяти. Стрелки показывают, что подпрограмма А содержит обращения только к подпрограммам В, D и Е; подпрограмма В - только к подпрограммам С и Е; подпрограмма D - только к Е; подпрограммы С и Е не содержат обращений к другим подпрограммам. На рис. 14.4,6 показана взаимосвязь процедур. Обратите внимание на то, что процедуры В и D никогда не выполняются одновременно, это же справедливо и для процедур С и Е. Если загрузить в память только те процедуры, которые в действительности могут быть использованы в некоторый конкретный момент времени, требуемое количество памяти окажется равным определяемому самой длинной ветвью структуры с перекрытием. Для примера, представленного на рис. 14.4.в, требуемое количество окажется равным 70К байтов (процедуры А, В и С). На рис. 14.4.в показано распределение памяти для всех процедур, участвующих в рассматриваемой структуре с перекрытием.
Рис. 14.4. Другие схемы загрузки; а — обращения процедур к подпрограммам; б — структура с перекрытием; в — распределение памяти для процедур.
Для того чтобы структура с перекрытием оказалась работоспособной, необходимо, чтобы загрузчик модуля загружал различные процедуры по мере необходимости. Мы не будем рассматривать эти вопросы детально, ограничимся лишь замечанием, что разработано большое количество программ, выполняющих функции объединителя, для работы с программами, имеющими структуру с перекрытием.
Та часть загрузчика, которая принимает запросы и загружает необходимые процедуры, называется супервизором перекрытий. В целом подобная схема загрузки называется динамической загрузкой.
ДИНАМИЧЕСКОЕ СВЯЗЫВАНИЕ
Основным недостатком всех ранее рассмотренных схем является то обстоятельство, что если к некоторой подпрограмме имеется обращение, то даже в том случае, когда она вообще не используется при выполнении программы (например, программист поместил в программу вызов некоторой подпрограммы, но этот вызов никогда не выполняется из-за того, что команды условного перехода приводят к обходу этого предложения), загрузчик все равно выполняет лишнюю работу по связыванию этой подпрограммы с основной программой.
Более того, все рассмотренные схемы требовали от программиста явного указания тех процедур, которые могут потребоваться.
Весьма общий тип схемы загрузки, называется динамическим связыванием. Это механизм, с помощью которого загрузка и связывание по внешним ссылкам откладываются до выполнения программы. Это означает, что ассемблер вырабатывает текст и информацию для объединения и настройки адресов для исходной программы. Загрузчик загружает только основную программу. Когда основная программа выполняет команды перехода по внешним адресам или обращается к внешним переменным, т. е. к переменным, не определенным в данном сегменте, вызывается загрузчик. Только теперь выполняется фактическая загрузка в память сегмента, содержащего внешний адрес, к которому было сделано обращение.
Преимущество такой схемы состоит в том, что исключается лишняя работа загрузчика и в память загружаются только те сегменты программ или данных, к которым действительно происходит обращение. Другим достоинством является возможность динамической реконфигурации системы. Основной недостаток использования такой схемы состоит в значительных дополнительных затратах и сложностях, связанных с переносом большей части процесса объединения на время выполнения программы.
15. ЯЗЫКИ ПРОГРАММИРОВАНИЯ
Настоящая глава преследует три основные цели:
1. Отметить наиболее важные свойства языков высокого уровня.
2. Кратко обсудить использование этих свойств в системном
программировании.
3. Познакомить с языком С, как примером входного языка, используемого при создании компилятора.
Мы выбрали С, поскольку большинство языков являет его подмножеством и, кроме того, он является реальным языком, а не просто синтаксической конструкцией, специально ею данной для целей обучения. С обладает развитыми средствами, такими, как структуры и объединения данных, указатели, операции над строками, указателями и битами, динамическое распределение памяти, прерывания, каждое из которых играет очень важную роль в системном программировании и, возможно, станет стандартным средством в будущих языках, того, эти средства являются наиболее трудно реализуемыми создании компиляторов.
ЗНАЧЕНИЕ ЯЗЫКОВ ВЫСОКОГО УРОВНЯ
Языки высокого уровня приобретают все возрастающую poль в программировании. Они эффективно используются, для решения задач, которые до сих пор считалось необходимым программировать на языке ассемблера. Например, операционная система UNIX написана на С. Мы рассмотрим здесь некоторые преимущества использования таких языков.
Преимущества использования языков высокого уровня состоят в следующем:
1. Меньше исполнителей, проще управление.
Исследования показали, что, как правило, количество строк программы, которое программист может написать и отладить в единицу времени, не зависит от уровня применяемого языка программирования. А так как язык высокого уровня обычно приблизительно в 10 раз более выразителен, чем язык ассемблера, т. е. одно предложение языка высокого уровня эквивалентно 10 предложениям языка ассемблера, для решения одной и той же задачи требуется меньше программистов, тем самым упрощается руководство таким коллективом. Кроме того, работа в небольшой группе зачастую способствует дополнительному росту производительности.
2. Сокращение времени обучения.
Новый сотрудник быстрее разбирается в разрабатываемом проекте и начинает приносить реальную пользу, так как время обучения резко сокращается. Программы и модули, написанные на языке высокого уровня, компактнее и понятнее новому читателю.
3. Проще отладка.
У программиста появилась возможность больше внимания уделять самому алгоритму решения задачи и способам его реализации, не затрачивая дополнительных усилий на разного рода мелкие детали, как это бывает при использовании машинного языка, т. е. внимание концентрируется на содержании, а не на форме.
4. Хорошая документация.
Программа на С сама по себе является хорошим документом. Например, описание модуля может содержать структуру данных С и список операторов обработки этих данных. В этой связи уместно напомнить высказывание, что большинство операционных систем - это простые программы со сложными структурами данных.
5. Более высокая степень машинной независимости. Языки высокого уровня, такие, как С, менее чувствительны к особенностям аппаратуры по сравнению с машинными языками. Это дает возможность использовать одну и ту же программу на разных машинах. И, что более важно, это заставляет программиста творить, а не заниматься «жонглированием» двоичными разрядами.
6. Возможность повышения эффективности. Эффективность программирования в кодах, возможно, наиболее серьезное возражение против языков высокого уровня. Конечно же, с точки зрения микроуровня хороший программист на машинном языке может создать более эффективную программу, чем компилятор. Однако в системе, содержащей миллион команд, фактор эффективности кодирования не так очевиден. Опытный программист, перекодируя С программу в коды машины, может увеличить скорость решения или эффективность распределения памяти в два-три раза. Но с точки зрения макро-подхода к проблеме эффективности совершенно не очевидно, что истинная экономия сводится только к случайной экономии отдельных байтов или слов. Часто оказывается более важным то, что при использовании языка высокого уровня алгоритм решения задачи становится проще и нагляднее. К сказанному следует также добавить, что в действительности программист, создавая. программу на машинном языке, редко имеет время для ее всесторонней оптимизации.
7. Создание работоспособной версии системы.
Обычно очень трудно предсказать неэффективность той или иной части программного обеспечения. И, как правило, в кодах команд машины пишутся именно те сегменты, для которых скорость и эффективность выполнения является наиболее критичными. В больших системах обычно около 80 процентов времени выполнения тратится менее чем на 20 процентов команд программы (например, подпрограммы обработки ошибок и ряд других программ используются редко). К сожалению, нет алгоритма, с помощью которого можно было бы заранее определять наиболее критические участки программы. Поэтому важно как можно раньше получить какую-то законченную версию системы, выявить эти критические участки, а затем оптимизировать их.
Вот семь областей, в которых преимущества языков высокого уровня представляются наиболее очевидными. Для извлечения максимальной пользы из этих преимуществ, необходимы:
1) язык высокого уровня, пригодный для системного программирования;
2) компилятор с этого языка ;
3) вычислительная машина и операционная система, которые помогли бы реализовать такую систему программирования на языке высокого уровня .
СВОЙСТВА ЯЗЫКА ВЫСОКОГО УРОВНЯ
В этом разделе рассматриваются наиболее важные свойства языков высокого уровня, таких, как С или Паскаль. Некоторые из этих свойств встречались в ФОРТРАНе, КОБОЛе и АЛГОЛе (например, структуры данных в языке КОБОЛ, алгольная блочная структура и рекурсии, указатели в ГПЛ), хотя ни один из указанных языков не обладает ими всеми. Нам кажется, что эти свойства в будущих языках станут обычными и компиляторы завтрашнего дня должны обеспечивать их реализацию. Основная трудность при создании компиляторов заключается вовсе не в грамматическом разборе арифметических операторов, на что так часто обращается особое внимание в литературе. Скорее наиболее сложными вопросами являются распределение памяти, рекурсии и тесное взаимодействие с операционной системой.
С представляет попытку эффективного объединения наиболее привлекательных свойств более ранних языков, В последующих разделах рассматриваются такие важные с точки зрения системного программирования свойства языка С, как:
1. Развитые типы данных и их организация :
а) строки символов,
б) строки битов,
в) структуры данных,