Дипломная работа система сборки, анализа и верификации кода осрв embox Абусалимов Эльдар Шакирович группа 545




Скачать 316.67 Kb.
Дата25.08.2016
Размер316.67 Kb.
Министерство образования и науки Российской Федерации

Федеральное агентство по образованию Российской Федерации

Государственное образовательное учреждение высшего профессионального

образования


САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ

Математико-механический факультет

Специальность 010503 «Математическое обеспечение

и администрирование информационных систем»



Кафедра системного программирования

ДИПЛОМНАЯ РАБОТА



Система сборки, анализа и верификации кода ОСРВ Embox
Абусалимов Эльдар Шакирович

группа 545




Руководитель


……………………………

асп. Бондарев А.В.

Рецензент


……………………………

Венгеров В.В.

«Допущен к защите» Заведующий кафедрой


……………………………

д.ф.-м. н., проф. А.Н.Терехов


Санкт-Петербург

2011

Saint-Petersburg State University

Mathematics and Mechanics Faculty

Software Engineering Department


Build system, code analysis and verification tool for Embox RTOS
Graduate paper by

Eldar Abusalimov

545 group


Scientific advisor


……………………………

Bondarev A.V.

Reviewer


……………………………

Vengerov V.V.

“Approved by”

Head of Department



……………………………

Professor
A.N.Terekhov



Saint-Petersburg

2011


Оглавление


Оглавление 1

Введение 2

Встроенные системы 2

ОС Embox 3

Системы сборки 4



Постановка задачи 5

Обзор существующих средств 6

GNU make 6

GNU Autotools 9

Kbuild 10

Другие 12

Реализация 13

Платформа 13

Файлы метаданных 14

Конфигурационные файлы 21

Процесс сборки 26

Анализ кода и верификация 33

Сравнение с Kbuild 34

Внедрение 35

Заключение 36

Литература 37




Введение

Встроенные системы


На кафедре системного программирования СПбГУ и компании ГУП Терком уже более 20 лет занимаются проблемами построения встроенных систем, в том числе систем реального времени. За это время был накоплен большой опыт в этой сфере, особенно в исследовании ОСРВ: применении уже существующих и построении собственных.

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



  • Зачастую аппаратное обеспечение устройства обладает ограниченными ресурсами, что сказывается на разработке программного обеспечения и ведет к требованию “ничего лишнего”;

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

  • И наконец, с ростом объема кода растет и количество ошибок в нем.

Разумеется, можно для каждого нового устройства создавать программное обеспечение “с нуля”. Но, во-первых, такой подход ведет к чрезмерному удорожанию стоимости разработки, а во-вторых, в новом коде неизбежно появление новых ошибок.

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

Одна из таких систем – Embox – разрабатывается и на кафедре системного программирования СПбГУ.

ОС Embox


Проект Embox1 ведется около двух лет и нацелен на создание операционной системы, подходящей для различных аппаратных платформ, в первую очередь с ограниченными ресурсами, а также различных задач, в том числе и задач реального времени. Еще одна цель, которую предполагают достигнуть разработчики – это применение кода проекта на всех стадиях разработки встроенной системы, то есть на этапе отладки оборудования в качестве системы тестирования, на этапе запуска основной ОС в качестве системного загрузчика и, наконец, в качестве полноценной ОС с различными характеристиками. Все это привело к требованию создания высококонфигурируемой системы, при этом обладающей возможностью проверки корректности заданной конфигурации и системы в целом, что является необходимым условием при разработке надежных систем реального времени.

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


Системы сборки


Одним из неизбежных этапов разработки программного обеспечения является сборка результирующего продукта, готового к эксплуатации или отладке, из исходных кодов. Сборка также может включать в себя такие этапы как:

  • Выполнение тестов

  • Подготовка сопроводительной документации

  • Создание отчетов о результатах сборки

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

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



  • Возможность построения приложения из взаимосвязанных компонент;

  • Поддержка множества конфигураций приложения;

  • Проверка непротиворечивости построенной конфигурации;

  • Функция получения “среза” лишь тех исходных кодов, которые действительно участвуют в сборке для конкретной конфигурации.

Постановка задачи


Необходимо разработать систему сборки, которая удовлетворяла требованиям для построения конечного образа ОСРВ Embox, а именно обладала бы следующими возможностями:

  • Отслеживание зависимостей для включения в сборку;

  • Задание порядка загрузки модулей на этапе исполнения;

  • Возможность указания системных модулей (стратегий планирования, алгоритмов выделения памяти …);

  • Возможность тонкой настройки каждого модуля;

  • Изолирование пространств имен (поддержка составных имен для модулей);

  • Простота описания модулей и общей конфигурации системы;

  • Кросс-платформенность.

Обзор существующих средств

GNU make


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

К примеру, Makefile для сборки приложения baz, состоящего из двух исходных файлов foo.c и bar.c, можно записать следующим образом:

baz : foo.o bar.o

ld -o $@ $^


foo.o bar.o : %.o : %.c

сс -o $@ -c $<

В этом Makefile’е отражено бувально следующее: приложение baz – это результат компоновки утилитой ld объектных файлов foo.o и bar.o, которые, в свою очередь, получаются при компиляции с помощью cc файлов исходного кода foo.c и bar.c соответственно.

Граф зависимостей для описанной сборки выглядит следующим образом:



Рис.1. Граф зависимостей, используемый системой сборки make.

В момент сборки make анализирует граф зависимостей и, на основе информации о времени последнего изменения каждого файла, определяет и запускает необходимые программы. Таким образом, make обеспечивает простой механизм инкрементальной сборки – при изменении нескольких реквизитов выполняется лишь минимальный набор рецептов, необходимый для получения цели.

Например, при изменении лишь файла bar.c производится только компиляция bar.o и компоновка цели baz:

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

Помимо базовой функциональности, большинство реализаций make (в том числе и наиболее распространенная – GNU make2) поддерживает возможность параллельной сборки на многопроцессорных компьютерах, что также актуально для больших приложений.

Таким образом, GNU make – это универсальное средство, позволяющее описывать шаги сборки на гибком скриптовом языке Makefile’ов.

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

Наконец, при создании программного обеспечения с высокой степенью конфигурируемости необходимо учитывать семантическую связь между различными частями системы, которую не отслеживает make. Например, одна часть системы (foo) может не функционировать без другой (bar).



Кроме того, при использовании лишь классических Makefile’ов с простым указанием необходимых к сборке модулей возникают следующие проблемы:



  • Сложно составить вручную такую конфигурацию системы, которая бы обладала непротиворечивостью, обеспечивая успешную компиляцию и корректное функционирование приложения

  • Поскольку Makefile’ы создаются разработчиком вручную, необходимо поддерживать их в актуальном состоянии при добавлении новых исходников или изменении существующих

Иными словами, в чистом виде make не пригоден для сборки и поддержки сложных проектов, к которым относятся высококонфигурируемые проекты, в том числе и Embox. С учетом всех недостатков, “голый” make используется лишь для небольших проектов или только на ранних стадиях развития. В большинстве же случаев используются либо специальные средства генерации Makefile’ов (такие как GNU Automake), либо сложные скрипты make, обеспечивающие готовую инфраструктуру для сборки (например, Kbuild). И тот, и другой способ дают возможность создавать описания на языке более высокого уровня.

GNU Autotools


Система GNU Autotools3 является набором программных средств для поддержки переносимости исходного кода программ между UNIX-подобными системами. Большинство прикладных программ проекта GNU используют эту систему сборки.

Утилиты Autoconf и Automake, входящие в состав GNU Autotools, обеспечивают генерацию Makefile’ов для сборки на основе макросов, предоставленных разработчиком с учетом системы, на которой производится сборка.

Опции конфигурации ограничиваются макросами препроцессора вида CONFIG_XXX, злоупотребление которыми в коде ведет к “#ifdef nightmare”. Поэтому использование таких макросов для построения высококонфигурируемой системы весьма затруднительно. Кроме того, в Autotools также отсутствует возможность определения семантических связей для компонентов системы.

Еще одним недостатком Autotools можно считать необходимость установки дополнительного ПО на компьютер разработчика.

Таким образом, эта система сборки ориентирована на решение задач, отличных от поставленной, поэтому от ее использования в проекте Embox решено было отказаться.

Kbuild


Система Kbuild4 разработана для автоматизации сборки ядра Linux. Kbuild по большей части реализован на языке make, что обеспечивает работоспособность системы сборки без установки дополнительного ПО на компьютер разработчика или пользователя. Помимо ядра Linux, Kbuild также используется в таких крупных проектах как Das U-Boot и BusyBox.

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



Определенные в Kconfig опции можно использовать для включения или отключения в Makefile’e необходимых объектных файлов:

obj-$(CONFIG_FOO) += foo.o

obj-$(CONFIG_BAR) += bar.o

Таким образом, для включения опции CONFIG_FOO потребуется также включить и CONFIG_BAR, что приведет к добавлению в сборку обоих объектных файлов: и foo.o, и bar.o.

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

Возможность использования Kbuild всерьез рассматривалась при выборе системы сборки для проекта Embox. Однако, из-за некоторых недостатков нам пришлось отказаться от выбора в пользу Kbuild.

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

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

Тем не менее, система сборки Kbuild хорошо справляется со своей задачей, и некоторые идеи (например, использование среды GNU make) легли в основу моей работы.


Другие


Прочие распространенные системы сборки, такие как Ant, Scons, Cmake и другие системы семейства Make в этой работе подробно рассматриваться не будут, поскольку все они имеют ряд серьезных недостатков, неудовлетворяющих поставленным требованиям:

  • Все они требуют установки дополнительного ПО, как на компьютер разработчика, так и на компьютер конечного пользователя. Для проекта Embox изначально требовалось реализовать автоматизацию сборки без необходимости установки дополнительного ПО;

  • Эти системы сборки не обладают встроенными механизмами разрешения межмодульных зависимостей. Таким образом, в чистом виде они не решают поставленную задачу, и, даже использовав одну из них в качестве среды исполнения, логику все равно пришлось бы реализовывать вручную;

  • То же самое можно сказать и про отсутствие в этих системах встроенной поддержки множества конфигураций для одного приложения;

  • Некоторые обладают сложным и неудобным форматом описания сборки (например, XML в Ant).

Реализация


В ходе работы над проектом Embox мною была реализована и внедрена система сборки Embuild.

Платформа


В качестве среды исполнения был выбран GNU make. Такой выбор обусловлен в первую очередь широким распространением (как уже говорилось, make является стандартом де-факто в мире Unix-подобных систем и предустановлен в абсолютном большинстве дистрибутивов GNU/Linux) и встроенной поддержкой механизма инкрементальной сборки.

GNU make предоставляет две парадигмы программирования: логический язык описания целей и зависимостей и функциональный язык, ориентированный на обработку текстовых данных и позволяющий динамически изменять правила сборки. Большая часть логики Embuild реализована именно на функциональном языке make, и далее языком make я буду называть именно функциональную его составляющую.

Язык make не типизирован5, любые данные в нем представляются строкой, и большинство встроенных функций относятся к функциям обработки текста. Язык имеет ограниченную поддержку массивов данных, представляемых строкой слов, разделенный пробелами. Поэтому для упрощения написания логики системы была реализована объектно-ориентированная библиотека6, а также использовалась библиотека GMSL7.

Файлы метаданных


Как и большинство систем сборок, Embuild требует от разработчика приложения описания и поддержки некоторых метаданных, необходимых для сборки.

Описания этих метаданных (сущностей) располагаются в так называемых em-файлах. Поддерживается возможность как описания каждой сущности в отдельном файле, называемом именем сущности с расширением .em, так и описания сразу нескольких в одном файле с именем Embuild. Для описаний был введен простой предметно-ориентированный язык, являющийся подмножеством языка make.

Все описываемые сущности имеют следующую структуру:

define name


= value

...


endef

Вместо указывается тип сущности, например, module; вместо


– название свойства, которым может обладать сущность, например, sources.

Тот факт, что язык описаний является подмножеством языка make, сильно упрощает разбор описаний, сводя его к простому исполнению файла описаний как части Makefile’а. Помимо избавления от необходимости реализации собственного парсера, такой подход обладает достаточно высокой производительностью, поскольку не требует дополнительного вызова процесса для разбора. С точки зрения реализации конструкция define … endef в примере выше представляет из себя определение переменной make (с именем name). Значение этой переменной, в свою очередь, также содержит код на языке make, содержащий определения переменных с именами вида


.

Пространства имен


В дереве исходных кодов em-файлы располагаются в тех же папках, что и сами файлы кодов. Таким образом, для всех сущностей естественным образом определяются пространства имен – полное имя сущности складывается из пути относительно корня проекта и собственного имени, разделенных точками.

Например, сущность, определенная в файле foo.em, расположенном в директории embox/src/path/to/example/, где embox/src – это корень проекта, будет иметь полное имя path.to.example.foo.

Сущности, находящиеся в одном пространстве имен, могут ссылаться друг на друга по собственному имени (короткая форма), в общем же случае используется полное имя. Благодаря изоляции пространств имен исключаются конфликты именования, что актуально при росте проекта.

Модули


Так как вся система построена по модульному принципу, основной сущностью, которой оперирует Embuild, является модуль. Метаданные каждого модуля включают в себя:

  • список ассоциированных с модулем файлов исходного кода (свойство sources) и специфичные флаги компиляции (cflags);

  • список объектных файлов (objects), получаемых отличным от компиляции способом, и правила (rules) для сборки этих файлов;

  • зависимости от других модулей (depends и provides);

  • используемые библиотеки (uses);

  • доступные параметры конфигурации (options);

  • документацию (свойства brief и details).

Свойства sources и cflags


В значении свойства sources перечисляются файлы, которые необходимо скомпилировать. Поскольку практически весь код системы Embox написан на Си и языке ассемблера, свойство sources значительно сокращает описания, позволяя разработчику в большинстве случаев не задумываться о способе получения объектного файла из исходного кода.

По умолчанию свойство sources принимает значение .c, таким образом, еще больше упрощая описание модулей, состоящих из единственного файла на языке Си с именем самого модуля. Файлы, перечисляемые в свойстве, ищутся в той же директории, где располагается em-файл с описанием, и могут содержать шаблоны поиска (например, *.c для включения всех файлов на языке Си в текущей папке).

Для компиляции файлов, перечисленных в sources, используется общее правило make вида (для файлов исходного кода на языке Си):

$(OBJECTS_DIR)/%.o : $(SOURCES_DIR)/%.c

$(CC) $(CFLAGS) -o $@ -c $<

Значение переменной CFLAGS содержит флаги компиляции, общие для всех компилируемых файлов, а также дополнительные флаги, индивидуальные для каждого модуля и задаваемые в свойстве cflags. К примеру, если в файле на языке Си включается специфичный заголовочный файл, отсутствующий в стандартных путях, то для компиляции этого файла требуется передать дополнительный флаг -Ipath/to/include.

Пример описания, использующего оба флага:

define module bob

sources = alice.c

cflags = -I./alice/include

endef

Итак, для описания большинства модулей Embox достаточно двух рассмотренных свойств.


Свойства objects и rules


Как уже говорилось, почти всегда можно обойтись свойствами sources и cflags. Исключение составляют три случая:

  • Специфичные и дополнительные скрипты компоновщика;

  • Бинарный код, включаемый в конечный образ системы, например, образ дерева файловой системы ramfs.cpio;

  • Генерируемые файлы.

Рассмотрим для примера модуль, реализующий механизм динамического выделения памяти из кучи. Сама куча представляет собой область памяти, и пусть мы хотим, чтобы за размещение этой области отвечал компоновщик. Кроме того, необходимо иметь возможность задавать размер кучи как опцию конфигурации. Тогда понадобится создать файл-заготовку (heap.lds.S), из которого после обработки препроцессором Си получится необходимый линкер-скрипт. Содержимое такого файла может быть следующим:

SECTIONS {

.heap (NOLOAD): {

_heap_start = .;

. += CONFIG_HEAP_SIZE;

_heap_end = .;

}

}

В данном случае CONFIG_HEAP_SIZE – это макрос, раскрываемый препроцессором в требуемую константу, равную размеру кучи.



Для описания модуля, включающего такую генерацию дополнительного скрипта для компоновки, используются свойства objects и rules.

define module heap_alloc

objects = heap.lds

define rules

$(@D)/heap.lds: $(^D)/heap.lds.S

$(CPP) $(CPPFLAGS) -o $@ $<

endef

endef

Таким образом, скрипт heap.lds – результат обработки препроцессором файла heap.lds.S – передается компоновщику, который, в свою очередь, выделит для кучи требуемый объем памяти.


Свойство depends


Это свойство определяет отношение зависимости между двумя модулями, которое несет два значения:

  • На этапе компиляции при включении в сборку одного модуля вместе с ним подключается весь подграф его зависимостей, гарантируя успешную компоновку итогового образа (см. “Разрешение зависимостей для модулей”);

  • Во время исполнения гарантируется правильный порядок запуска и останова модулей приложения (см. “Внедрение зависимостей”).

Описание двух модулей, один из которых зависит от второго, может выглядеть следующим образом:

define module foo

brief = The main application module

sources = foo.c

depends = bar

endef
define module bar

brief = Calculates the Answer to the Ultimate Question

sources = bar.c

endef

В этом примере зависимость модуля foo от bar отражена через свойство модуля depends.


Абстрактные модули


Зачастую от модуля в системе требуется лишь предоставить некий интерфейс – соглашение об экспортируемых функциях и поведении модуля в целом. В данном случае реализация скрывается за интерфейсом. Более того, интерфейс может иметь несколько реализаций.

В качестве примера можно снова обратиться к механизму динамического выделения памяти. Интерфейс определяет лишь функции malloc и free – их сигнатуры и семантику. Реализация же зависит от используемого алгоритма, например, граничные признаки или метод близнецов.

Для описания таких сущностей в Embuild используется понятие абстрактных модулей, называемых api. Тогда пример с динамической памятью описывается следующим образом:

define api alloc

brief = Dynamic memory allocator

endef
define module buddy

brief = Memory allocator using buddy system

provides = alloc

sources = buddy.c

endef

В данном примере определяется интерфейс alloc и одна из его реализаций – buddy. По аналогии со свойством depends, в значении свойства provides указывается интерфейс, реализуемый данным модулем.


Библиотеки


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

Типичным примером можно считать стандартную библиотеку, содержащую функции работы со строками, памятью и другие.



define library c

brief = C standard library

sources = *.c

endef

Библиотеки также могут быть взаимосвязаны между собой по двум отношениям, для определения которых используются одноименные свойства сущности library:



  • uses – библиотека использует функции, определенные в другой библиотеке;

  • overrides – библиотека переопределяет некоторые функции другой.

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

define library arch

brief = Memory functions optimized for x86

overrides = c

sources = memcpy.c

endef

Таким образом, в приложении будет использоваться именно функция memcpy из библиотеки arch вместо ее аналога по умолчанию.


Конфигурационные файлы


Для описания конфигурации конечного образа используют специальные конфигурационные файлы – conf-файлы, – отделенные от описания модулей и кода системы. В отличие от описания модулей и библиотек приложения в em-файлах, создаваемых и поддерживаемых разработчиком самого приложения, конфигурационные файлы используются конечным пользователем системы.

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

Таким образом, описание конфигурации системы представляет собой набор простых conf-файлов. Это дает возможность хранить некоторые заранее описанные конфигурации системы (шаблоны), модифицируя которые пользователь может подстроить систему для своих нужд.

Основными conf-файлами являются:



  • build.conf, в котором задаются общие параметры сборки системы

  • lds.conf, задающий карту памяти для компоновщика

  • mods.conf – список включаемых в сборку модулей с их собственными параметрами

Параметры сборки


В файле build.conf задается информация об архитектуре процессора, используемой платформе, специфичных опциях компилятора, и так далее. В результате считывания данного файла создаются соответствующие переменные для make’а, и они используются при сборке проекта. Основными переменными являются:

  • ARCH – подпапка в папке src/arch, в которой располагается архитектурно-зависимый код проекта (низкоуровневый код для различных процессоров);

  • PLATFORM – подпапка в папке platform проекта. В этой папке располагаются модули, необходимые при сборке платформо-зависимой части (загрузочный код конкретного устройства, специфичные драйверы и так далее);

  • CROSS_COMPILE задает префикс кросс-компилятора, используемого при сборке.

Примером может послужить файл build.conf, используемый для сборки Embox для устройства Lego NXT:

TARGET = embox
ARCH = arm

PLATFORM = lego_nxt
CROSS_COMPILE = arm-elf-
CFLAGS += -O0 -g

CFLAGS += -march=armv4 -mtune=arm7tdmi

LDFLAGS += -N -g

Параметры компоновки образа


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

Основными директивами lds.conf являются:



  • lds_region объявляет регион памяти для редактора связей, например, строка

lds_region(sdram, 0x200000, 64K)

описывает регион памяти по имени sdram, начинающийся с адреса 0x200000 и размером 64 Килобайт;



  • lds_section задает отображение секции в регион памяти. Таким образом,

lds_section(bss, sdram)

предписывает редактору связей отобразить все ссылки на неинициализированные данные секции bss в регион памяти sdram;



  • lds_section_load задает, помимо отображения в виртуальную память также регион памяти, из которого будет загружен первоначальный образ секции. Например, чтобы разместить образ секции data в регионе памяти flash, а все ссылки отобразить в регион sdram, следует написать:

lds_section_load(data, sdram, flash)

Рассмотрим обработку этих директив на примере из первого пункта:



lds_region(sdram, 0x200000, 64K)

Макрос lds_region объявляется следующим образом:



#define lds_region(name, base, size) \

$define LDS_REGION_BASE_##name base $N\

$define LDS_REGION_SIZE_##name size

Файл конфигурации последовательно обрабатывается препроцессором и утилитой sed, заменяющей порожденные препроцессором вхождения $N и $define:



sed -e 's/$N/\n/g' -e 's/$define/#define/g'

В результате получается код, содержащий необходимые определения (опять же, на языке препроцессора):

#define LDS_REGION_BASE_sdram 0x200000

#define LDS_REGION_SIZE_sdram 64K

Этот файл впоследствии подключается к файлу-заготовке, содержащему шаблон скрипта компоновщика, который при сборке будет преобразован в итоговый линкер-скрипт.

Включаемые модули


Наконец, в файле mods.conf перечисляются все модули для включения в итоговый образ системы.

Основной директивой в этом файле является директива mod, принимающая от одного до двух аргументов. Первым аргументом передается полное имя модуля для включения. В необязательном втором аргументе указывается уровень загрузки системы, на котором следует запустить данный модуль (см. “Внедрение зависимостей”).

Например, для включения в сборку двух модулей bob и alice, первый из которых должен запуститься не позже уровня 1 (системные компоненты), можно использовать следующую запись:

mod(bob, 1)

mod(alice)

Здесь для модуля alice уровень исполнения не задан, поэтому будет использован уровень по умолчанию (2, драйверы).

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

#define mod(mod, ...) \

MODS_ENABLE += mod $N\

RUNLEVEL-##mod := __FIRST_ARG(__VA_ARGS__)

Таким образом, пример выше преобразуется в следующий код:

MODS_ENABLE += bob

RUNLEVEL-bob := 1

MODS_ENABLE += alice

RUNLEVEL-alice :=

Пустое определение переменной RUNLEVEL-alice в дальнейшем будет заменено на значение уровня исполнения по умолчанию.

Процесс сборки


В момент первого запуска Embuild проходит по всем директориям проекта и обрабатывает найденные em-файлы. В случае успешного завершения строится внутреннее представление, которое сохраняется в файле embuild_cache.mk и используется при всех последующих инкрементальных пересборках. Такое решение принято исключительно из соображений производительности. Этот файл зависит от всех найденных в дереве исходников em-файлов, поэтому при изменении одного из них make обеспечит и обновление первого.

Обработка em-файлов


Система изначально проектировалась с использованием подхода так называемого нерекурсивного make. В отличие от более распространенного рекурсивного подхода, при котором в каждой директории с исходным кодом запускается дочерний процесс make, нерекурсивный make обладает более высокой производительностью и лишен сложностей межпроцессного взаимодействия для обмена данными между вызовами make для разных директорий. Хороший сравнительный анализ этих двух подходов проводится в статье Питера Миллера8.

Как уже говорилось, формат em-файлов являет собой подмножество языка make. Поэтому для чтения используется простая директива include языка. В результате исполнения em-файла происходит определение некоторых переменных (например, module foo), по имени которых определяется тип сущностей. Также производится проверка на допустимость определения в текущем em-файле (module foo можно определить только в файле Embuild или foo.em) и на конфликты с другими сущностями (нельзя определить одновременно module foo и api foo в одном пространстве имен).

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

Разрешение зависимостей


На этом этапе происходит построение двух ориентированных графов зависимостей – модулей и библиотек.

Для модулей


Граф межмодульных зависимостей строится на основании информации, полученной из свойств depends и provides обработанных модулей и интерфейсов.

Итак, в вершинах графа находятся модули и интерфейсы. Дуги построенного графа могут быть двух типов в зависимости от того, каким отношениям они соответствуют. Любая дуга соответствует либо отношению depends (ребра первого типа) в направлении этого отношения, либо отношению provides (ребра второго типа) в обращенном направлении. Ребра первого типа всегда выходят из вершин, обозначающих модули, конечной же вершиной может быть любой. Начальной вершиной ребра второго типа может быть только интерфейс, а конечной – только модуль.

Для примера рассмотрим, как строится граф для данных трех модулей и одного интерфейса:

Отношение depends напрямую отображается в дугу результирующего графа. Две же дуги, соответствующие отношениям provides, направлены от интерфейса к модулям, реализующим этот интерфейс. На рисунке ниже они изображены пунктиром. Таким образом, можно говорить о том, что отношения provides заменяются отношениями provided by.



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

На этом проверка информации, предоставленной разработчиком в em-файлах, для модулей завершается.

Для библиотек


При компоновке нескольких библиотек важно обеспечить нужный порядок обработки их компоновщиком (такова особенность работы последнего), а именно передать аргументы, содержащие имена библиотек, в нужном порядке. Порядок определяется зависимостями, определенными в свойствах uses и overrides описанных в em-файлах сущностей library.

Для этого строится граф, в вершинах которого находятся библиотеки, а дуги образуются следующим образом:



  • Все отношения overrides образуют соответствующие дуги;

  • Отношение A uses B порождает дуги из библиотеки A во все вершины, для которых существует транзитивное замыкание по отношению overrides в дугу B.

Проще всего рассмотреть это преобразование на примере.

В результате замены отношения uses получается следующий граф:



Полученный граф подвергается топологической сортировке9, чтобы получить список библиотек для передачи его компоновщику. Для примера выше очевидно возможны два варианта (поскольку отношение между arch и barf не определено):



  • foo, arch, barf, c

  • foo, barf, arch, c

Обработка conf-файлов


После построения внутренней модели система сборки приступает к обработке пользовательских конфигурационных файлов. Файл build.conf имеет формат Makefile’а, поэтому исполняется как есть. Файл mods.conf предварительно обрабатывается препроцессором, после чего также интрепретируется make’ом. Все модули из списка, полученного из этого файла, проверяются на наличие в построенном ранее графе, после чего список дополняется модулями, необходимыми для удовлетворения всех зависимостей. Если во вновь полученном списке появляются абстрактные модули, то система сборки удостоверяется, что каждый из них имеет ровно одну реализацию.

Определение правил make


Наконец, Embuild динамически определяет все необходимые цели (объектные файлы и библиотечные архивы) и правила для их сборки. Оставшуюся работу – компиляцию необходимых исходников и компоновку – проделывает make, он же обеспечивает инкрементальную сборку.

Внедрение зависимостей


Одно из требований, предъявленных к системе сборки, – обеспечение правильного порядка инициализации модулей на этапе загрузки и исполнения системы Embox. Реализация этого требования стала возможной за счет использования знаний, извлекаемых Embuild при обработке метаданных модулей: главным образом, информации о межмодульных зависимостях.

Для этого в образ системы добавляется репозиторий модулей, содержащий дескрипторы всех модулей с указанием их зависимостей. Дескриптор содержит массив указателей на все модули, которые должны быть проинициализированы до него. Репозиторий модулей генерируется системой сборки и представляет собой файл с исходным кодом на языке Си, в котором описываются все необходимые дескрипторы.

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

Пример графа зависимостей с добавленными модулями, соответствующими уровням исполнения, выглядит следующим образом:



А дескрипторы, соответствующие данному графу, будут иметь следующую структуру:



Целью диспетчера загрузки является запуск модуля run1. Во время инициализации системы диспетчер проверяет список зависимостей дескриптора этого модуля. Если среди них находятся неинициализированные модули, диспетчер переходит к рекурсивной инициализации этих модулей. Можно сказать, что диспетчер в процессе запуска системы спускается по дереву зависимостей к листьям и инициализирует их первыми. На последнем рисунке инициализация будет происходить снизу вверх: bar, foo, run0, baz, run1.


Анализ кода и верификация


Так как em-файлы создаются и поддерживаются разработчиком, в описаниях неизбежно появляются ошибки и неточности, к примеру, в определении межмодульных зависимостей. Для решения этой проблемы в Embuild была добавлена возможность анализа исходного кода и проверки описаний на соответствие извлеченной информации.

Такая возможность реализуется компиляцией необходимых модулей и обработкой полученных файлов утилитой nm, которая показывает все символы, экспортируемые данным объектным файлом, а также символы, требуемые для успешной компоновки. По этой информации строится граф, который сопоставляется с графом, полученным в результате обработки em-файлов. Извлеченная информация также используется для подсказки разработчику возможных зависимостей для модулей, не прошедших проверку.



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

Облегчение сертификации


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

Сравнение с Kbuild


В этом сравнении не участвуют GNU make и GNU Autotools, поскольку являются общими средствами автоматизации сборки. То же самое относится и к другим системам сборки, рассматривавшимся ранее.




Kbuild

Embuild

Среда исполнения

GNU make

GNU make

Установка дополнительного ПО

не требуется

не требуется

Обход дерева исходных кодов

рекурсивный make

нерекурсивный make

Формат описаний

раздельные файлы Kconfig и Makefile

описание всех свойств сущности в одном файле

Пространства имен

нет

есть

Разрешение зависимостей при сборке

зависимости для опций конфигурации

межмодульные зависимости

Внедрение зависимостей времени исполнения

нет

есть

Средства графического конфигурирования

есть

нет

Внедрение


Разработка системы сборки Embuild ведется мной с марта 2010, и большая часть реализованных возможностей была внедрена в проект Embox к концу 2010 года. Частично не внедренной остается последняя версия формата em-файлов, рассмотренная в данной работе, – в настоящее время в Embox используется предыдущая версия. Поскольку изменения по большей части носят лишь синтаксический характер, переход ведется в фоновом режиме.

Заключение


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

Embuild обладает следующими преимуществами:



  • Кросс-платформенность за счет использования языка make;

  • Высокий уровень описаний компонентов системы;

  • Использование изолированных пространств имен для компонентов;

  • Простота описаний конфигураций конечных модулей;

  • Возможность задания различных реализаций системных интерфейсов.

В ближайших планах развития проекта Embuild – завершение внедрения разработанной системы сборки в проект Embox и создание полноценной пользовательской документации на русском и английском языках.

Литература


1Embox – Essential toolbox for embedded development – http://code.google.com/p/embox

2GNU `make' manual – http://www.gnu.org/software/make/manual/make.html

3The GNU Build System – http://www.gnu.org/software/autoconf/manual/autoconf.html#The-GNU-Build-System

4Bram Adams, Kris De Schutter, Herman Tromp, Wolfgang De Meuter –The Evolution of the Linux Build System –
http://journal.ub.tu-berlin.de/index.php/eceasst/article/view/115/119

5What’s Wrong With GNU make? – http://www.conifersystems.com/whitepapers/gnu-make/

6Robert Mecklenburg – Managing Projects with GNU Make, Third Edition – O'Reilly Media

7GNU Make Standard Library – http://gmsl.sourceforge.net/

8Peter Miller “Recursive Make Considered Harmful” – http://miller.emu.id.au/pmiller/books/rmch/

9Кормен, Т., Лейзерсон, Ч., Ривест, Р., Штайн, К. Алгоритмы: построение и анализ = Introduction to Algorithms. – 2-е. – М.: Вильямс, 2005.


База данных защищена авторским правом ©infoeto.ru 2016
обратиться к администрации
Как написать курсовую работу | Как написать хороший реферат
    Главная страница