Процедура создания большинства приложений является общей и приведена на рис. 2.2.
Рис. 2.2. Схема компиляции программы
Первой фазой является стадия компиляции, когда файлы с исходными текстами программы, включая файлы заголовков, обрабатываются компилятором cc(1) . Параметры компиляции задаются либо с помощью файла makefile(или Makefile), либо явным указанием необходимых опций компилятора в командной строке. В итоге компилятор создает набор промежуточных объектных файлов. Традиционно имена созданных объектных файлов имеют суффикс ".o".
На следующей стадии эти файлы с помощью редактора связей ld(1) связываются друг с другом и с различными библиотеками, включая стандартную библиотеку по умолчанию и библиотеки, указанные пользователем в качестве параметров. При этом редактор связей может выполняться в двух режимах: статическом и динамическом, что задается соответствующими опциями. В статическом, наиболее традиционном режиме связываются все объектные модули и статические библиотеки (их имена имеют суффикс ".а"), производится разрешение всех внешних ссылок модулей и создается единый исполняемый файл, содержащий весь необходимый для выполнения код. Во втором случае, редактор связей по возможности подключает разделяемые библиотеки (имена этих библиотек имеют суффикс ".so"). В результате создается исполняемый файл, к которому в процессе запуска на выполнение будут подключены все разделяемые объекты. В обоих случаях по умолчанию создается исполняемый файл с именем a.out.
Для достаточно простых задач все фазы автоматически выполняются вызовом команды:
$ make prog
или эквивалентной ей
$ cc -о prog prog.c
которые создают исполняемый файл с именем prog. В этом случае умалчиваемое имя исполняемого файла ( a.out) изменено на progс помощью опции -о.
Впрочем, указанные стадии можно выполнять и раздельно, с использованием команд cc(1) и ld(1) . Заметим, что на самом деле команда cc(1) является программной оболочкой и компилятора и редактора связей, которую и рекомендуется использовать при создании программ.
Проиллюстрируем процесс создания более сложной программы с помощью конкретных вызовов команд.
$ cc -с file1.c file2.c
$ cc -о prog
Создадим промежуточные объектные файлы file1.o и file2.o
$ cc -o prog file1.o file2.o -lnsl
Создадим исполняемый файл с именем prog, используя промежуточные объектные файлы и библиотеку libnsl.aили libnsl.so
Форматы исполняемых файлов
Виртуальная память процесса состоит из нескольких сегментов или областей памяти. Размер, содержимое и расположение сегментов в памяти определяется как самой программой, например, использованием библиотек, размером кода и данных, так и форматом исполняемого файла этой программы. В большинстве современных операционных систем UNIX используются два стандартных формата исполняемых файлов — COFF (Common Object File Format) и ELF (Executable and Linking Format).
Описание форматов исполняемых файлов может показаться лишним, однако представление о них необходимо для описания базовой функциональности ядра операционной системы. В частности, информация, хранящаяся в исполняемых файлах форматов COFF и ELF позволяет ответить на ряд вопросов весьма важных для работы приложения и системы в целом:
□ Какие части программы необходимо загрузить в память?
□ Как создается область для неинициализированных данных?
□ Какие части процесса должны быть сохранены в дисковой области свопинга (специальной области дискового пространства, предназначенной для временного хранения фрагментов адресного пространства процесса), например, при замещении страниц, а какие могут быть при необходимости считаны из файла, и таким образом не требуют сохранения?
□ Где в памяти располагаются инструкции и данные программы?
□ Какие библиотеки необходимы для выполнения программы?
□ Как связаны исполняемый файл на диске, образ программы в памяти и дисковая область свопинга?
На рис. 2.3 приведена базовая структура памяти для процессов, загруженных из исполняемых файлов форматов COFF и ELF, соответственно. Хотя расположение сегментов различается для этих двух форматов, основные компоненты одни и те же. Оба процесса имеют сегменты кода (text), данных (data), стека (stack). Как видно из рисунка, размер сегментов данных и стека может изменяться, а направление этого изменения определяется форматом исполняемого файла. Размер стека автоматически изменяется операционной системой, в то время как управление размером сегмента данных производится самим приложением. Эти вопросы мы подробно обсудим в разделе "Выделение памяти" далее в этой главе.
Читать дальше