ФЕДЕРАЛЬНОЕ АГЕНТСТВО ПО
ОБРАЗОВАНИЮ
Государственное образовательное учреждение
высшего профессионального
образования
ОБНИНСКИЙ ГОСУДАРСТВЕННЫЙ
ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ
АТОМНОЙ ЭНЕРГЕТИКИ
(ИАТЭ)
Факультет «Кибернетики»
Кафедра «Компьютерные
Системы, Сети и Технологии»
ОТЧЕТ ПО
ПРОИЗВОДСТВЕННОЙ ПРАКТИКЕ
На тему : «Разработка и создание элементов ОС на
основе микроядра»
|
Выполнил: |
_______________ |
Студент
5 курса Группы
ВТ-3-00 Специальности
220100 Дневного
отделения Степанов А. В. Email: andrusha [at] gmail [dot] com |
|
Руководитель: |
_______________ |
Зав лабораторией ЛТТ ГУ ВНИИГМИ-МЦД к.т.н. Шаймарданов В.М. |
М.П.
Обнинск 2005 г.
3. Платформа проведения тестов и опытов
4. Реализация семафоров POSIX для приложений микроядра L4
4.2. Функции для работы с семафорами Posix: sem_init и
sem_destroy
4.3. Функции для работы с семафорами Posix: sem_wait и
sem_trywait
4.4. Функции для работы с семафорами Posix: sem_post и sem_getvalue
5.1. Преследуемая цель создания
5.3. Общая структура драйвера сетевой карты
5.4. Архитектура Ethernet сервера.
6.3. Проверка работоспособности Click роутера как приложения микроядра.
Список использованных
источников
53 стр., 6 рис.,
4 ист., 3 прил..
Способы построения и
проектирования ОС, ОС основанные на микроядре, L4::Pistachio, разработка сервисов и приложений
для работы в окружении микроядра, программный роутер Click, семафоры POSIX.
Данная преддипломная
практика посвящена изучению основ разработки сервисов и подсистем ОС, основных
возможностей микроядра L4Ka::Pistachio, а также методов оптимизации и защиты работы приложений.
Целью данной работы является освоение возможностей микроядра L4Ka::Pistachio и предоставляемый им интерфейс.
Задачи, решены в ходе выполнения работы:
§
Проанализированы
научные доклады исследовательских групп и определены ведущие принципы
проектирования и взаимосвязи элементов ОС.
§
Изучены
инструментальные средства построения сервисов ОС (С++, GNU make, scons, GNU arch, IDL, vim, cscope и др).
§
Спроектированы
сервисы ОС.
В настоящей преддипломной
практике применяют следующие термины с соответствующими определениями:
§
ООП – Объектное Ориентированное Программирование
§
DMA – Direct Memory Access (прямой доступ к памяти)
§
CPU – Central
Process Unit (процессор)
§
ЗД – Защитный домен
§
NAT – Network Address Translation (сетевое преобразование адресов)
§
BIOS – Basic Iput Output System (базовая система ввода/вывода)
§
IPSEC – Сетевой транспортный протокол,
передающий данные в зашифрованном виде
§
NIC
– Network Interface Card (сетевая карта)
Мы живем во время эффективного
развития информационных, электронных,
коммуникационных технологий. В нашей повседневной
жизни дне мы сталкиваемся с множеством различных технических устройств. С каждым
новым днем появляются все новые сетевые, фото, аудио, видео, охранные, коммуникационные
устройства. Прогресс начинает затрагивать очень специфические области человеческой
деятельности. Каждое новое устройство наделено новыми особенными функциями,
которые зависят от своей области применения. Такие системы выполняют одно или
некое маленькое семейство приложений со специализированными нуждами. В целях
долгой, экономичной, непрерывной работы нагрузка на память, CPU и периферийные устройства должна
быть сведена к минимуму. Требования к
программному и аппаратному обеспечению постоянно и очень быстро меняются. От
систем требуется гибкой поддержки в контроле, диагностике и оптимизации. Становиться
очевидно, что писать каждый раз или переносить старое ПО на новую аппаратную
платформу должно происходить с максимальной скоростью. При написании ПО
необходимо избегать включения и использования низкоуровневых функций для работы
с периферией или управлением работы CPU. Также для обеспечения гибкости,
должна быть предоставлена возможность по изменению рабочей конфигурации
системы. В каждой системе есть участок кода, который занимает определенное
привилегированное место, и выполняет базовые функции синхронизации,
коммуникации, защиты и взаимодействует с аппаратным слоем. Поэтому наиболее
целесообразно выделить и создать некую общую платформу, которая бы
предоставляла в независимости от аппаратной платформы набор базовых функций.
Так было введено понятие микроядра.
Микроядро функционально
несет необходимою, но не избыточную нагрузку, которая позволяет реализовать
различные в зависимости от требований системы. Причем при проектировании
конкретной системы, не накладывается никаких ограничений на архитектуру.
Введение понятия микроядра, дало толчок к возникновению новых направлений в
исследованиях по построению ОС.
Микроядро L4 имеет много реализаций, оно
разрабатывается несколькими исследовательскими группами университетов:
§
System
Architecture Group университета Karlsruhe
§
DiSy
Group университета New
South Wales, Австралия
На его основе создано не
мало научных разработок, начиная от простой студенческой ОС (Minix А. С. Таненбаума на основе L4), заканчивая реально действующей и
использующейся системой для управления полетами спутниками. Микроядро L4 находится в постоянном развитии и
модернизации. На сегодняшний день последняя реализация носит индекс X2 (eXperimental 2). Оно работает на различных
архитектурных платформах, начиная от процессоров для супер производительных
мейнфреймов, заканчивая встраиваемыми процессорами для наладонных и мобильных
систем:
§
Alpha
§
AMD64
§
ARM
§
IA-32
§
IA-64
§
MIPS64
§
PowerPC
§
PowerPC64
§
SPARCv9
Ядро свободно
распространяется под условиями BSD лицензии. Т.е. любой желающий может скачать из
Интернета исходные коды и использовать по своему усмотрению, оставляя ссылку на
разработчика. Написано на С++, используя в своей реализации методы ООП. Ядро
представляет механизмы защиты и передачи сообщений (способ взаимодействия между
исполняемыми единицами), и не несет в себе никаких драйверов устройств. Весь
другой функционал выносится на непривилегированный пользовательский уровень
(драйвера, сетевые протоколы и т.д.). Можно начать с нуля разрабатывать систему
для L4, а можно использовать уже готовые наборы базовых библиотек и серверов (IO, naming, timer и т.д.). Во время проведения
практики было принято решение остановится на рабочем окружении для микроядра Pistachio Kenge/Iguana.
Kenge
– свод правил и ПО с помощью
которого компилируются, линкуются и строятся зависимости между компонентами ОС.
Сердцем Kenge является система выявления зависимостей Scons (функционально похоже на ПО make), и система ведения и отслеживания
изменений в исходном коде компонентов GNU arch (по своим функциональным
возможностям не уступает CVS – Current Version System). А также набор вспомогательных
инструментов необходимый при разработке ПО:
o
IDL4
Magpie – компилятор
интерфейсов описанных в формате Corba
,
o
Grub
позволяющего загружать образ созданной ОС с различных устройств, включая
сетевые карточки
o
qemu
– эмулятора различных аппаратных платформ (ia32, x86_64,
SPARC64)
Iguana – набор библиотек и серверов выполняющихся на основе
микроядра. В наличии доступны следующие компоненты:
§
Сервер Naming – предоставляет возможность поиска сервера по имени.
§
Сервер Serial – содержит драйвер COM-порта. Используется для управления и отладки процессов через
консоль, подключенной к последовательному порту.
§
Сервер Server – дополняет базовые системные вызовы L4. Содержит средства для
управления/создания/удаления/ защитных доменов, нитей, участков памяти, сессий,
внешних адресных пространств. Предоставляет доступ к порам В/В и участкам DMA.
§
Сервер Init – выполняет начальную инициализацию и запускает на
выполнение все последующие сервера.
§
Тестовые программки – главная цель которых,
наглядно продемонстрировать, как использовать выше перечисленные сервера.
Во время практики этот набор был расширен такими серверами как ethernet и semaphore.
Весь процесс
исследования, обучения и разработки проводился в ОС Linux, так как эта ОС как нельзя лучше
подходит для этих целей, предоставляя такие мощные инструменты как:
§
Gdb
– GNU Debugger позволяет вести
отладку исполняемых программ
§
Scons
– система контроля версий
§
vim
– мощнейший текстовый редактор исходных файлов
§
gcc
– компилятор языка C/C++
§
ld
– линкер объектных файлов, позволяет использовать карту (скрипт) будущего
размещения объектов в оперативной памяти.
§
cscope
– утилита для навигации по файлам с исходным кодом программ, также позволяет
быстро понять внутреннюю структуру больших проектов.
Полученный
скомпилированный файл не может непосредственно выполняться как исполняемый файл
ОС Linux. Главным
препятствием является тот факт, что микроядро непосредственно управляет CPU, его работой, например механизмом
защиты памяти и прерываниями от внешних устройств. То есть, ему необходим
доступ к 0-му кольцу защиты. А любое исполняемое приложение в ОС выполняется на
3-м кольце защиты, в своем адресном пространстве и не имеет никакой возможности
прямого доступа к оборудованию. Выход из сложившейся ситуации представляется
двумя путями:
§
Запускать скомпилированный файл непосредственно
на «голом» оборудовании. Образ ОС можно загрузить для выполнения с помощью ПО GRUB по сети из FTPF сервера, или с жесткого
диска.
§
Использовать ПО которое полностью эмулирует
работу некой аппаратной платформы (ia32, arm и т.д.). Данное ПО выполняется на ОС Linux и позволяет запускать
гостевые ОС, эмулируя все аппаратные части. Во время практичной работы, такую
неоценимую помощь оказал эмулятор qemu. Так как отладка ОС, а особенно ядра и его компонентов почти
неосуществима в силу того, что само ядро является самым нижнем уровнем ПО, то
ни о каком отладчике говорить не приходится. Мельчайшая ошибка в таком ПО ведет
к полному зависанию платформы. Частые перезагрузки просто неизбежны. qemu позволяет значительно
экономить время, выполняя гостевую ОС на машине где ведется разработка.
Стоит заметить, если во
время программирования была допущена ошибка, то единственный ход действий по ее
выявлению заключается в следующих шагах:
§
Скомпилировать все ПО с отладочной информацией
§
Получить от внутреннего встроенного отладчика
ядра указатели SP и IP, чтоб выявить на каком
шаге и где произошла критическая ошибка
§
Вернутся в окружение ОС Linux
§
Дизассемблировать полностью программу, в которой произошла
ошибка (objdump –d –failed.program.elf.reloc).
§
Загрузить дамп в текстовый редактор.
§
Найти по указателю инструкций (IP) в каком именно участке кода
произошла ошибка.
§
Попытаться понять, почему произошла ошибка.
Никаких других отладочных
средств для перехода вперед или назад по давшей сбой программе нет. В этом и
заключается одна из сложностей разработки ПО такого класса.
Семафор представляет
собой простейшие средство синхронизации процессов и потоков. Семафоры могут
использоваться как отдельных процессов, так и потоков одного процесса. Процесс
в окружении ядра L4 можно рассматривать как набор всех нитей исполняющихся в одном адресном
пространстве. Главная задача семафоров заключается в предоставлении механизма,
который бы позволял организовывать взаимоисключающий доступ к некому
исполняемому участку кода. То есть, под этим термином скрывается великое
множество способов и реализаций, которые предоставляют упомянутую выше
функциональность. Семафоры бывают двоичные, целочисленные («со счетчиком») и
взаимного исключения (которые, между прочим, программисты называют между собой
«мьютексами», стихийно стремясь избегать недоразумений). В современных ОС более
всего популярны два варианта семафоров:
§
Семафоры определены стандартом POSIX (Portable Operating System Interface), которые были
установлены PASC (Portable Applications
Standarts Committe)
(Комитет по стандартам переносимых приложений).
§
Семафоры используемее в ОС System V. Данный класс семафоров
получил также очень широкое распространение, по той причине, что почти все
современные варианты ОС UNIX наследуют подсистему IPC именно от System V.
В нашем случае семафор
представить как счетчик доступа программ к публичным ресурсам.
Во время проведения
практики было решено реализовать часть подсистемы IPC, которая управляет семафорами,
причем чтобы она полностью удовлетворяла стандарту POSIX.
Семафоры POSIX разделяются на:
§
Именованные, идентифицируемые именами,
соответствующими стандарту Posix
для IPC.
§
Размещаемые в разделяемой памяти.
Именованные семафоры Posix не обязательно должны обрабатываться
ядром. Их особенностью является наличие имен, которые могут соответствовать
именам реальных файлов в файловой системе. В силу этого, реализовать их в
настоящие время не предоставляется возможности (так как нет файловой системы).
(На самом деле, для достижения данной цели разумно было бы использовать сервер
имен, но при будущем добавлении файловой системы, понадобилась бы модернизация
клиентского ПО).
Для использования
размещаемых в памяти семафоров, приложение должно выделить для них участок
памяти, а инициализируются они системой.
#include <semaphore.h>
int sem_init(sem_t *sem, int shared, unsigned int
value);
/* Возвращает
-1 в случае ошибки */
int sem_destroy(sem_t *sem);
/* Возвращает 0 в случае успешного завершения. -1 – в
случае ошибки */
Размещенный в памяти
семафор инициализируется вызовом sem_init. Аргумент sem указывает на переменную типа sem_t, место под которую должно быть
выделено приложением. Если аргумент shared равен 0, семафор используется
потоками одного процесса (в одном ЗД), в противном случае доступ к нему могут
иметь несколько процессов. Если аргумент shared равен ненулевой, семафор должен быть
размещен в разделяемой памяти и должен быть доступен всем процессам
использующим его. Аргумент value задает начальное значение семафора. После завершения
работы с размещаемым в памяти семафором его можно уничтожить, вызвав sem_destroy. Размещаемый в памяти семафор не
утрачивает функциональности до тех пор, пока память, в которой он размещен, еще
доступна какому-либо процессу.
Если размещаемый в памяти
семафор совместно используется потоками одного процесса (аргумент shared при вызове sem_init равен 0), семафор обладает
живучестью процесса и удаляется при завершении последнего.
Если размещаемый в памяти
семафор совместно используется несколькими процессами (аргумент shared при вызове sem_init равен 1), он должен располагаться в
разделяемой памяти, и в этом случае семафор существует столько, сколько
существует эта область памяти. Это значит, что сервер может создать область
разделяемой памяти, инициализировать в ней размещаемый в памяти семафор Posix, а затем завершить работу. Некоторое
время спустя один или несколько клиентов могут присоединить эту область к
своему адресному пространству и получить доступ к хранящемуся в ней семафору.
На рисунке изображен
размещенный в разделяемой памяти семафор, используемый двумя процессами. Общий
сегмент памяти принадлежит адресному пространству обоих процессов:

Рис.1
Семафор в разделяемой памяти
Функция sem_wait проверяет значение заданного
семафора на положительность, уменьшает его на единицу и немедленно возвращает
процессу. Если значение семафора при вызове функции равно нулю, процесс приостанавливается
до тех пор, пока оно снова не станет больше нуля, после чего значение семафора
будет уменьшено на единицу и произойдет возврат из функции. Операция «проверка
и уменьшение» должна быть атомарной по отношению к другим потокам, работающим с
этим семафором:
#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
/* функции возвращают 0 в случае успешного завершения.
-1 в случае ошибки */
Разница между sem_wait и sem_trywait заключается в том, что последняя не
приостанавливает выполнение процесса, если значение семафора равно нулю, а
просто немедленно возвращает ошибку EGAIN.
После завершения работы с
семафором поток вызывает sem_post. Этот вызов увеличивает значение семафора на единицу
и возобновляет выполнение любых потоков, ожидающих изменения значения семафора:
#include <semaphore.h>
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *valp);
/* функции возвращают 0 в случае успешного завершения.
-1 в случае ошибки */
Функция sem_getvalue возвращает текущее значение
семафора, помещая его в целочисленную переменную, на которую указывает valp. Если семафор заблокирован,
стандартом указано возвращать либо 0, либо отрицательное число, модуль которого
соответствует количеству потоков, ожидающих разблокирования семафора. В ходе
практической работы был выбран второй вариант. Один из потоков может ожидать
изменения значения семафора, чтобы потом уменьшить его с 1 до 0, а другой поток
может изменить значение семафора с 0 до 1.
В процессе реализации
семафоров POSIX как некой службы работающей на основе микроядра, была выбрана
классическая схема представления некого сервиса клиенту. Клиент использовал
библиотеку, где были определены все необходимые функции и константы, которые бы
понадобились во время работы с семафорами. И сервера, который бы контролировал
поведение приложений, использующие семафоры.
Основная проблема при
реализации семафоров состояла в том, что операция по проверке текущего
состояния семафора и выбор последующего действия в зависимости от состояния
должна быть атомарная (т.е. переключение контекста в процессоре во время
исполнения этой операции должно быть предотвращено). Для достижения этой цели была использована
единственная команда микропроцессора семейства x86 cmpxchg r/m32, r32. Была создана функция следующего вида:
inline int cmpxchg(volatile L4_Word_t * dest,
L4_Word_t cmp_val, L4_Word_t new_val)
{
L4_Word_t tmp;
__asm__ __volatile__
(
“cmpxchgl %1, %3 \n\t”
:
“=a” (tmp) ///<
0 EAX, return val
:
“r”
(new_val), ///< 1 reg,
new value
“0”
(cmp_val), ///< EAX,
compare value
“m”
(*dest) ///< mem,
destination operand
:
“memory”, “cc”
);
return tmp == cmp_val; ///< return true if change is succesful
}
Алгоритм работы
заключается в следующем: сравнивается значение регистра EAX со значением расположенным по адресу
в памяти или в другом регистре. Если равны, выставляется флаг ZF, и r32 загружается в r/m32. Иначе, флаг ZF сбрасывается и в EAX загружается r/m32. Наглядно можно представить
следующей схемой:

Рис.2. Функция процессора cmpxchg r/m32, r32
Данная реализация
семафоров POSIX позволяет использовать взаимное исключение или разграниченный доступ к критическому объекту
в разных ЗД. Единственное что нужно, так это домену владельцу дать доступ к
телу семафора (например, через совместно-разделяемую память).
Для тестирования
работоспособность семафоров была написана программка, работа которой
заключалась в следующих шагах:
§
Создание и инициализация семафора.
§
Создание 10 нитей микроядра.
§
Каждая нить пыталась войти в критический важный
участок с помощью семафора.
§
Захвативши ресурс, нить засыпала на некоторое
время (симулируя работу в критически важном участке), в то время как другие
нити были заблокированы в ожидании освобождения ресурса.
§
После выхода из критически важного участка, нить
сама себя уничтожала.
Исходные коды библиотеки,
сервера, и тестовой программки можно просмотреть в дополнении.
Сервер ethernet карт проектировался с целью
возможности использования ethernet карточек в приложениях работающих на
основе микроядра. На первый план ставились следующие требования:
§
Поддерживать
максимальное количество карт от различных производителей и модификаций.
§
Предоставить пользователю простой, доступный и
унифицированный интерфейс контроля и использования сетевых карт.
§
Довести до максимальной производительности работу,
как и сетевых аппаратных средств, так и серверного программного обеспечения.
§
Предоставляемый интерфейс должен наследовать
классические концепции UNIX
систем и отображать устройство в виде файла.
§
Сервер должен защищать привилегированные участки
памяти, работу оборудования и не должен наносить вредоносного действия
клиентского ПО.
§
Должны быть приняты меры для предотвращения
вхождения сервера в тупиковые ситуации
§
Должна быть предусмотрена возможность, легко
добавлять в будущем новые модели сетевого оборудования.
§
Кроссплатформенность (сервер должен работать на
различных аппаратных архитектурах)
Разработка нового драйвера, для какого либо аппаратного
продукта выпускаемого производителем на рынок может потребовать значительных
затрат. К ним относятся изучение технической документации устройства. Некоторые
производители выпускают продукт уже с готовыми драйверами для конкретной ОС (например,
Windows),
закрывая все спецификации работы производимого устройства. Заставить работать
такое устройство в другом окружении является довольно проблематичным занятием.
А если и удается добиться от производителя техническою спецификацию на сетевую
карточку, то как правило она насчитывает не одну сотню страниц (Например
описание работы карточки 3Com 905-B насчитывает 300 страниц). На обработку информации такого
объема нужно много усилий и времени. И разработку драйвера для такого класса
устройства сможет провести высоко квалифицированный специалист. Для решения
проблем этого плана было решено найти ПО, которое содержит базу драйверов
сетевых карт. Как возможные варианты рассматривались следующие продукты:
Ядра ОС Linux, FreeBSD, и т.п. – данные ядра совершенно
открыты, включают драйвера для многих и различных устройств, постоянно
обновляют и добавляют новые версии. Но как один большой недостаток подсистема
работы с Ethernet карточками очень тесно связана с другими подсистемами,
такими как TCP/IP стек, обработкой времени, планирования, абстракции оборудования и т.д.
То есть просто взять и вырезать код драйверов и приспособить его для работы в
другом незнакомом окружении практически невозможно. К счастью, мир открытого ПО
не ограничивается одними ОС. После тщательного поиска было найдено ПО Etherboot. Главная задача Etherboot-а – загрузка ОС посредством сети. В
частности Etherboot используется для организации бездисковых рабочих станций.
Вся работа ПО Etherboot сводится к загрузке образа ОС через сеть с FTPF сервера, и передачи управления ядру
этой ОС. Во время проведения практики был исследован исходный код Etherboot и выявлено, что главными
компонентами являются следующие подсистемы:
§
Код для работы с протоколом TFTP
§
Код для работы с PCI шиной
§
Код инициализирующий систему В/В конкретной
архитектуры.
§
Код драйверов сетевых карт (которые архитектурно
независимые)
Единственный обнаруженный
недостаток драйверов был выявлен уже при написании Ethernet сервера, и заключается в том, что
все доступные драйвера спроектированы для работы в монопольном режиме. Этого и
следовало ожидать: так как при загрузке ядра некой ОС не требуется участие
более одной сетевой карты. Препятствие в использовании кода драйверов из Etherboot было легко решено упаковыванием
глобальных переменных драйвера в структуру данных, отвечающую за обнаруженную
сетевую карту. То есть, если в системе будет обнаружено две сетевых сетевые
карты, и если даже одной модели, будут созданы две структуры, которые содержат
индивидуальные настройки каждой сетевой карточки в отдельности. В частности,
большинство драйверов сохраняют там базовый адрес В/В, адрес участка DMA, MAC адрес, режим работы (HalfDuplex, FullDuplex) и какие типы пакетов можно
применять (broadcast, multicast, unicast). Итак, была найдена база драйверов, сетевых карт, которая
постоянно обновляется и совершенствуется и находится в свободном доступе (под
лицензией GPL).
Каждый драйвер
представляет стандартный набор функций, в независимости какое оборудование оно
обслуживает. Будь то драйвер для карточек построенных на чипсете Tulip, или на 3C90x и т.д., все они наследуют следующие функции:
1.
static int probe (struct nic *nic,
struct pci_device *pci);
Функции передаются номер PCI шины и номер функции. Если по этим
реквизитам драйвер находит знакомое ему устройство (по одному из полей VendorID, DeviceID, ClassID) то эта функция возвращает
положительный результат, свидетельствующий, что данное PCI устройство распознано и драйвер
знает и умеет как им управлять.
2.
static void transmit(
struct nic *nic,
const char d, /
Destination */
unsigned int t, /* Type */
unsigned int s, /* size */
const char p) /
Packet */);
Этой функции передается
указатель на участок памяти, где содержится пакет (фрейм, кадр) который
необходимо отправить. Понятно, что за содержимое пакета драйвер ответственности
не несет, он даже не знает какие данные хранит этот пакет, то есть, он никак не
интерпретирует его содержимое. Единственная задача испустить в Etherner-сеть данный пакет через знакомое ему
устройство. Также функция получает размер посылаемого пакета.
Существует два способа
реализации драйверов в ПО Etherboot. Они зависят от того, как содержимое
пакета попадает в буфер самого устройства:
§
С использованием команд ассемблера inb/outb, inw/outw,
inl/outl
§
С использованием DMA
Выбор способа реализации
драйвера в первую очередь зависит от конкретного устройства. Но многие
современные NIC поддерживают два способа (например, чип Tulip). В каком режиме будет происходить
передача данных между карточкой и памятью компьютера устанавливается в
конфигурационном регистре устройства.
У каждого способа были
выявлены свои недостатки и преимущества. Во время использования in/out команд идет постоянное использование
CPU в пересылке
байтов между оперативной памятью и PCI шиной. Если же драйвер спроектирован
с использованием DMA, то участие CPU в передаче данных не требуется. Для устройства
достаточно указать адрес, где начинается пакет, его размер и дать отмашку, что
пакет сформирован. Тогда сетевая карточка без участия CPU считает весь пакет, используя,
контролер DMA и выставит соответствующий флаг, свидетельствующий, что память где
находится тело пакета можно модифицировать.
Программное обеспечение Etherboot использует защищенный режим работы
процессора для возможности использования 32-х битной адресации. Но
инициализирует работу подсистемы виртуальной памяти таким образом, что
виртуальный адрес прямо соответствует линейному физическому адресу. То есть при
работе драйверов, которые используют режим DMA, нет необходимости дополнительно
преобразовывать виртуальный адрес в
физический адрес. Но в нашем случае, т.е. при работе драйвера как нити
микроядра виртуальный адрес не соответствует физическому адресу. Необходимо
вводить следующие функции:
uintptr_t phys_to_virt(uintptr_t);
uintptr_t virt_to_virt(uintptr_t);
Данные функции должен
предоставлять другой сервер микроядра, который ведает выделением памяти.
3.
static int poll(struct nic *nic);
(Опросить устройство о
новом поступившем пакете). Этой функции предоставляется участок памяти, куда
следует положить пакет. Все выше перечисленные проблемы для функции transmit затрагивают функцию poll. В случае если сетевой картой пакет
получен пакет, данная функция возвращает положительный ответ, а также размер
полученного пакета (с учетом Ethernet заголовка). Никакой интерпретации
полученного пакета драйвер не производит.
Методика постоянного
опроса имеет свои области применения и использования. Как противоположный метод
взаимодействия с сетевыми карточками, является использование аппаратных
прерываний от устройства. Следует сказать пару слов защиты в сторону выбранного
нами способа (опроса). Так как ethernet сервер является нитью микроядра,
можно очень гибко управлять его работой и приспосабливать к конкретным задачам.
Используя системные вызовы микроядра можно проектировать системы, от которых
требуется реакция в реальном времени. Например, если задать опрашивающей нити
более высокий приоритет, чем остальным, а в самой нити использовать системный
вызов L4_ThreadSwitch(l4_nilthread); (освободить процессор), можно реализовать разные схемы
работы, и устанавливать жесткие временные границы. Даная методика также
задействована в архитектурах построения драйверов mkLinux (модифицированной версии Liunx), которая отвечает требованиям RealTime системам. При будущем проектировании
QoS систем на
основе данного сервера не должно возникнуть никаких затруднений.
4.
static void disable(struct nic *nic);
Драйвер при вызове этой
функции, выставляет флаги в конфигурационном регистре устройства линий Rx и Tx.
То есть, в таком состоянии карточка отказывается от посылки и приема новых
пакетов.
Ethernet сервер является
сервером микроядра L4. Он выполняется в своем защитном домене, в своем адресном
пространстве. Никакой другой компонент ОС не сможет повредить работе данного
сервера. Сервер состоит из двух нитей микроядра. Первая нить постоянно
опрашивает открытые пользовательским кодом сетевые карточки (для которых была
вызвана библиотечная функция open()). Другая же нить находится в замороженном
состоянии, в состоянии ожидания запроса от клиентского приложения. Эта нить
использует процессорное время только при поступлении запроса от клиента.

Рис.1 Две нити ethernet сервера
Основные узлы
во время работы Ethernet сервера можно отобразить следующей схемой:

Рис.2. Взаимодействие между сервером и клиентами
В примере рассмотрена
работа двух клиентов, которые взаимодействуют с сервером. Каждому клиенту
библиотека предоставляет следующие функции:
int n_init();eth_t * n_open(int nic_num);int n_close(eth_t *nic);int n_found();size_t n_write(eth_t *nic, const void *tx_packet, size_t count);size_t n_read(eth_t *nic, void *rx_packet, size_t count);void n_mac(eth_t *nic, unsigned char *mac);
При таком подходе
построения архитектуры, от клиента полностью спрятаны все технические и
аппаратные аспекты работы сетевой карты. Сетевая карточка отображается как
обычный файл. То есть клиенты взаимодействуют с сервером ether_server косвенно,
посредством библиотеки ether_lib.
Сервер ether_server, и библиотека ether_lib построены с
использованием IDL4 (Magpie). IDL сокращение к Interface Description Language.
Этот язык берет свои корни от кластерной, многопроцессорной, мультикомплексной
обработки информации в параллельном режиме, и используется при описании
интерфейсов некого узла.
На этом языке описано как
работает сервер. В нашем случае сервер экспортирует один интерфейс, который
может обслуживать следующие запросы:
int init(out unsigned nic_base, out int total_nics);int transmit(in int nic_num);int open(in int nic_num);
int close(in int nic_num);
После обработки файла
описывающего интерфейсы IDL компилятор создает два заголовочных файла для
написания библиотеки и самого сервера. Тем самым отпадает необходимость
непосредственного кодирования механизма передачи сообщений между библиотекой и
сервером. То есть IDL компилятор прячет в заголовочных файлах все аспекты использования
системных вызовов микроядра.
При использовании IDL
компилятора отпадает необходимость переписывать код взаимодействия между
библиотекой и сервером при смене спецификаций системных вызовов микроядра, а
также при смене аппаратной платформы. В некоторых случаях IDL компилятор строит
более эффективный код в сторону производительности, так как ему известна
спецификация микроядра (в какой регистров следует занести параметр перед
выполнением системного вызова).
Во время открытия сетевой
карты, сервер отображает два участка памяти из своего ЗД в ЗД клиента. Один с
возможностью записи и чтения другой только с правами на чтение. Участок памяти,
который доступен только на чтение, содержит очередь с прочитанными
(полученными) пакетами. То есть сервер получает пакеты постоянно, в
независимости запрашивает или нет клиент, следующий пакет. В случае если,
очередь переполнена, и клиент не успевает выбирать из очереди полученные
пакеты, Ethernet сервер начинает все последующие поступившие пакеты отбрасывать
до тех пор, пока в очереди не появится свободное место.
Участок памяти,
предоставляемый на запись, предназначен для того чтоб клиент мог поместить
пакет для отправки, и также отметить какой последний пакет был прочитан из
входной очереди.

Рис.3. Участки памяти отображены в адресное
пространство клиента
Во время обращения к
серверу, происходит проверка сервером на соответствие корректности запроса.
Сервер спрашивает ID защитного домена клиента подавшего запрос на обслуживание
и сравнивает со своими внутренними структурами, в которых фиксируется, какой
защитный домен вызвал функцию open для данной сетевой карточки.
При такой организации
взаимодействия сервера и клиента только один ЗД монопольно владеет сетевой
карточкой, начиная от команды open, заканчивая командой close. Освободивши
карточку, другое приложение в другом защитном домене сможет открыть ее и стать
новым монопольным владельцем.
Следует заметить, что все
взаимодействие между клиентом и сервером состоит из синхронных сообщений
микроядра. То есть одна из нитей сервера должна постоянно ждать входящего
запроса от клиента. Если сервер обслуживает некий запрос от клиента, то
поступивший в данный момент другой запрос получит отказ.
Каждый драйвер сетевой
карточки компилируется в виде отдельной единицы. В любом из драйверов
выделяется секция, в которой содержатся указатели на стандартные для сетевой
карты операции (probe, poll, transmit, disable). Эта секция помечается
специфическим для компилятора методом. И
во время компоновки все эти участки располагаются в одном сегменте,
последовательно друг за другом. Во время инициализации Ethernet сервера
происходит последовательное сканирование этого участка, в итоге сервер знает,
какие в его расположении присутствуют драйвера. Для достижения выше описанной
схемы был написан скрипт на специфическом языке, который передавался на вход
линкеру (ld) вместе с другими объектными файлами. В итоге операция добавления
или изъятия нового драйвера устройства сводится к операции копирования \ удаления
исходного файла из папки компилируемых файлов.
Во время загрузки
ethernet сервера происходит полное сканирование PCI-шины, каждое найденное
устройство последовательно передается функции probe из каждого драйвера. В
случае положительного ответа данной функции заводится служебные структуры.
Также следует заметить
одну особенность при реализации ethernet сервера. Каждому из драйверов
необходимо иметь доступ к функциям реализующие точные временные задержки с
точностью до 1 микросекунды. Данные функции в Etherboot были реализованы с использованием стандартной микросхемы i8254 и подсчета количества тиков центрального
микропроцессора за одно переключение таймера. Во время портирования драйверов
было решено непосредственно использовать системный вызов микроядра L4_SystemClock(); возвращающий текущее
значение времени в микросекундах с начала работы системы. (Переполнение счетчика
микроядра происходит раз в 1000 лет). Такой подход способствует более легкому и
безболезненному портированию приложения на новые архитектуры.
Сканирование устройств
расположенных на шине PCI решено производить непосредственно, без использования
функций BIOS-а.
Для тестирования
работоспособности предложенного сервера было написано маленькое приложение,
представляющие собой клиента ethernet сервера, и выполняющего одну единственную
функцию – выдачу ответов на ICMP запросы. Алгоритм работы данного приложения
достаточно примитивен и представлен следующей диаграммой:

Рис.4. алгоритм работы приложения, отвечающего на
ICMP запросы
Следует напомнить, что
такой простенький клиент создан только в тестовых рамках и предназначен для тестирования
ethernet сервера. Он имеет два недостатка:
§
Размер ICMP пакета не может превышать 1500 байт
(с учетом ethernet заголовка), в связи с ограничением размера на ethernet кадр.
§
Будет работать только в рамках одного Ethernet
домена. То есть, кто посылает ICMP – запросы и наш пример должны выполняться на
компьютерах расположенных в одном домене коллизий. Если же они находятся в
разных сегментах сети, работоспособность нашего примера не возможна. Данное
ограничение вводится тем, что клиент только меняет местами адреса сетевых карт
отправителя и получателя (контрольная сумма пакета от этого не меняется).
Click представляет собой модульный, программный
роутер разработанный Computer Science and Artificial Laboratory
при MIT (Massachusetts institute of technology). В дальнейшем разрабатывался
Mazu Networks, потом в Center for
Internet Research при
ICSI (International Computer Science Institute) а теперь в
Computer Science Department в UCLA (University of California Los Angeles).
Роутеры построены на базе
Click очень гибки, конфигурируемые, и легки в понимании. Также они показывают
очень высокою производительность по сравнению с другими программными роутерами.
Как наглядный пример на Pentium 3 работающем на 700 Mhz Click IP роутер может
обслуживать до 435000 64-х байтных пакетов за 1 сек.
Роутер представляет собой
коллекцию взаимосвязанных модулей называемыми элементами. Элементы контролируют
каждый аспект поведения роутера, начиная от коммуникации с аппаратными
устройствами, до модификации пакета, до создания очередей, построения политик
отказа и отброса пакетов, а также планирования приоритетов для пакетов.
Индивидуальные элементы могут иметь удивительно чудовищное поведение, и их
легко реализовать на языке C++. Работа роутера задается конфигурационным
файлом, в котором описываются взаимосвязи между элементами. Файл конфигурации
пишется на простеньком специфическом языке. Имея такую инфраструктуру легко
описать рабочую конфигурацию, которая бы соответствовала поведению стандартного
IP роутера 2-го/3-го уровня. Для Click роутера созданы элементы, которые могут,
выполняют NAT, IPSEC, и т.д.
Click роутер
умеет работать в следующих режимах:
§
На уровне пользователя (как обычное прикладное
ПО) используя библиотеку для захвата пакетов libpcap
§
Как модуль ядра Linux версии 2.2 и 2.4
(управление роутером в данном случае производится через файловую систему /proc)
§
Как часть ядра FreeBSD.
В ходе выполнения
практики было решено спортировать Click роутер для работы на микроядре L4. Для
решения поставленного задания были сделаны следующие шаги:
§
С сайта производителя была загружена последняя
версия Click роутера.
§
Была полностью проанализирована работа Click
роутера, в частности были запущенны тестовые конфигурации в ОС Linux в
пользовательском режиме.
§
Был разобран синтаксис написания конфигурационного
файла.
§
Изучение внутренней структуры Click роутера.
§
Была изучена система построения роутера для
конкретной платформы (Linux UM, Linux module, FreeBSD).
Для построения бинарного
исполняемого файла (модуля) из исходных кодов разработчики использовали систему
GNU autotools (Automake, Autoconf, Libtool). Для каждой платформы разработчикам
пришлось написать отдельные специфические элементы роутера (участвуют при
написании конфигурации) которые бы получали / отсылали пакеты со специфического
устройства. Также для каждой платформы созданы конфигурационные заголовочные
файлы, в которых определены на языке C++ необходимые макросы и константы.
§
для запуска Click роутера как клиента микроядра
были проведены следующие шаги:
§
Система сбора, компиляции, и выявления
зависимостей была перенесена на Scons.
§
Были добавлены заголовочные конфигурационные
файлы, в которых задавались параметры и пути взаимодействия с рабочим
окружением микроядра.
§
Были адаптированы некоторые необходимые функции
(которые отсутствуют в Kenge/Iguana), а именно:
o
gettimeofday для определения текущего времени
o
ntohl, ntohs, htonl, htons для перевода
следования байтов между машиной и сетью
§
Добавлены элементы tonic, pollnic - для работы Click
роутера с ethernet сервером, представленным выше.
§
Изменены загрузочные и функции инициализации с
целью возможности подгрузки конфигурационного файла роутера как некого модуля.
Click роутер
был запущен на компьютере Pentium 3 со следующей конфигурацией:
in :: PollNic(0);
out :: Print(p_out) -> ToNic(0);
c :: Classifier(12/0806 20/0001, // ARP request
12/0806 20/0002, // ARP reply
12/0800, // IP packet
-); // All other
//ns8390
00:00:1c:09:37:71
//3Com 00:01:02:de:53:5e
//arpq ::
ARPQuerier(195.178.197.241, 00:00:1c:09:37:71);
arpq ::
ARPQuerier(195.178.197.241, 00:01:02:de:53:5e);
in -> Print(p_in)
-> c;
arpq[0] -> out;
c[2]
Strip(14)
CheckIPHeader(INTERFACES 195.178.197.241/24)
IPPrint(ICMP)
ICMPPingResponder
[0]arpq;
c[0]
// ->
ARPResponder(195.178.197.241 00:00:1c:09:37:71, 10.0.0.1 52:54:00:12:34:57)
ARPResponder(195.178.197.241
00:01:02:de:53:5e, 10.0.0.1 52:54:00:12:34:57)
Print(Something_want_to_know_my_MAC)
out;
c[1]
Print(Answer_to_my_APR_Request)
-> [1]arpq;
c[3]
Discard;
//Comments:
//-> CheckICMPHeade
Любой компьютер,
пославший Echo - запрос получал Echo – ответ. При запуске команды ping,
варьировали параметрами -i (время между посылками ICMP запросов) и -s (размер посылаемого ICMP пакета). На все
запросы, при любых параметрах были получены ответы.
В итоге выполнения
настоящей проектно-эксплуатационной практики были получены следующие основные
результаты и выводы:
1.
Получено
представление о построении ОС на основе микроядра и его окружения.
2.
Приобретены
навыки разработки компонентов работающих в окружении микроядра.
3.
Получены
глубокие знания о работе микроядра
L4::Pistachio, его архитектуре.
4.
Разработаны
службы, работающие на основе микроядра, и предоставляющие стандартный интерфейс
семафоров POSIX, а также отображающие сетевые устройства в виде файла.
5.
Написаны
тестовые программы для проверки работоспособности представленных выше серверов.
6.
Перенесен
программный роутер Click для работы в окружении микроядра.
Микроядро представляет
гибкую инфраструктуру, для проведения опытов в построении операционных систем
будущего поколения. Было показано как можно без больших затрат времени создать
набор необходимых компонентов ОС. Радует тот факт, что удалось заставить
работать ПО Click роутер в окружении микроядра. На данном этапе тестированию
подверглись лишь базовые возможности данного ПО. В будущем, особый интерес
представляет сравнение производительности по пересылке пакетов роутером Click
как в родной исполнительной среде (Linux, FreeBSD) так и в новой рабочей
обстановке микроядра. Для полной картины, также следует сравнить функциональные
и производительные характеристики между роутером Click и аппаратным роутером
Cisco. Данные вопросы являются очень актуальными в настоящие время, в связи широким распространением
телекоммуникационных технологий во всех сферах человеческой деятельности.
1. Стивенс У. Unix: Взаимодействие процессов. - СПб.: Питер, 2002. – 576 с.: ил
2.
Gernot Heiser. Iguana User Manual. http://www.ertos.nicta.com.ua/Software/Iguana/userman.pdf
3. L4 X.2 Rev 6 Reference Manual http://l4hq.org/docs/manuals/l4-x2-20050608.pdf
4. Nicholas
FitzRoy-Dale. Magpie — An
Extensible Interface Generator. http://nicta.com.au/ertos.html
Исходный код:
§
сервера
семафоров POSIX
§
библиотеки
для его использования.
§
приложения
для тестирования работоспособности
iguana\sem\src\sem_server.c :
#include <stdio.h>
#include <assert.h>
#include <l4/ipc.h>
#include <l4/kdebug.h>
#include <l4/thread.h>
#include <naming/naming.h>
#include <sem_common/sem_common.h>
#define L4_SEM_NSEMS_MAX (255) ///< maximum semaphores
int main(int argc, char **argv)
{
L4_ThreadId_t
caller;
L4_ThreadId_t
me;
L4_ThreadId_t
wakeup_thrd;
L4_MsgTag_t
tag;
L4_Msg_t
msg;
L4_Word_t
label;
int
nsems; ///<
Activated semaphores presents in system
printf("Semaphore
thread startup\n");
nsems
= 0;
L4_KDB_SetThreadName(L4_Myself(),
"sem_srv");
L4_Accept(L4_UntypedWordsAcceptor); ///< We accept only untyped words
me
= L4_Myself();
assert(naming_insert("semaphore",
me.raw) == 0);
while(1)
{
tag
= L4_Wait(&caller);
if(L4_IpcFailed(tag))
{
continue;
}
L4_MsgStore(tag,
&msg);
label
= L4_Label(tag); ///< In the
label we save the type of request
switch(label)
{
case
L4_SEM_INIT:
if(nsems
== L4_SEM_NSEMS_MAX)
label
= L4_SEM_CALL_FAILED;
else
label
= L4_SEM_CALL_SUCCESS;
L4_MsgClear(&msg);
L4_Set_Label(&msg.tag,
label);
L4_MsgLoad(&msg);
tag
= L4_Send(caller);
if(L4_IpcSucceeded(tag)
&& label == L4_SEM_CALL_SUCCESS)
{
nsems++;
}
break;
case
L4_SEM_DESTROY:
L4_MsgClear(&msg);
L4_Set_Label(&msg.tag,
L4_SEM_CALL_SUCCESS);
L4_MsgLoad(&msg);
tag
= L4_Send(caller);
if(L4_IpcSucceeded(tag))
{
nsems--;
}
break;
case
L4_SEM_POST:
wakeup_thrd.raw
= L4_MsgWord(&msg, 0);
if(wakeup_thrd.raw
== L4_nilthread.raw)
{
label
= L4_SEM_NO_THRD_TO_UBLOCK;
}
else
{
L4_MsgClear(&msg);
L4_Set_Label(&msg.tag,
L4_SEM_CALL_SUCCESS);
L4_MsgLoad(&msg);
tag
= L4_Send(wakeup_thrd);
if(L4_IpcFailed(tag))
{
label
= L4_SEM_NO_THRD_TO_UBLOCK;
}
else
{
label
= L4_SEM_CALL_SUCCESS;
}
}
L4_MsgClear(&msg);
L4_Set_Label(&msg.tag,
label);
L4_MsgLoad(&msg);
L4_Send(caller);
break;
default:
break;
}
}
assert(!"Should
reach here\n");
return
0;
}
libs\sem\include\semaphore\semaphore.h
:
#ifndef L4_SEMAPHORE_H
#define L4_SEMAPHORE_H
extern L4_ThreadId_t semaphore_thread; ///< must be defined and initialised by
application
typedef struct
{
int
counter;
L4_ThreadId_t
running_thrd;
L4_ThreadId_t
blocked_thrd;
int
valid;
} sem_t;
int sem_init(sem_t *sem, int pshared, unsigned
int value);
int sem_destroy(sem_t *sem);
int sem_post(sem_t *sem);
int sem_getvalue(sem_t *sem, int *valp);
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
#endif ///< end
L4_SEMAPHORE_H
libs\sem\src\sem_lib.c :
#include <stdio.h>
#include <l4/types.h>
#include <l4/ipc.h>
#include <l4/message.h>
#include <sem_common/sem_common.h>
#include <semaphore/semaphore.h>
#define L4_SEM_VALUE_MAX (32767)
//#define l4_SEM_VALID_SEM 0xA6CEOCB3FB9B13AE
#define L4_SEM_VALID_SEM 0x26ce0cb3
inline int cmpxchg(volatile L4_Word_t * dest,
L4_Word_t cmp_val, L4_Word_t new_val);
inline int cmpxchg(volatile L4_Word_t * dest,
L4_Word_t cmp_val, L4_Word_t new_val)
{
L4_Word_t
tmp;
__asm__
__volatile__
(
"cmpxchgl %1, %3 \n\t"
:
"=a" (tmp) ///< 0 EAX, return val
:
"r"
(new_val), ///< 1 reg,
new value
"0"
(cmp_val), ///< EAX,
compare value
"m"
(*dest) ///< mem,
destination operand
:
"memory", "cc"
);
return
tmp == cmp_val; ///<
return true if change is succesful
}
///< flag pshared isn't used. semaphore can
be used in all PD, if it in the common memory.
int sem_init(sem_t *sem, int pshared, unsigned
int value)
{
L4_Msg_t
msg;
L4_MsgTag_t
tag;
L4_Word_t
label;
if(value
> L4_SEM_VALUE_MAX || value < 0)
return
-1; ///< errno = EINVAL
if(sem->valid
== L4_SEM_VALID_SEM)
return
-1; ///< semaphore already
inited
L4_MsgClear(&msg);
L4_Set_Label(&msg.tag,
L4_SEM_INIT);
L4_MsgLoad(&msg);
tag
= L4_Call(semaphore_thread);
label
= L4_Label(tag);
if(L4_IpcFailed(tag)
|| label != L4_SEM_CALL_SUCCESS)
return
-1;
sem->counter
= value;
sem->blocked_thrd
= L4_nilthread;
sem->running_thrd
= L4_nilthread;
sem->valid
= L4_SEM_VALID_SEM;
///<
if value = 0, first sem_wait call will be blocked
return
0;
/*
The
function will fail if:
[EINVAL]
The
value argument exceeds SEM_VALUE_MAX.
[ENOSPC]
A
resource required to initialise the semaphore has been exhausted, or the limit
on semaphores (SEM_NSEMS_MAX) has been reached.
[EPERM]
The
process lacks the appropriate privileges to initialise the semaphore.
*/
}
int sem_destroy(sem_t *sem)
{
L4_Msg_t
msg;
L4_MsgTag_t
tag;
L4_Word_t
label;
L4_ThreadId_t
myself;
int
success;
myself
= L4_Myself();
do
{
success
= cmpxchg(&sem->running_thrd.raw, L4_nilthread.raw, myself.raw);
if(sem->valid
!= L4_SEM_VALID_SEM)
return
-1; ///< errno == EINVAL
///<
The sem argument does not refer to a valid semaphore.
if(sem->running_thrd.raw
!= myself.raw)
L4_ThreadSwitch(sem->running_thrd);
}while(!success);
if(sem->counter
< 0 || !L4_IsNilThread(sem->blocked_thrd))
{
sem->running_thrd
= L4_nilthread;
return
-1; ///< errno == EBUSY
///<
some thread blocked on this semaphore
}
L4_MsgClear(&msg);
L4_Set_Label(&msg.tag,
L4_SEM_DESTROY);
L4_MsgLoad(&msg);
tag
= L4_Call(semaphore_thread);
label
= L4_Label(tag);
if(L4_IpcFailed(tag)
|| label != L4_SEM_CALL_SUCCESS)
{
sem->running_thrd
= L4_nilthread;
return
-1;
}
sem->valid
= 0;
return
0;
/*
The
function will fail if:
[EINVAL]
The
sem argument is not a valid semaphore.
[ENOSYS]
The
function sem_destroy() is not supported by this implementation.
[EBUSY]
There
are currently processes blocked on the semaphore.
*/
}
int sem_post(sem_t *sem)
{
L4_Msg_t
msg;
L4_MsgTag_t
tag;
L4_Word_t
label;
L4_ThreadId_t
myself;
int
success;
myself
= L4_Myself();
do
{
success
= cmpxchg(&sem->running_thrd.raw, L4_nilthread.raw, myself.raw);
if(sem->valid
!= L4_SEM_VALID_SEM)
return
-1; ///< errno == EINVAL
///<
The sem argument does not refer to a valid semaphore.
if(sem->running_thrd.raw
!= myself.raw)
L4_ThreadSwitch(sem->running_thrd);
}while(!success);
if(sem->counter
== L4_SEM_VALUE_MAX)
{
sem->running_thrd
= L4_nilthread;
return
-1; ///< Maximum value of
semaphore reached
}
sem->counter++;
if(sem->counter
<= 0)
{
sem->running_thrd
= L4_nilthread;
do
{
L4_MsgClear(&msg);
L4_Set_Label(&msg.tag,
L4_SEM_POST);
L4_MsgAppendWord(&msg,
sem->blocked_thrd.raw);
L4_MsgLoad(&msg);
tag
= L4_Call(semaphore_thread);
label
= L4_Label(tag);
if(L4_IpcFailed(tag))
{
return
-1;
}
else
if(label == L4_SEM_NO_THRD_TO_UBLOCK)
{
L4_ThreadSwitch(sem->blocked_thrd);
///<
Maybe wait-thread don't have time to join to
///<
L4_Receive, so give it chanse to do this.
}
}while(label
!= L4_SEM_CALL_SUCCESS);
sem->blocked_thrd
= L4_nilthread;
}
else
{
sem->running_thrd
= L4_nilthread;
}
return
0;
/*
The
function will fail if:
[EINVAL]
The
sem argument is not a valid semaphore.
*/
}
int sem_getvalue(sem_t *sem, int *valp)
{
if(sem->valid
!= L4_SEM_VALID_SEM)
return
-1; ///< errno == EINVAL
///<
The sem argument does not refer to a valid semaphore.
*valp
= sem->counter;
return
0;
/*
The
function will fail if:
[EINVAL]
The
sem argument is not a valid semaphore.
*/
}
int sem_wait(sem_t *sem)
{
int
success;
L4_MsgTag_t
tag;
L4_Word_t
label;
L4_ThreadId_t
myself;
myself
= L4_Myself();
do
{
success
= cmpxchg(&sem->running_thrd.raw, L4_nilthread.raw, myself.raw);
if(sem->valid
!= L4_SEM_VALID_SEM)
return
-1; ///< errno == EINVAL
///<
The sem argument does not refer to a valid semaphore.
if(sem->running_thrd.raw
!= myself.raw)
L4_ThreadSwitch(sem->running_thrd);
}while(!success);
sem->counter--;
if(sem->counter
< 0)
{
sem->running_thrd
= L4_nilthread;
do
{
success
= cmpxchg(&sem->blocked_thrd.raw, L4_nilthread.raw, myself.raw);
if(!success)
L4_ThreadSwitch(L4_nilthread);
}while(!success);
tag
= L4_Receive(semaphore_thread);
label
= L4_Label(tag);
if(L4_IpcFailed(tag)
|| label != L4_SEM_CALL_SUCCESS)
{
return
-1;
}
}
else
{
sem->running_thrd
= L4_nilthread;
}
return
0;
/*
The function will fail if:
[EINVAL]
The
sem argument does not refer to a valid semaphore.
[EINTR]
A
signal interrupted this function.
*/
}
int sem_trywait(sem_t *sem)
{
L4_ThreadId_t
myself;
int
lock_stat;
int
success;
myself
= L4_Myself();
do
{
success
= cmpxchg(&sem->running_thrd.raw, L4_nilthread.raw, myself.raw);
if(sem->valid
!= L4_SEM_VALID_SEM)
return
-1; ///< errno == EINVAL
///<
The sem argument does not refer to a valid semaphore.
if(sem->running_thrd.raw
!= myself.raw)
L4_ThreadSwitch(sem->running_thrd);
}while(!success);
if(sem->counter
> 0)
{
lock_stat
= 0;
sem->counter--;
}
else
{
lock_stat
= -1; ///< errno = EAGAIN
}
sem->running_thrd
= L4_nilthread;
return
lock_stat;
/*
The function
will fail if:
[EINVAL]
The
sem argument does not refer to a valid semaphore.
[EAGAIN]
The
semaphore was already locked, so it cannot be immediately locked by the
sem_trywait() operation
[EINTR]
A
signal interrupted this function.
*/
}
example\sem_test\src\sem_example.c
:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <iguana/thread.h>
#include <l4/types.h>
#include <l4/ipc.h>
#include <iguana/memsection.h>
#include <naming/naming.h>
#include <semaphore/semaphore.h>
L4_ThreadId_t semaphore_thread;
#define THREADS 7
sem_t *sem_lock;
int thrd_no = 1;
void thread(void);
void thread(void)
{
L4_ThreadId_t
l4thrd;
thread_ref_t
thrd;
int
value;
L4_Time_t
sleep;
char
*stack_top;
int
my_no;
L4_ThreadId_t
me;
sleep.period.m
= 1023;
sleep.period.e
= 6;
sleep.period.a
= 0;
my_no
= thrd_no;
me
= L4_Myself();
if(thrd_no
< THREADS)
{
thrd_no++;
memsection_create(0x1000,
(void*) &stack_top);
stack_top
+= 0x1000 - sizeof(uintptr_t);
thrd
= thread_create(&l4thrd);
thread_start(thrd,
(uintptr_t) thread, (uintptr_t) stack_top);
}
printf("%d
Thread:0x%lx Parent:0x%lx Child:0x%lx \n", my_no, (long)me.raw,
(long)L4_Pager().raw, (long)l4thrd.raw);
L4_Sleep(sleep);
if
(-1 == sem_wait(sem_lock)) {
assert(!"sem_wait didn't return
success\n");
}
sem_getvalue(sem_lock,
&value);
printf("Sem
value = %d\n", value);
for
(int i = 1; i<= 10; i++) {
printf("%d",
my_no);
L4_Sleep(sleep);
}
fputc('\n',
stdout);
if
(-1 == sem_post(sem_lock)) {
// assert(!"sem_wait
didn't return success\n");
}
if(sem_destroy(sem_lock)
== 0)
{
printf("Semaphore
destroyed succesfuly by me: 0x%lx\n", me.raw);
}
thread_delete_self();
return;
}
int main(int argc, char** argv)
{
semaphore_thread.raw
= naming_lookup("semaphore");
int
shared = 1;
int
value = 1;
sem_lock
= (sem_t *)malloc(sizeof(sem_t));
if
(-1 == sem_init(sem_lock, shared, value)) {
assert(!"sem_init
didn't return success\n");
}
thread();
return 0;
}
Исходный код:
§
Сервера
сетевых ethernet карт (не включен, ввиду большего объема)
§
Библиотеки
для его использования
§
Тестового
приложения ping reply.
libs\ether_lib\include\ether_lib\ether_lib.h :
#ifndef _ETHER_LIB_H
#define _ETHER_LIB_H
#define PKT_BUF_SIZE 1536
#define ARP_ADDR_SIZE 6
#define MAX_SERVER_CAN_HOLD_PACKETS 50
typedef unsigned char uchar; // Special for
MAGPIE IDL
typedef struct
{
unsigned
char data[PKT_BUF_SIZE];
unsigned
int size;
} packet_t;
struct nic_state
{
packet_t
* rx_packets; //
buffer for recived packets, if no free space packet will be droped
int
* read_flags; //
array, edited by client, and show what packets was read
unsigned
char *tx_buf; //
Client write here packets body for transmit
unsigned
int *tx_buf_sz; // Size
of transmited packet
unsigned
char *arp_dest_addr; //
Client fill this field.
unsigned
char mac[6]; //
NIC's MAC address
};
typedef struct nic_state nic_state_t;
typedef struct
{
int
num; //nic number in system
int
rx_i; //index of next rx_packet
} eth_t;
int n_init(void);
eth_t * n_open(int nic_num);
int n_close(eth_t *nic);
int n_found(void);
size_t n_write(eth_t *nic, const void
*tx_packet, size_t count);
size_t n_read(eth_t *nic, void *rx_packet,
size_t count);
void n_mac(eth_t *nic, unsigned char *mac);
#endif /* _ETHER_LIB_H
*/
libs\ether_lib\src\ether_lib.c
:
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <iguana/types.h>
#include <iguana/object.h>
#include <naming/naming.h>
#include <ether_lib/ether_lib.h>
#include <interfaces/ether_client.h>
static nic_state_t *nics_state;
static object_t *ether_object;
static int found_nics;
int n_init()
{
objref_t
ether_server;
uintptr_t
info_base;
ether_server
= naming_lookup("ether_server");
assert(ether_server
!= 0);
ether_object
= object_get_interface(ether_server);
assert(ether_object
!=0 );
nic_init(ether_object->server,
&info_base, &found_nics, NULL);
nics_state
= (nic_state_t *) info_base;
return
0;
}
eth_t * n_open(int nic_num)
{
eth_t
*eth_ptr;
if(nic_open(ether_object->server,
nic_num, NULL) != 0)
return
NULL;
if((eth_ptr
= malloc(sizeof(eth_t))) == NULL)
return
NULL;
eth_ptr->num
= nic_num;
eth_ptr->rx_i
= 0;
return
eth_ptr;
}
int n_close(eth_t *nic)
{
if(nic_close(ether_object->server,
nic->num, NULL) !=0)
return
-1;
free(nic);
return
0;
}
int n_found()
{
return
found_nics;
}
size_t n_write(eth_t *nic, const void
*tx_packet, size_t count)
{
memcpy(nics_state[nic->num].tx_buf,
tx_packet, count);
*nics_state[nic->num].tx_buf_sz
= count;
return
nic_transmit(ether_object->server, nic->num, NULL);
}
size_t n_read(eth_t *nic, void *rx_packet,
size_t count)
{
size_t
size = 0;
int
i;
i
= nic->rx_i;
if(!nics_state[nic->num].read_flags[i])
{
size
= nics_state[nic->num].rx_packets[i].size;
memcpy(rx_packet,
nics_state[nic->num].rx_packets[i].data, size);
if(size)
{
nics_state[nic->num].read_flags[i]
= 1;
if(i
== MAX_SERVER_CAN_HOLD_PACKETS-1)
nic->rx_i
= 0;
else
nic->rx_i++;
}
}
return
size;
}
void n_mac(eth_t *nic, unsigned char *mac)
{
memcpy(mac,
nics_state[nic->num].mac, 6);
return;
}
example\ether_ping\src\ping-reply.c
:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <l4/ipc.h>
#include <iguana/thread.h>
#include <ether_lib/ether_lib.h>
#define ECHO_REPLY 0x00
#define IP_PACKET 0x0800
#define ARP_PACKET 0x0806
#define ARP_REPLY 0x02
#define ICMP_PACKET 0x01
#define ECHO_REQUEST 0x08
#define __bswap_constant_16(x) \
((((x) >> 8) & 0xff) | (((x)
& 0xff) << 8))
#define __bswap_constant_32(x) \
((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000)
>> 8) | \
(((x) & 0x0000ff00) <<
8) | (((x) & 0x000000ff) << 24))
#define __bswap_32(x) \
(__extension__ \
({
register unsigned int __v, __x = (x); \
if (__builtin_constant_p (__x)) \
__v = __bswap_constant_32 (__x); \
else \
__asm__ ("bswap %0" :
"=r" (__v) : "0" (__x)); \
__v; }))
# define
__bswap_16(x) \
(__extension__ \
({
register unsigned short int __v, __x = (x); \
if (__builtin_constant_p (__x)) \
__v = __bswap_constant_16 (__x); \
else \
__asm__ ("rorw $8, %w0" \
: "=r" (__v) \
: "0" (__x) \
: "cc"); \
__v; }))
#define ntohl(x) __bswap_32(x)
#define htonl(x) __bswap_32(x)
#define ntohs(x) __bswap_16(x)
#define htons(x) __bswap_16(x)
static unsigned int
convert_ip_string_to_int(const char *ip, int len);
static void prepare_icmp_echo_reply(unsigned
char *out_buf, unsigned char *in_buf, int len);
static void prepare_arp_reply(unsigned char
*out_buf, unsigned char *in_buf, int len, int sender_ip, unsigned char
*sender_ha);
/*-----------------------------------------------------------------------------------*/
int main(int argc, char **argv)
{
const
char ip[] = "192.168.0.1";
const
int nic_num = 1;
eth_t
*nic;
unsigned
char mac[6];
unsigned int my_ip;
unsigned
char rx_packet[PKT_BUF_SIZE];
unsigned
char tx_packet[PKT_BUF_SIZE];
int len;
n_init();
if((nic
= n_open(nic_num)) == NULL)
{
printf("Error
opening %d nic\n", nic_num);
thread_delete_self();
}
n_mac(nic,
mac);
printf("My
IP address: %s, send to me ping request!\n", ip);
my_ip
= convert_ip_string_to_int(ip, strlen(ip));
while(1)
{
//
Obtain the size of the packet and put it into the "len" variable
len = n_read(nic, rx_packet,
PKT_BUF_SIZE);
if(len == 0)
{
L4_ThreadSwitch(L4_nilthread);
continue;
}
//Is
an IP Packet??
if(htons(*(unsigned
short *)&(rx_packet[12])) == IP_PACKET)
{
//Is
an ICMP Packet??
if(rx_packet[23]
== ICMP_PACKET){
//Is
an ICMP echo request??
if(rx_packet[34]
== ECHO_REQUEST){
//Let's
prepare the ICMP echo reply
prepare_icmp_echo_reply(tx_packet,
rx_packet, len);
//Send
the reply
n_write(nic,
tx_packet, len);
}
}
}
else
{
//It is not an IP packet
//Is
an ARP Packet??
if(htons(*(unsigned
short *)&(rx_packet[12])) == ARP_PACKET){
//Asks
for my IP address??
if(htonl(*(unsigned
int *)&(rx_packet[38])) == my_ip){
//Let's
prepare the ARP reply
prepare_arp_reply(tx_packet,
rx_packet, len, my_ip, mac);
//Send
the reply
n_write(nic,
tx_packet, len);
}
}
}
}
assert(!"How
we here?");
return
0;
}
/*-----------------------------------------------------------------------------------*/
static unsigned int convert_ip_string_to_int(const
char *ip, int len){
unsigned int integer_ip=0, last_dot_pos=-1;
unsigned short i, n_dots = 0;
unsigned char part[3];
for(i =
0; i<len; i++)
if(ip[i] == '.'){
memcpy(part, &ip[last_dot_pos+1], i-last_dot_pos-1);
n_dots++;
last_dot_pos = i;
integer_ip |= atoi(part) << (32-(n_dots*8));
part[0] = part[1] = part[2] = 0x00;
if(n_dots == 3) break;
}
memcpy(part, &ip[last_dot_pos+1], len-last_dot_pos+1);
integer_ip |= atoi(part);
return
integer_ip;
}
/*-----------------------------------------------------------------------------------*/
void prepare_icmp_echo_reply(unsigned char
*out_buf, unsigned char *in_buf, int len){
//Copy
the incoming packet into the out buffer
memcpy(out_buf, in_buf, len);
//Swap
ethernet source and destination addresses
memcpy(out_buf, &in_buf[6], 6);
//ethout_dst = ethin_src
memcpy(&out_buf[6],in_buf, 6);
//ethout_src = ethin_dst
//Swap
IP source and destination addresses
memcpy(&out_buf[26],
&in_buf[30], 4); //ipout_src = ipin_dst
memcpy(&out_buf[30], &in_buf[26], 4); //ipout_dst = ipin_src
//The
IP checksum does not need to be recomputed because
//swaping source and destination addresses does not change
//the
result of the checksum computation.
//Change the type of the ICMP packet
out_buf[34] = ECHO_REPLY;
//Although in the case of ICMP we change some fields, the
//checksum field does not need to be recomputed because
//the
Linux TCP/IP stack does not check if it is right.
}
/*-----------------------------------------------------------------------------------*/
void prepare_arp_reply(unsigned char *out_buf,
unsigned char *in_buf, int len, int sender_ip, unsigned char *sender_ha){
unsigned
int net_ip = ntohl(sender_ip);
//Copy
the incoming packet into the out buffer
memcpy(out_buf, in_buf, len);
//Swap
ethernet source and destination addresses
memcpy(out_buf, &in_buf[6], 6);
//ethout_dst = ethin_src
memcpy(&out_buf[6], sender_ha, 6);
//ethout_src = our MAC
//Change the operation type to an ARP reply
out_buf[21] = ARP_REPLY;
//Swap
sender IP and sender MAC with target IP and target MAC
memcpy(&out_buf[38], &in_buf[28], 4); //target_ip = sender_ip
memcpy(&out_buf[32],
&in_buf[22], 6); //target_ha = sender_ha
memcpy(&out_buf[28], &net_ip, 4); //sender_ip = my_ip
memcpy(&out_buf[22], sender_ha, 6);
//sender_ha = MAC
}
Исходный код:
§
Элементов
Click роутера для взаимодействия с ethernet сервером
kenge\example\click\pollnic.hh
:
#ifndef CLICK_POLLNIC_HH
#define CLICK_POLLNIC_HH
#include <click/element.hh>
#include <click/task.hh>
#include <l4/types.h>
#include <l4/ipc.h>
#include <click/cxxprotect.h>
CLICK_CXX_PROTECT
#include <ether_lib/ether_lib.h>
CLICK_CXX_UNPROTECT
#include <click/cxxunprotect.h>
class PollNic : public Element { public:
PollNic();
~PollNic();
static
void static_initialize();
static
void static_cleanup() {};
const
char *class_name() const { return
"PollNic"; }
const
char *processing() const { return
PUSH; }
int
configure(Vector<String> &, ErrorHandler *);
int
initialize(ErrorHandler *);
void
cleanup(CleanupStage);
/*
process a packet. return 0 if not wanted after all. */
bool
run_task();
void
reset_counts();
uint32_t _npackets;
private:
unsigned _devindex;
int
_burst;
eth_t
*_nic;
Task
_task;
};
#endif
kenge\example\click\pollnic.сс :
#include <click/config.h>
#include <click/glue.hh>
#include <click/error.hh>
#include <click/confparse.hh>
#include <click/router.hh>
#include <click/skbmgr.hh>
#include <click/standard/scheduleinfo.hh>
#include <nics.hh>
#include "pollnic.hh"
PollNic::PollNic(): _task(this)
{
MOD_INC_USE_COUNT;
add_output();
}
PollNic::~PollNic()
{
MOD_DEC_USE_COUNT;
}
void PollNic::static_initialize()
{
}
int
PollNic::configure(Vector<String>
&conf, ErrorHandler *errh)
{
_burst = 8;
if
(cp_va_parse(conf, this, errh,
cpUnsigned, "device index",
&_devindex,
cpOptional,
cpUnsigned, "burst size",
&_burst,
cpEnd) < 0)
return
-1;
if((_nic
= nics_get_nic(_devindex)) == NULL)
{
printf("Error
opening %d nic\n", _devindex);
return
-1;
}
return 0;
}
int
PollNic::initialize(ErrorHandler *errh)
{
//
check for duplicate readers
void
*&used = router()->force_attachment("nic_reader_" +
String(_devindex));
if
(used)
return errh->error("duplicate
reader for device '%d'", _devindex);
used
= this;
/* if
(!router()->attachment("nic_writer_" + String(_devindex)))
errh->warning("no ToDevice(%d) in
configuration\n(\
Generally, you will get bad performance from
PollNic unless\n\
you include a ToDevice for the same device. Try
adding\n\
'Idle -> ToDevice(%d)' to your configuration.)",
_devindex, _devindex);
*/
ScheduleInfo::initialize_task(this, &_task, _nic != 0 , errh);
reset_counts();
return 0;
}
void
PollNic::reset_counts()
{
_npackets = 0;
}
void
PollNic::cleanup(CleanupStage)
{
}
bool
PollNic::run_task()
{
unsigned
char rx_packet[PKT_BUF_SIZE];
int len;
int
got = 0;
do
{
len
= n_read(_nic, rx_packet, PKT_BUF_SIZE);
if(len)
{
//printf("GOT!\n");
got++;
Packet
*p = Packet::make(rx_packet, len);
if(p
== 0)
{
printf("Can't
make packet!!!!\n");
break;
}
_npackets++;
output(0).push(p);
}
}while(len
&& got < _burst);
// if(got
== 0)
// L4_ThreadSwitch(L4_nilthread);
_task.fast_reschedule();
return
len > 0;
}
EXPORT_ELEMENT(PollNic)
kenge\example\click\tonic.hh :
#ifndef CLICK_TONIC_HH
#define CLICK_TONIC_HH
#include <click/element.hh>
#include <click/task.hh>
#include <click/notifier.hh>
#include <click/cxxprotect.h>
CLICK_CXX_PROTECT
#include <ether_lib/ether_lib.h>
CLICK_CXX_UNPROTECT
#include <click/cxxunprotect.h>
CLICK_DECLS
class ToNic : public Element { public:
ToNic();
~ToNic();
const
char *class_name() const {
return "ToNic"; }
const
char *processing() const {
return PUSH; }
int
configure(Vector<String> &, ErrorHandler *);
static
void static_initialize();
static
void static_cleanup() {};
int
initialize(ErrorHandler *);
void
add_handlers();
void
push(int, Packet *);
// bool
run_task();
protected:
Task
_task;
int
_devindex;
NotifierSignal _signal;
eth_t
*_nic;
};
CLICK_ENDDECLS
#endif
kenge\example\click\tonic.сс :
#include <click/config.h>
#include "tonic.hh"
#include <click/error.hh>
#include <click/confparse.hh>
#include <click/standard/scheduleinfo.hh>
#include <nics.hh>
ToNic::ToNic()
:
Element(1, 0), _task(this)
{
MOD_INC_USE_COUNT;
}
ToNic::~ToNic()
{
MOD_DEC_USE_COUNT;
}
void ToNic::static_initialize()
{
}
int
ToNic::configure(Vector<String>
&conf, ErrorHandler *errh)
{
if
(cp_va_parse(conf, this, errh,
cpUnsigned, "device index",
&_devindex,
cpEnd) < 0)
return
-1;
if((_nic
= nics_get_nic(_devindex)) == NULL)
{
printf("Error
opening %d nic\n", _devindex);
return
-1;
}
return 0;
}
int
ToNic::initialize(ErrorHandler *errh)
{
// if
(input_is_pull(0)) {
//
ScheduleInfo::initialize_task(this, &_task, errh);
//
_signal = Notifier::upstream_empty_signal(this, 0, &_task);
// }
return
0;
}
void
ToNic::push(int, Packet *p)
{
n_write(_nic, p->data(), p->length());
p->kill();
}
/*
bool
ToNic::run_task()
{
Packet
*p = input(0).pull();
if
(p)
{
n_write(_nic,
p->buffer_data(), p->buffer_length());
// p->kill();
}
else
if (!_signal)
return
false;
_task.fast_reschedule();
return
p != 0;
}
*/
void
ToNic::add_handlers()
{
if
(input_is_pull(0))
add_task_handlers(&_task);
}
EXPORT_ELEMENT(ToNic)
ФЕДЕРАЛЬНОЕ
АГЕНТСТВО ПО ОБРАЗОВАНИЮ
Государственное образовательное учреждение
высшего профессионального образования
ОБНИНСКИЙ ГОСУДАРСТВЕННЫЙ ТЕХНИЧЕСКИЙ УНИВЕРСИТЕТ
АТОМНОЙ ЭНЕРГЕТИКИ
(ИАТЭ)
Студент Степанов Андрей Вадимович
(фамилия, имя, отчество)
факультета «Кибернетики», специальности 220100, группы
ВТ-3-00
(факультет,
специальность, № группы)
ОТЗЫВ РУКОВОДИТЕЛЯ
на преддипломную практику
на
тему «Разработка и создание элементов ОС
на основе микроядра»
Степанов
А.В. проходил преддипломную практику в лаборатории телекоммуникационных
технологий ГУ «ВНИИГМИ-МЦД»
Степанов
А.В. проявил настойчивость, самостоятельность, творческий подход в процессе
формирования и достижения поставленной задачи. Его отношение к исследуемой теме
проявились в стилистике изложения материала, аргументации выводов и стремлении
использовать фактические данные. Главной чертой студента является
самостоятельность и убежденность в перспективности поставленной цели.
Для
достижения поставленной цели автор выполнил следующие работы:
Проанализировал
исследовательские работы в области создания и развития ОС на базе микроядра. На
основе анализа были сделаны выводы о перспективности данного направления в
ближайшем будущем.
Систематизированы
технологии создания компонентов ОС, что позволило автору определить наиболее
перспективные методы проектирования сервисов ОС.
В частности
были реализованы следующие компоненты ОС:
§
сервер, предоставляющий стандартный интерфейс
семафоров POSIX для синхронизации исполняемых потоков
§
сервер, представляющий унифицированный интерфейс
для работы с сетевым устройствами типа Ethernet
Так же, как
один из пунктов практической работы, был перенесен программный роутер Click в
рабочее окружение микроядра.
Были
выделены позитивные и негативные факторы, влияющие на быстродействие и
защищенность серверов ОС. Предложены наиболее рациональные способы реализации
компонентов ОС.
В процессе
работы автор продемонстрировал аналитические способности поиска, анализа, и
систематизации информации, умение аргументировать полученные результаты.
Считаю, что
работа Степанова А.В. Заслуживает оценки “отлично”.
|
Зав лабораторией ЛТТ ГУ ВНИИГМИ-МЦД, к.т.н. |
_______________ |
Шаймарданов В.М. |
|
Подпись Шаймарданов В.М. заверяю, ученый секретарь |
_______________ |
Кутько А.П. |