Как я писал ядро ОС. Часть 2.

Часть 1. Платформа
Часть 2. Архитектура и библиотека ядра
Часть 3. Ключевые подсистемы ядра

Архитектура L4Xpresso

Теперь настало время перейти собственно к разработке ядра. Разберемся сначала с его архитектурой, Архитектурной основой моей ОС выступило микроядро 2-го поколения L4. Оно привлекло интересными особенностями дизайна, и к тому же я с ним работал в ходе своей бакалаврского диплома, и написать свою реализацию L4 было бы интересно. За основу я взял спецификацию L4 Version X.2 Reference Manual

Архитектура микроядра L4Xpresso (кликабельно).

    Верхний уровень представляют из себя интерфейсы ядра: первый состоит из 13 системных вызовов, второй интерфейс - KIP (Kernel Information page) - служит для получения информации из ядра без собственно совершения системного вызова - соответсвующая область памяти всегда отображена в адресное пространство процесса.  KIP описан в файле kernel/include/l4/kip.h Средний уровень представлен основными подсистемами ядра: в первую очередь менеджерами виртуальной памяти и процессов (задач). На нижнем уровне же располагаются библиотека ядра, слой аппаратных абстракций, отладчик ядра и код инициализации и загрузки системы. Разберем нижний уровень микроядра L4Xpresso подробнее.   

Слой аппаратных абстракций (HAL) и библиотека ядра

    Слой аппаратных абстракций основан на библиотеке CMSIS, поставляемой вместе с средой LPCXpresso, и использует:

  • Функцию SystemInit(), предназначенную для инициализации контроллера перед запуском L4Xpresso - аналогичную роль играет BIOS в IBM PC

  • Интерфейс для работы с системным таймером SysTick

  • Драйвер UART3, используемый в качестве интерфейса для логов и отладки ядра

   Платформо-зависимый код расположен в директориях kernel/src/platform и kernel/src/lpc. Кроме CMSIS к нему относятся:

  • Микрооперации: атомарные операции и спин-блокировки (они зарезервированы, а также операции test-and-set для слова и бита.

  • Код для работы с прерываниями

  • Код для управлением Memory Protection Unit

    Хотя в поставке LPCXpresso две реализации стандартной библиотеки C: Redlib и Newlib, я не стал использовать ни одну из них, т.к. при разработке микроядра большая часть их функционала не потребовалась. С другой стороны, некоторые вещи все же пришлось написать. Во-первых это код для работы с очередями (FIFO) - оно необходимо для того, чтобы сделать вывод в отладочный UART буферизованным. Интерфейс fifo довольно простой:

struct fifo_t {
    uint8_t* q_data;    /*!< Указатель на первый байт очереди*/
    uint32_t q_top;         /*!< Голова очереди*/
    uint32_t q_end;         /*!< Конец очереди*/
    size_t   q_size;        /*!< Размер очереди*/
};

/*Инициализирует очередь, данные которой располаются по адресу addr (выделяется отдельно)*/
uint32_t fifo_init(struct fifo_t* queue, uint8_t* addr, size_t size);

/*Помещает и извлекает элемент из очереди*/
uint32_t fifo_push(struct fifo_t* queue, uint8_t el);
uint32_t fifo_pop(struct fifo_t* queue, uint8_t* el);

/*Возвращает состояние очереди переполнена - FIFO_OVERFLOW, пуста - FIFO_EMPTY или FIFO_OK*/
uint32_t fifo_state(struct fifo_t* queue);

/*Возвращает длину очереди*/
uint32_t fifo_length(struct fifo_t* queue);

    Во-вторых это код работы с битовыми массивами. В микроконтроллерах семейства Cortex-M3 есть специальный Bit-Band region, каждому биту которого соответствует отдельный адрес, что позволяет значительно упростить обход всех битов. Таким образом, модуль bitmap содержит две реализации битовых массивов, но с единым интерфейсом. Для обхода битового массива используется итератор типа bitmap_cursor_t.

#define DECLARE_BITMAP(name, size)

/*Инициализатор для курсора, где bitmap - битовый массив, а bit - номер бита*/
#define bitmap_cursor(bitmap, bit)

/*Возвращает текущую позицию курсора*/
#define bitmap_cursor_id(cursor)

/*Передвигает курсор на позицию вперед*/
#define bitmap_cursor_goto_next(cursor)

void bitmap_set_bit(bitmap_cursor_t cursor);
void bitmap_clear_bit(bitmap_cursor_t cursor);
int bitmap_get_bit(bitmap_cursor_t cursor);
int bitmap_test_and_set_bit(bitmap_cursor_t cursor);

    
    Основное применение битовых массивов bitmap - в модуле ktable, который отвечает за выделение ядром памяти. Упомянутые библиотеки Newlib и Redlib содержат функцию malloc, но к сожалению, ее использование не всегда оптимально. В частности, такой подход приемлим, когда мы обладаем имеем доступ ко всей оперативной памяти, однако для ядра без MMU это не так - мы должны уже при компоновке разделить память на выделенную ядру и пользовательским процессам. Внутри же выделенной ядру памяти лишь часть требует динамического распределения: это разного рода таблицы: процессов, адресных пространств и страниц. Для них была придумана концепция SLAB-ового аллокатора, аналогично ему (но несколько упрощенно) написан и модуль ktable.

struct ktable
{
    char* tname;
    bitmap_ptr_t bitmap;
    ptr_t data;
    size_t num;
    size_t size;
};
typedef struct ktable ktable_t;

/*Создает таблицу размером в num_ элементов типа type, с именем name*/
#define DECLARE_KTABLE(type, name, num_)

/*Инициализирует таблицу kt*/
void  ktable_init(ktable_t* kt);

/*Возвращает 1 если элемент с номером i выделен, 0 - если нет и -1 в случае выхода за пределы таблицы*/
int ktable_is_allocated(ktable_t* kt, int i);

/*Выделяет элемент таблицы по номеру или первый свободный*/
void* ktable_alloc_id(ktable_t* kt, int i);
void* ktable_alloc(ktable_t* kt);

/*Освобождает элемент*/
void  ktable_free(ktable_t* kt, void* element);

/*Возвращает номер элемента по его адресу*/
uint32_t ktable_getid(ktable_t* kt, void* element);

/*Обходит последовательно все выделенные элементы таблицы*/
#define for_each_in_ktable(el, idx, kt)

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

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

  • Фатальные ошибки пользовательского потока (например обращение к недопустимому адресу). Такие ситуации должны завершаться перезагрузкой потока. В данный момент не реализовано.

  • Фатальные ошибки ядра (так называемая паника). Она связана с ситуациями, выходящими за рамки нормального поведения ядра, например передача NULL-указателя там, где его не должно быть. Такая ситуация может также возникать на этапе разработки и контролироваться т.н. утверждениями (assertions), например:

fpage_t* fpage = (fpage_t*) ktable_alloc(&fpage_table);
assert(fpage != NULL);

    В случае если свободные слоты в fpage_table закончились, ktable_alloc вернет NULL, сработает утверждение и произойдет паника системы. Код обработчиков ошибок находится в файле kernel/src/error.c

Отладчик ядра   

    И наконец, отладчик ядра. Хотя в отличие от традиционных систем, в которых отладка внешними средствами затруднена и требуется полная реализация отладчика внутри ядра (kmdb в Solaris, kgdb в Linux), в случае L4Xpresso это можно выполнить средствами среды LPCXpresso. Однако такие вещи, как например состояние управляющих структур ядра лучше реализовывать отдельно. Для этого в L4Xpresso и предусмотрен отладчик. Он срабатывает на передаваемый по UART3 (этот же интерфейс используется и для отладочной печати символ и печатает в удобном виде текущее состояние системы, например список текущих потоков, как показано на скриншоте.

Пример отладочной печати и работы отладчика KDB (кликабельно)

    Основной код отладчика находится в файле kernel/src/kdb.c, а внешние обработчики в соответствующих исходниках. Само меню вызывается по знаку вопроса.

     Исходники проекта располагаются на github: https://github.com/myaut/l4xpresso

К списку статей

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

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs