Компиляция
4.1. Компиляция
Этот этап реализуется не ОС, а системами программирования, которые представляют собой "системы, образуемые языком программирования, компиляторами или интерпретаторами программ, представленных на этом языке, соответствующей документацией, а также вспомогательными средствами для подготовки программ в форме, пригодной для выполнения" [4]. Системы программирования представляют собой предмет изучения отдельного курса, им посвящена обширная литература, здесь мы остановимся только на тех их аспектах, которые имеют отношение к ОС и аппаратным средствам вычислительной системы.
Основным функциональным назначением системы программирования является генерация объектного кода программы (машинных команд). Компиляторы предварительно формируют структуру виртуального адресного пространства: определяют состав сегментов и формируют содержимое (образы) кодовых сегментов и сегментов инициализированных статических данных.
Система программирования создает также дополнительный интерфейс между программистом и ОС. Состав и спецификации этого интерфейса могут быть либо стандартными для языка, либо определяться конкретной системой программирования. Везде в этом пособии мы описываем системные вызовы ОС, как некоторые процедуры или функции высокоуровневого языка программирования. В конкретных системах программирования набор таких процедур составляет библиотеки системных вызовов, эти процедуры обеспечивают передачу вызовов ОС в той форме, в какой они специфицированы для данной ОС (например, в виде программного прерывания с передачей параметров через регистры общего назначения). Многие процедуры систем программирования включают в себя интегрированный системный сервис - выполнение в составе одной процедуры нескольких системных вызовов с некоторой обработкой их результатов. Можно говорить о том, что системы программирования продолжают тенденцию виртуализации ресурсов: они формируют на базе примитивов, обеспечиваемых системными вызовами ОС, ресурсы более высокого уровня, доступные через средства системы программирования. Так, работая на языке высокого уровня, мы имеем в своем распоряжении виртуальную ЭВМ, в которой "система команд" представлена операциями, операторами и стандартными процедурами языка, а адресация выполняется в пространстве символьных имен. Некоторые языки или их конкретные системы программирования могут включать в себя и более сложные средства управления ресурсами, такие как: буферизацию ввода/вывода (см. главу 6), работу с файлами сложной логической структуры (см. главу 7), средства синхронизации и взаимодействия процессов (см. главу 8) и т.д.
С целью получения наиболее эффективного объектного кода компиляторы могут выполнять оптимизацию обрабатываемой программы. Можно выделить три стадии такой оптимизации:
- системно-независимая оптимизация;
- системно-зависимая, но аппаратно-независимая оптимизация;
- аппаратно-зависимая оптимизация.
Ни одна из указанных стадий не является строго обязательной. На первой стадии выполняется оптимизация логической структуры программы и ее виртуальной памяти. К оптимизационным процедурам этого этапа можно отнести: оптимизацию циклов, устранение избыточных вычислений, преобразование индексных выражений, сокращение числа переменных и т.п. Эти методы требуют не пооператорного просмотра, а анализа всей программы или ее участка. Такая оптимизация может осуществляться либо на уровне исходного языка, либо на уровне некоторого промежуточного языка. Подавляющее большинство современных языков высокого уровня воплощает принципы структурного программирования [6], а это означает, что с одной стороны, эффективность алгоритма зачастую приносится в жертву его ясности, но с другой, - что логическая структура программы получается строгой и, следовательно, легко анализируемой и оптимизируемой. Общность принципов структурного программирования для разных языков позволяют также формировать единое промежуточное представление разноязыковых программ и применять оптимизационные процедуры, не зависящие от языка.
Системно-зависимая стадия оптимизации связана прежде всего с дисциплинами управления памятью в ОС. Если компилятор "знает", какие алгоритмы управления памятью (например, страничного обмена) применяет ОС, он может "помочь" ОС. Поскольку компилятор, в отличие от ОС, обладает возможностью глобального анализа программы, он может предсказывать будущую потребность в тех или иных страницах памяти, влиять на размер и состав рабочего набора процесса. Компилятор может также перестроить программу таким образом, чтобы повысить степень локализации обращений к памяти и тем самым снизить интенсивность страничного обмена.
Роль аппаратно-зависимой оптимизации все более возрастает с развитием процессорных архитектур. Мы уже отмечали, что на компилятор, таким образом, возлагается задача расположения команд программы в такой последовательности, чтобы обеспечить максимальную загрузку конвейерных линий. Рассмотрим пример возможного дальнейшего развития этой идеи.
Недостатком современных суперскалярных процессоров является необходимость для процессора в каждом цикле анализировать поток команд, чтобы определить, какие конвейерные линии могут быть использованы. Это является препятствием как для увеличения числа линий, так и для сокращения времени цикла. Перспективной для RISС-процессоров, по-видимому, является идея упаковки нескольких простых команд в одну большую команду фиксированной длины. Такая команда называется VLIW (very long instruction word - очень длинное командное слово). Составляющие VLIW-команды должны выполняться строго последовательно, сами VLIW-команды могут выполняться параллельно. Процессор, таким образом, просто загружает очередную VLIW-команду в очередную конвейерную линию, не занимаясь анализом командного потока. Задача формирования VLIW-команд с оптимизацией их под данную платформу ложится на компилятор. На сегодняшний день подобные подходы применяются, например, в процессорах фирмы Hewlett-Packard и процессоре Itanium фирмы Intel.