Сборка и загрузка программ «Операционные системы» НГУ ФИТ-ФФ Иртегов Д.В. Начнем издалека • Что такое память? • Основная память компьютера – ОЗУ + ПЗУ • Массив пронумерованных ячеек • Ячейка – это один байт (у старых компьютеров, по словам) • Номер ячейки называется ее адресом • В первом приближении, основная память – это память, которую вы можете адресовать указателем языка С • Большинство компьютеров также имеет внешнюю (вторичную) память Внешняя память • Это память, которую вы не можете адресовать указателями • Магнитные ленты вообще не имеют адресации (данные можно только читать все подряд) • Диски, флэш-накопители – адресация по блокам (512 байт или более) • Чтобы работать с программами и данными во внешней памяти, их надо загрузить (скопировать) в основную память • ОС предоставляют удобные средства для этого (файлы, страничную подкачку) Сборка программ на языке C • Формат командной строки cc file1.c file2.c [-lgen] [-o out] • Рекомендованный способ cс –c file1.c cc –c file2.c cc file1.o file2.o • Кроме перечисленных в командной строке файлов и библиотек, сс по умолчанию добавляет библиотеку libc.so (стандартную библиотеку языка C) Еще про сборку программ на языке C • В отличие от более новых языков (Java, Go), исходный код программы на C не содержит указаний на то, какие внешние модули ему нужны • Используемые внешние модули вы должны сами перечислить при компиляции (если только это не стандартная библиотека) • Include-файлы – это не сами внешние модули, это только описание их интерфейсов (структур, типов, объявлений функций) • Обычно, у каждого include-файла должны быть один или несколько соответствующих .c-файлов с определениями функций • У стандартной библиотеки языка C, обычно исходники вам напрямую не доступны, есть только соответствующая объектная или разделяемая библиотека Что вообще такое программа? Машинный код – последовательность команд, лежащих в памяти Команда – последовательность байт, состоящая из кода операции и операндов Например, команда b8 22 11 00 FF B8 –код операции+операнд (movl %eax) 22 11 00 FF – операнд (константа 0xFF001122) У фон-неймановских процессоров, есть так называемые «команды перехода» Аналог оператора goto Используются для реализации циклов, if, подпрограмм, переключения нитей и процессов Операнд команды перехода – адрес в основной памяти, куда нужно перейти Почему это важно? • Программа содержит адреса • Переменных • Точек перехода • При создании программы, надо знать, как она будет размещена в памяти, чтобы правильно посчитать эти адреса • Или нет? Метки (символы ассемблера и линкера) Метка – именованное значение Метка – это место (адрес) в коде или данных Метку можно использовать в операндах команд Если значение метки по какой-то причине поменяется, код переписывать не надо, ассемблер сам пересчитает все операнды, где использовалась метка В программах на C, метки соответствуют точкам входа функций, переменным Также компилятор создает метки для реализации условных операторов и циклов Раздельная компиляция и сборка Как пересчитать метки при раздельной компиляции? • Ассемблер этого сделать не может • Он не знает, в каком месте программы окажется модуль • Это делает линкер (сборщик) • Ассемблер генерирует таблицы символов • Таблица экспорта • Таблица импорта • Таблица перемещения • на самом деле, это не таблица символов, в ней нету имен • Линкер использует эти таблицы, чтобы пересчитать все символы Маленькие хитрости • Многие процессоры поддерживают относительную адресацию • Относительная адресация – это когда операнд представляет собой не адрес, а смещение относительно адреса команды (значения счетчика команд) • У x86 так устроены большинство команд перехода • У ARM, также возможно обращаться к константам и данным • Относительная адресация позволяет сильно сократить таблицу перемещения • Если таблицу перемещения удается сделать вообще пустой, получается позиционно-независимый код Объектный файл (модуль) • Содержит бинарный код с неразрешенными ссылками • Также содержит • Заголовок (сигнатура, количество других секций и их размеры) • Таблицу экспорта (символы, которые определены в этом модуле) • Таблицу импорта (внешние символы, которые использует этот модуль, + для каждого символа, все точки, где на него ссылаются • Таблицу перемещений (ссылки на внутренние метки, которые надо пересчитать, чтобы привязать модуль к его реальному положению в программе) • Служебную и отладочную информацию Программные секции При создании метки, ассемблер знает, к какой секции она относится Линкер собирает код или данные каждой секции в свое место, и учитывает это при пересчете меток Абсолютная и относительная загрузка • Абсолютная загрузка: мы знаем, как программа будет размещена в памяти • Однопроцессные (однопрограммные) ОС • Образы для прошивки в ПЗУ • Виртуальная память (каждой программе выделяют свой образ) • Относительная загрузка: мы не знаем, как программа будет размещена в памяти • Многопроцессные ОС без виртуальной памяти • Динамическая сборка и загрузка Абсолютные и относительные исполняемые файлы • Абсолютный (неперемещаемый) исполняемый файл: • Содержит точную копию кода и начальные значения данных • Просто копируется или отображается в память при загрузке • Относительный (перемещаемый) файл • Содержит код и данные с неразрешенными ссылками • Имеет таблицу перемещения • Ссылки нужно пересчитать при загрузке Динамическая сборка • Сборка в момент загрузки • Разделяемые библиотеки: • Все программы на C используют стандартную библиотеку • Зачем держать ее в каждом исполняемом файле? • Зачем много раз загружать ее в память? • Если мы найдем ошибку в библиотеке, можно будет пропатчить эту библиотеку, и все исполняемые файлы, которые ее используют, волшебным образом получат этот патч Динамическая загрузка • Сборка и загрузка модулей после загрузки основной программы (во время исполнения) • Примеры: • Фильтры экспорта-импорта в Word, Photoshop • Плагины в Netscape/Firefox • Модули Apache • Обращения к коду на C из интерпретируемых/JiT языков (Python, Java) • Модули ядра ОС DLL hell • Темная сторона динамической загрузки • Динамически линкуемые модули взаимодействуют через ABI • Требуется бинарная совместимость • Точное совпадение форматов всех структур данных • Добавили поле или изменили тип поля в структуре – сломали ABI • Ломать совместимость иногда приходится –> версии ABI • Конфликты версий • Много разделяемых библиотек -> конфликты возникают постоянно Выходы из ситуации • Не ломать ABI никогда • IBM OS/360, OS/2 • Пересобирать все из исходников по месту • *BSD, Gentoo • «Все свое ношу с собой» • Windows, Docker • «Дистрибутивы» - наборы пакетов с совместимым ABI • Debian, Red Hat, etc