Прикладная поддержка новой архитектуры в Plan 9

Боб Фландрена
bobf@plan9.bell-labs.com

Введение

В Plan 9 есть пять классов архитектуро-зависимого ПО: заголовки, ядра, компиляторы и загрузчики, системная библиотека libc, и несколько прикладных программ. В общем, архитектуро-зависимые программы состоят из портабельной части, распространяемой на все архитектуры, и процессоро-специфичной — для каждой поддерживаемой архитектуры. Часто портабельный код компилируется и сохраняется в библиотеке, ассоциированной с архитектурой. Формирование программы осуществляется посредством компиляции архитектуро-специфичной части кода и ее загрузки вместе с библиотекой. Поддержка новой архитектуры обеспечивается построением компилятора, его использованием для компиляции портабельного кода в библиотеки, написанием архитектуро-специфичного кода, и последующей загрузкой этого кода вместе с библиотеками.

В этом документе описана организация архитектуро-зависимого кода и заголовков в Plan 9. Первый раздел кратко обсуждает формат заголовков и исходного кода ядра, компиляторов, загрузчиков и системной библиотеки libc. Во втором разделе детально раскрывается структура libmach — библиотеки, содержащей почти весь архитектуро-зависимый код, который используется прикладными программами. Последний раздел описывает действия, необходимые при добавлении прикладной программной поддержки для новой архитектуры.

Структура каталогов

Архитектуро-зависимая информация для нового процессора находится в каталоге /m, где m (от machine) соответствует имени новой архитектуры (например, как mips). Новый каталог должен быть проинициализирован с несколькими важными подкаталогами, особенно с bin, include и lib. Дерево каталогов существующей архитектуры служит хорошей моделью для нового дерева. Архитектуро-зависимый mkfile должен быть размещен в ново созданном корневом каталоге архитектуры. Легче всего скопировать mkfile уже существующей архитектуры и модифицировать его для новой архитектуры. Если mkfile корректный, то для соответствия новой архитектуре вам следует лишь изменить переменные OS и CPUS в /sys/src/mkfile.proto.

Заголовки

Архитектуро-зависимые заголовки хранятся в каталоге /m/include. Обязательны два заголовочных файла: u.h и ureg.h. В первом описаны фундаментальные типы данных, настройки бит для статуса плавающей запятой и управляющих регистров, и обработка va_list, которая зависит от стековой модели архитектуры. Сформировать этот файл лучше всего, скопировав и модифицировав файл u.h из архитектуры со схожей стековой моделью. Файл ureg.h содержит структуру, описывающую формат набора регистров архитектуры; он определяется ядром.

В заголовочном файле /sys/include/a.out.h находятся определения магических чисел, используемых для идентификации исполняемых файлов для каждой архитектуры. После того как поддержка для новой архитектуры добавлена, в этот файл должно быть добавлено ее магическое число.

Формат заголовка загрузочных исполняемых файлов определяется производителем. Заголовочный файл /sys/include/bootexec.h содержит структуру, в которой описаны поддерживаемые заголовки. Если новая архитектура использует общий заголовок, наподобие COFF, то формат заголовка вероятно уже был определен, но если формат заголовка загрузчика не отвечает стандарту, то структура, определяющая формат, должна быть добавлена в этот файл.

Ядро

Хотя ядро критически зависит от свойств используемого оборудования, большая часть высокоуровневых функций ядра, включая управление процессами, подкачку, псевдо-устройства и немного сетевого кода, — независимы от процессорной архитектуры. Портабельный ядерный код разделен на две части: те, что реализуют ядерные функции, и те, что отвечают за процесс загрузки. Код первого класса хранится в каталоге /sys/src/9/port, а портабельный загрузочный код в /sys/src/9/boot. Архитектуро-зависимый код ядра находится в подкаталогах /sys/src/9 каждой архитектуры.

Отношение между кодом ядра и загрузочным кодом довольно свернутое и тонкое. Портабельный загрузочный код компилируется в библиотеку для каждой архитектуры. Архитектуро-специфичная главная программа загружается вместе с соответствующей библиотекой, и результирующий исполняемый файл компилируется в ядро, где он выполняется как пользовательский процесс на протяжении финальных стадий инициализации ядра. Загрузочный процесс выполняет аутентификацию, подключает корень пространства имен к соответствующей файловой системе и начинает процесс init.

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

Компиляторы и загрузчики

Исходный код компилятора соответствует обычной организации: портабельный код компилируется в библиотеку для каждой архитектуры и архитектуро-зависимый код загружается вместе с библиотекой. Общий код компилятора размещен в каталоге /sys/src/cmd/cc. Mkfile в этом каталоге компилирует портабельный исходный код и архивирует объекты в библиотеке для каждой архитектуры. Архитектуро-специфичный код компилятора размещен в подкаталоге /sys/src/cmd с соответствующим именем (как например, /sys/src/cmd/vc).

Портабельный код, распространяемый на загрузчики, отсутствует. Каждый каталог исходного кода загрузчика является замкнутым, за исключением заголовочного файла и таблицы имен инструкций, включаемых из каталога связанного компилятора.

Библиотеки

Большинство модулей библиотеки C являются портабельными; исходный код находится в каталогах /sys/src/libc/port и /sys/src/libc/9sys. Архитектуро-зависимый код библиотеки размещен в подкаталоге /sys/src/libc и имеет схожее с целевым процессором название. Не-портабельные функции выполняют не только архитектуро-зависимые операции, но также обеспечивают реализации функций на языке ассемблера, где скорость критична.

Каталог /sys/src/libc/9syscall — необычен, поскольку он содержит архитектуро-зависимую информацию для всех архитектур. В нем хранятся заголовочный файл с описанием имен и номеров системных вызовов, и mkfile. Mkfile запускает сценарий rc, который выполняет синтаксический анализ заголовочного файла, создает функции языка ассемблера, реализующие системный вызов для каждой архитектуры, формирует код, и архивирует объектные файлы в libc. Синтаксис языка ассемблера и системный интерфейс различаются для каждой архитектуры. Для поддержки новой архитектуры сценарий rc в этом mkfile должен подвергнуться модификации.

Приложения

Прикладные программы обрабатывают две формы архитектуро-зависимой информации: исполняемые образы и промежуточные объектные файлы. Почти вся работа приходится на исполняемые файлы. Системная библиотека libmach обеспечивает функции, которые преобразовывают архитектуро-специфичные данные в портабельный формат, так что прикладные программы могут обрабатывать эти данные независимо от основного представления. Далее, когда новая архитектура реализована, почти все изменения кода ограничиваются библиотекой; наиболее пораженные прикладные программы требуют лишь перезагрузки. Исходный код для библиотеки находится в /sys/src/libmach.

Прикладная программа, запущенная на одном типе процессора, должна иметь возможность интерпретировать архитектуро-зависимую информацию для всех поддерживаемых процессоров. К примеру, отладчик должен быть способен отлаживать исполняемые файлы для всех архитектур, а не только для архитектуры, на которой он запущен, потому что /proc может импортироваться с других машин.

Небольшая часть прикладной библиотеки обеспечивает функции извлечения символьных ссылок из объектных файлов. Ниже приведены этапы обработки исполняемых файлов или образов памяти:

Заголовочный файл /sys/include/mach.h определяет интерфейсы для прикладной библиотеки. Детали библиотечных функций описаны в man-страницах mach(2), symbol(2) и object(2).

Две структуры данных под названиями Mach и Machdata содержат архитектуро-зависимые параметры и функции таблицы переходов. Глобальные переменные mach и machdata указывают на структуры данных Mach и Machdata, связанные с целевой архитектурой. Приложение определяет целевую архитектуру файла или исполняемых образов, устанавливает глобальные указатели на структуры данных, ассоциированные с этой архитектурой, и впоследствии выполняет все действия косвенно через указатели. Как результат, непосредственные указания на таблицы для каждой архитектуры аннулированы и прикладной код внутренне поддерживает все архитектуры (но только поочередно).

Обработка объектного файла осуществляется таким же образом: архитектуро-зависимые функции идентифицируют и декодируют промежуточные файлы для процессора. Приложение косвенно вызывает функцию классификации для идентификации архитектуры объектного кода и выбора соответствующей функции декодирования. Затем последующие вызовы могут использовать функцию для декодирования каждой записи. Вновь, формат косвенности позволяет прикладному коду поддерживать все архитектуры без модификации.

Разделение архитектуро-зависимой информации между структурами данных Mach и Machdata позволяет приложениям выбирать соответствующий уровень сервиса. Приложение даже не ссылается непосредственно на архитектуро-специфичные структуры данных, оно должно загружать архитектуро-зависимые таблицы и код для всех поддерживаемых архитектур. Размер этих данных может быть постоянным, большинство приложений не требуют полной области архитектуро-зависимой функциональности. К примеру, команда size не требует дизассемблеров для каждой архитектуры; ей лишь необходимо декодировать заголовок. Структура данных Mach содержит несколько архитектуро-специфичных параметров и описание набора регистров процессора. Хотя размер структуры изменяется в зависимости от размера набора регистров, обычно он не велик. Структура данных Machdata содержит таблицу переходов архитектуро-зависимых функций; объем кода и данных, указанных в этой таблице, обычно достаточно большой.

Организация исходного кода libmach

Библиотека libmach представляет четыре класса функциональности:

Декодирование заголовка и символьной таблицы
Файлы executable.c и sym.c содержат код для интерпретации заголовочной и символьных таблиц исполняемых файлов или исполняемого образа. Функция crackhdr декодирует заголовок, реформатирует информацию в структуру данных Fhdr, и направляет глобальную переменную mach на структуру данных Mach целевой архитектуры. Для декодирования символьной таблицы используются данные в структуре Fhdr. Ряд функций доступа к символьной таблице затем поддерживает запросы к реформатированной таблице.
Поддержка отладчика
Файлы m.c, где m — кодовая буква, присвоенная архитектуре, содержат инициализированную структуру данных Mach и определение набора регистров для каждой архитектуры. Архитектуро-специфичный отладчик, поддерживающий функции и проинициализированную структуру Machdata, собраны в файлах mdb.c. Файлы machdata.c и setmach.c содержат функции поддержки отладчика, совместно используемые многочисленными архитектурами.
Архитектуро-независимый доступ
Файлы map.c, access.c и swap.c обеспечивают доступы посредством карты перемещений к данным в исполняемом файле или исполняемом образе. При необходимости выполняется байт-свопинг. Глобальные переменные mach и machdata должны указывать на структуры данных Mach и Machdata целевой архитектуры.
Интерпретация объектных файлов
Эти файлы содержат функции идентификации целевой архитектуры промежуточного объектного файла и извлечения указателей на символы. Файл obj.c содержит общий для всех архитектур код; mobj.c содержит архитектуро-специфичный исходный код для машины с кодовым символом m.

Структура данных Machdata первоначально является таблицей переходов архитектуро-зависимых функций поддержки отладчика. Функции выбирают структуру Machdata для целевой архитектуры, основанной на значении типа кода в структуре Fhdr или имени архитектуры. Таблица переходов обеспечивает функции обмена байт, интерпретацию машинных команд, выполняет трассировки стека, поиск стековых фреймов, форматирует числа с плавающей запятой, и декодирует машинные исключения. Некоторые функции, такие как декодирование машинных исключений, — идиосинкразтичны и должны поставляться для каждой архитектуры. Другие зависят от модели время прогона компилятора, различные архитектуры могут совместно использовать общий для модели код. К примеру, много архитектур распределяют код обработки фиксированно-фреймовой модели стека, реализованной в различных компиляторах. Наконец, некоторые функции, вроде байт-свопинга, обеспечивают общую способность и таблица переходов требует только выбора реализации соответствующей архитектуры.

Добавление прикладной поддержки для новой архитектуры

В этом разделе описаны действия, необходимые для добавления поддержки прикладного уровня для новой архитектуры. Предполагается, что ядро, компиляторы и системные библиотеки для новой архитектуры уже на своих местах. При этом подразумевается, что кодовый символ присвоен и архитектуро-специфичные заголовки обновлены. За исключением двух программ, изменения в прикладном уровне ограничены заголовочными файлами и исходным кодом в /sys/src/libmach.

1. Начните с обновления заголовочного файла прикладной библиотеки в /sys/include/mach.h. Добавьте следующие символьные коды в оператор enum ближе к началу файла:

2. В имени файла /sys/src/libmach/m.c (где m — идентификационный символ, присвоенный архитектуре), инициализируйте структуры данных Reglist и Mach со значениями, определяющими набор регистров и различные параметры системы. Исходный файл для схожей архитектуры может служить шаблоном. Большая часть полей в структуре данных Mach очевидны, но некоторые все-же требуют дальнейшего объяснения.

kbase
Поле содержит адрес ublock. Отладчики принимают первый элемент ublock, указывающего на структуру Proc для нити ядра.
ktmask
Битовая маска для вычисления текстового адреса ядра из адреса ublock. Первая страница текстового сегмента ядра вычисляется путем выполнения бинарной операции И над отрицанием этой маски с kbase.
kspoff
Содержит смещение байт в структуре данных Proc к сохраненному указателю на стек ядра для приостановленной нити ядра. Это смещение до поля sched.sp элемента таблицы Proc.
kpcoff
Содержит смещение байт в структуре данных Proc программного счетчика приостановленной нити ядра. Это смещение до поля sched.pc в этой структуре.
kspdelta и kpcdelta
Эти поля содержат коррекции, которые добавляются к указателю стека и программному счетчику, соответственно, к правильному расположению стека и следующей команде нити ядра. Эти значения смещают сохраненные регистры, извлеченные из структуры Label под названием sched в структуре данных Proc. Большинство архитектур не требуют смещения и эти поля содержат нули.
scalloff
Содержит смещение байт поля scallnr в структуре данных ublock, связанной с процессом. Поле scallnr содержит номер последнего системного вызова, выполненного процессом. Расположение поля изменяется в зависимости от размера набора регистров с плавающей запятой, которые следуют за ним в ublock.

3. Добавьте элемент в инициализацию структуры данных ExecTable в начале файла /sys/src/libmach/executable.c. Большинство архитектур требуют два элемента: один для нормального исполняемого файла и один для загрузочного образа. Каждый элемент таблицы содержит:

4. Напишите синтаксический анализатор объектных файлов и сохраните его в файле /sys/src/libmach/mobj.c, где m является символом-идентификатором, который присвоен архитектуре. Необходимы две функции: предикат для идентификации объектного файла для архитектуры и функция извлечения символьных указателей из объектного кода. Формат объектного кода затемнен, но часто есть возможность адаптации кода существующей архитектуры с незначительными модификациями. Когда эти функции под рукой, вставьте их адреса в таблицу переходов в начале файла /sys/src/libmach/obj.c.

5. Реализуйте требуемые функции поддержки отладчика и инициализируйте параметры и таблицу переходов структуры данных Machdata. Этот код обычно размещается в файле /sys/src/libmach/mdb.c. Поля архитектуры таковы:

bpinst и bpsize
Содержат команду точечного разрыва (breakpoint) и размер команды, соответственно.
swab
Содержит адрес функции для обмена байт 16-битным значением. Выберите leswab или beswab для архитектуры little-endian или big-endian, соответственно.
swal
Содержит адрес функции обмена байт 32-битным значением. Выберите leswab или beswab для архитектуры little-endian или big-endian, соответственно
ctrace
Содержит адрес функции трассировки стека (язык C). Две общих функции трассировки, risctrace и cisctrace, просмотр фиксированно-фреймового и относительно-фреймовых стеков, соответственно. Если компилятор для новой архитектуры подходит для одной из этих моделей, тогда выберите необходимую. Если модель стека уникальна, то придется обеспечить специальную функцию трассировки стека.
findframe
Содержит адрес функции расположения стекового фрейма, ассоциированного с текстовым адресом. Общие функции riscframe и ciscframe обрабатывают модели фиксированно-фреймового и относительно-фреймового стеков.
ufixup
Содержит адрес функции регулировки базового адреса регистровой области сохранения. К настоящему времени, лишь 68020 требует это смещение выше фрейма активного исключения.
excep
Содержит адрес функции создания текстовой строки, описывающей текущее исключение. Каждая архитектура хранит исключающую информации по-своему, таким образом этот код всегда должен поставляться.
bpfix
Содержит функцию регулировки адреса до формулирования точечного разрыва.
sftos
Содержит адрес функции преобразования значения с плавающей запятой одинарной точности в строку. Выберите leieeesftos для little-endian или beieeesftos для big-endian.
dftos
Содержит адрес функции преобразования значения с плавающей запятой двойной точности в строку. Выберите leieeedftos для little-endian или beieeedftos для big-endian.
foll, das, hexinst и instsize
Эти поля указывают на функции интерпретации машинных команд. Они полагаются на дизассемблирование команды и уникальны для каждой архитектуры. Foll вычисляет следующий набор команд. Das дизассемблирует машинную команду в язык ассемблера. Hexinst форматирует машинную команду как текстовую строку шестнадцатеричных цифр. Instsize вычисляет размер команды в байтах. Как только дизассемблер написан, другие функции могут быть реализованы как его тривиальные расширения.

Есть возможность предоставления поддержки для новой архитектуры пошагово путем заполнения элементов таблицы переходов структуры Machdata как написанного кода. В общем, если элемент таблицы переходов содержит нуль, то прикладная программа, требующая эту функцию, будет производить сообщение об ошибке взамен попытки вызова функции. К примеру, слоты таблицы переходов: foll, das, hexinst и instsize могут быть обнулены, пока не написан дизассемблер. Другие возможности, такие как трассировка стека или проверка переменных, могут быть обеспечены и будут доступны отладчикам, но попытка использовать дизассемблер будет результировать с сообщением об ошибке.

6. Обновите таблицу machines ближе к началу /sys/src/libmach/setmach.c. Эта таблица связывает код типа файла и имя машины в структурах Mach и Machdata архитектуры. Имена инициализированных структур Mach и Machdata, сформированных в действиях 2 и 5, должны быть добавлены к списку структурных определений непосредственно следующие за таблицей инициализации. Если оба Plan 9 и исконное дизассемблирование поддерживаются, тогда добавьте в таблицу элемент для каждого дизассемблера. Элемент для дизассемблера по-умолчанию (обычно Plan 9) указывается первым.

7. Добавьте элемент, описывающий архитектуру в таблицу trans ближе к концу /sys/src/cmd/prof.c.

8. Добавьте элемент, описывающий архитектуру в таблицу objtype ближе к началу /sys/src/cmd/pcc.c.

9. Перекомпилируйте и установите все прикладные программы, которые включают заголовочный файл mach.h, и загрузите вместе с libmach.a.

Copyright © 2002 Lucent Technologies Inc. All rights reserved.
Copyright © 2003 Перевод Андрей С. Кухар.