Инструменты пользователя

Инструменты сайта


examination:os:question11

Процессы компиляции, связывания и загрузки.

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

Компиляция

Несмотря на то, что каждый компьютер может воспринимать только свой машинный язык, практически все программы пишутся на языках высокого уровня. Первой ступенью в процессе создания исполняемых про¬грамм является трансляция программы на языке высокого уровня в про¬грамму на машинном языке. Компилятор принимает исходную програм¬му (source code), написанную на языке высокого уровня, и выдает объектную программу (object code), содержащую подлежащие выполне¬нию команды на машинном языке. Практически все коммерчески доступ¬ные программы распространяются в виде объектного кода, но некоторая часть программных продуктов (например, открытое программное обеспе¬чение) содержит и исходный код. Весь процесс компиляции можно разбить на ряд этапов. Один из вари¬антов представления

На ка¬ждом этапе программа модифицируется таким образом, чтобы результат модификации мог быть обработан на следующем этапе, и так до тех пор, пока она не будет оттранслирована в машинный код. Сначала исходная программа поступает на лексер (lexer), также называемый лексическим анализатором или сканером (lexical analyzer, lexical scanner), который разделяет поток символов исходного текста программы на лексемы(token). Примерами лексем являются ключевые слова (if, else и int), иденти-фикаторы (именованные переменные и константы), операторы (—, +, * и /), а также знаки пунктуации (точка с запятой). Лексический анализатор передает порожденный поток лексем на син¬таксический анализатор (parser или syntax analyzer), который складыва¬ет их в синтаксически правильные операторы. Генератор промежуточного кода (Intermediate code generator) трансформирует данную синтаксиче¬скую структуру в поток простых команд, сходных с языком ассемблера (несмотря на то, что он не определяет регистры, используемые для каждой операции). Оптимизатор (optimizer) пытается повысить производитель¬ность выполнения кода и снизить потребности программы в памяти. На за¬ключительном этапе генератор кода (code generator) генерирует объект¬ный файл, содержащий команды на машинном языке. ние вводом/выводом данных или генерация последовательности случай¬ных чисел, объединены в предварительно скомпилированные модули, на¬зываемые библиотеками (libraries). Связывание (linking) — это процесс интеграции различных модулей, на которые ссылается программа, в еди¬ный исполняемый модуль программы. Когда программа скомпилируетея, соответствующий ей объектный модуль будет содержать данные и команды программы, полученные из ее исходного файла. Если программа обращалась к функциям и данным другого модуля, компилятор транслирует эти обращения во внешние ссылки (external reference). Если же программа предоставляет доступ к своим данным и функ¬циям другим программам, каждый доступный элемент представляется как внешнее имя (external name). Объектные модули хранят эти внешние имена и ссылки в структуре данных, называемой таблицей имен (symbol table). Интегральный модуль, созданный компоновщиком, называется за¬грузочным модулем (load module). На вход компоновщика могут подаваться объектные модули, загрузочные модули и управляющие операторы, напри¬мер, задающие расположение библиотечных файлов.

Связывание

Как правило, программы состоят из нескольких независимо разработан¬ных подпрограмм, которые называются модулями (modules). Функции, выполняющие такие стандартные компьютерные операции, как управле- Как правило, на вход компоновщика подается несколько объектных файлов, которые формируют единую программу. Эти объектные файлы обычно определяют расположение своих данных и команд, используя ад¬реса, которые отсчитываются от начала файла и называются относитель¬ными адресами (relative addresses).

Как цравило, связывание осуществляется в два прохода. Первый про¬ход определяет размер каждого модуля и их объединения и создает табли¬цу имен. Таблица имен связывает каждый символ (например, имя пере¬менной) с адресом, что позволяет компоновщику выполнить разрешение ссылок. На втором проходе КОМПОНОВЩИК присваивает адреса блокам дан¬ных и команд, а также выполняет разрешение внешних ссылок на симво¬лы . Компоновщики также выполняют разрешение внешних ссылок (symbol resolution), при котором внешние ссылки одного модуля привязываются к соответствующим внешним именам другого модуля45- 46. На рис. 2.11 внешняя ссылка на символ С в объектном модуле 2 связывается с внешним именем С из объектного модуля 1. В случае если внешняя ссылка привязы-' вается к соответствующему имени в другом модуле, адрес внешней ссылки должен быть модифицирован, чтобы удовлетворять размещению адресуемо¬го объекта в скомпонованном модуле. входных данных при следующем сеансе работы компоновщика, в загрузоч¬ный модуль включается таблица имен, в которой все символы являются внешними именами. Следует отметить, что на рисунке 2.11 внешняя ссылка на символ Y отсутствует в таблице имен загрузочного модуля, так как для нее уже выполнено разрешение. Момент выполнения компоновки программы зависит от среды. Про¬грамма может быть скомпонована во время компиляции, если програм¬мист обладает всем необходимым кодом в исходном файле, для того чтобы разрешить все ссылки на внешние имена. При этом выполняется поиск в исходном коде всех внешних ссылок и размещения соответствующих внешних символов в итоговом объектном файле. Как правило, этот метод не позволяет получить полноценный результат, поскольку многие про¬граммы полагаются на совместно используемые библиотеки (shared library), содержащие совокупность функций, которые могут быть совмест¬но использованы различными процессами. Многие программы могут ссы¬латься на одни и те же функции (такие как функции библиотеки, которые управляют входными и выходными потоками), не включая их в свой объ¬ектный код. Как правило, этот тип связывания осуществляется после ком¬пиляции, но перед загрузкой. Мы рассмотрим в разделе «Учебный пример: Операционная система Mach», как совместно используемые библиотеки позволяют микроядру системы Mach эмулировать множество операцион¬ных систем. Этот же процесс может быть выполнен во время загрузки. Иногда и компоновка (связывание) и загрузка могут выполнять¬ся одним приложением, которое называется связывающим загрузчиком (linking loader). Связывание также может происходить во время выполне¬ния программы — процесс, называемый динамическим связыванием (dynamic linking). В этом случае разрешение ссылок на внешние функции не выполняется, пока процесс не загрузится в память или не выдаст запрос функции. Это полезно для больших программ, использующих программы сторонних разработчиков, так как при таком подходе нет необходимости пе¬рекомпоновывать динамически связанную программу, когда используемая ей библиотека модифицируется 60. Более того, так как динамически связан¬ные программы не связываются, пока не будут загружены в память, код со¬вместно используемой библиотеки может храниться отдельно от кода дру¬гих программ. Динамическое связывание также позволяет экономить место на вторичных запоминающих устройствах, поскольку только одна копия совместно используемой библиотеки хранится для любого количества ис¬пользующих ее программ.

Загрузка

После того как компоновщик создал загрузочный модуль, он передает его программе загрузчику (loader). Загрузчик отвечает за размещение каждого блока команд и данных по определенному адресу в памяти — процесс, назы¬ваемый адресным связыванием (address binding'). Существует несколько методов загрузки программ в основную память, большая часть из которых имеют значение только для систем, не поддерживающих виртуальную па¬мять. Если загрузочный модуль уже определяет физические адреса в памя: ти, загрузчик просто размещает блоки команд и данных по адресам, опреде¬ленным программистом или компилятором (при условии, что эти адреса па¬мяти доступны). Данный процесс называется абсолютной загрузкой (absolute loading). Перемещающая (настраивающая) загрузка (relocatable loading) выполняется в случае, если в загрузочном модуле содержатся отно¬сительные адреса, которые необходимо трансформировать в абсолютные ад¬реса памяти. Загрузчик отвечает за запрашивание блока памяти для размещения в нем программы, а затем, за преобразование адресов программы, чтобы та соответствовала своему расположению в памяти. На рисунке операционная система выделила программе блок памяти, начиная с адреса 10000. Когда программа загружается, загрузчик должен прибавить 10000 к каждому адресу в загрузочном модуле. За¬грузчик меняет исходный относительный адрес 450 переменной Example на 10450. Динамическая загрузка (dynamic loading) — это подход, при котором программные модули загружаются при первом использовании. Во мно¬гих системах с виртуальной памятью каждому процессу присвоен собст¬венный набор виртуальных адресов, начинающихся с нуля, а загрузчик отвечает за загрузку программы в действительную область памяти.

Мы можем проследить весь процесс компиляции, связывания и загруз¬ки (используя адресное связывание во время загрузки) от исходного кода до выполнения на рис. 2.13. Программист начинает с написания исходного кода на некотором языке высокого уровня — в данном случае на языке С. Затем компилятор трансформирует файлы foo.c и bar.с исходного кода в машинный язык, создавая при этом объектные модули foo.o и bar.р. В коде программист определил переменную X в foo.c и переменную Y в bar.с; обе они расположены по относительному адресу 100 в соответст¬вующих им объектных модулях. Объектные модули расположены на вто¬ричных запоминающих устройствах, пока пользователь или другой про¬цесс не укажет на необходимость их связывания.На следующем этапе компоновщик объединяет два модуля в единый за¬грузочный модуль. Компоновщик решает свою задачу, собирая информа¬цию о размерах модулей и внешних символах при первом проходе и связы¬вая файлы вместе при втором проходе. Следует обратить внимание, что компоновщик перемещает переменную Y по относительному адресу 400. На третьем этапе загрузчик запрашивает блок памяти под программу. Операционная система предоставляет диапазон адресов от 4000 до 5050, так что загрузчик перемещает переменную X по абсолютному адресу 4100, а переменную Y по абсолютному адресу 4400.

examination/os/question11.txt · Последние изменения: 2014/01/15 08:21 (внешнее изменение)