Мой студенческий проект в проедшем семестре был связан с разработкой под микроконтроллеры семейства Cortex M3 (архитектура ARMv7M). В отличие от x86 систем с общим адресным пространством и большой оперативной памятью (причем учитывая прогресс Grub'а в последнее время, достаточно просто сделать multi-boot совместимый образ, все остальное за нас сделает grub), в микроконтроллерах нужно распихивать данные между Flash и RAM явно посредством компоновщика. Учитывая что система не имеет виртуальной памяти (и относительной адресации), пришлось реализовать ld-скрипт, явно указывающий по каким адресам должен положить компоновщик образ ядра и пользовательские данные.
Итак, образ состоит из четырех основных сегментов: текст, данные, статические переменные (BSS) и стек. Для ядра у меня получился скрипт примерно следующего вида:
MEMORY
{
MFlash512 (rx) : ORIGIN = 0x0, LENGTH = 0x80000 /* 512k */
RamLoc32 (rwx) : ORIGIN = 0x10000000, LENGTH = 0x8000 /* 32k */
RamAHB32 (rwx) : ORIGIN = 0x2007c000, LENGTH = 0x8000 /* 32k */
CANAccFilterRAM (rwx) : ORIGIN = 0x40038000, LENGTH = 0x800 /* 2k */
}
ENTRY (__l4_start)
KERNEL_BASE = 0x00001000;
KERNEL_STACK_SIZE = 0x00000800;
SECTIONS {
.text 0x00000000:
{
KEEP(*(.isr_vector))
. = KERNEL_BASE;
kernel_text_start = .;
*(.text*)
*(.rodata*)
kernel_text_end = .;
} > MFlash512
.stack :
{
. += KERNEL_STACK_SIZE;
kernel_stack_addr = .;
} > RamLoc32
.data :
AT( ADDR(.text) + SIZEOF(.text) )
ALIGN (128)
{
kernel_data_start = .;
kip_start = .;
*(.kip*)
kip_end = .;
/*Only kernel data*/
*(.data*)
kernel_data_end = .;
} >RamLoc32
/* zero initialized data */
.bss (NOLOAD) :
{
kernel_bss_start = .;
*(.bss*)
*(.ktable*) /* Statically allocated kernel table */
*(COMMON)
kernel_bss_end = .;
} > RamLoc32
.data_AHB (NOLOAD) :
{
kernel_ahb_start = .;
*(.bitmap*)
kernel_ahb_end = .;
} > RamAHB32
}
Директива ENTRY указывает компоновщику точку входа. В моем случае это весьма условно, так как точка входа итак задается по нулевому смещению в векторе прерываний.
Точка в ld обозначает текущий счетчик расположения (location counter). Присваивание ему значения обозначает смещение относительно смещения самого сегмента (и таким образом, я например, резервирую место под стек). С другой стороны, можно присваивать его значение псевдо-переменным (как например kernel_data_start), чтобы затем получая их адрес, можно было точно знать, где расположен сегмент данных и, например, явно запрещать доступ пользовательского приложения к операционной системе. В коде это выглядит следующим образом:
extern uint32_t kernel_text_start;
Теперь при взятии адреса &kernel_data_start мы получим точный адрес начала сегмента данных ядра.
В конце объявления секции мы указываем, в какую из областей памяти размещать сегмент. Особый интерес здесь представляет сегмент данных, для которого начальные данные помещаются в Flash контроллера сразу за сегментом .text, а сами данные располагаются в RAM. Для этого и предусмотрена директива AT. С другой стороны, для BSS, данные инициализируются нулями и надо явно указать параметр NOLOAD (иначе в моем случае утилита программирования контроллера решала, что по адресу 512 Мб у нее расположены данные и сходила с ума, т.к. лицензии хватает только на прошивку 128k.
При старте системы нужно инициализировать BSS и data, что я собственно и делаю:
void init_zero_seg(uint32_t* dst, uint32_t* dst_end) {
while(dst != dst_end) {
*dst++ = 0;
}
}
void init_copy_seg(uint32_t* src, uint32_t* dst, uint32_t* dst_end) {
while(dst != dst_end) {
*dst++ = *src++;
}
}
void __l4_start() {
/*DATA (ROM) -> DATA (RAM)*/
init_copy_seg(&kernel_text_end, &kernel_data_start, &kernel_data_end);
/*Fill bss with zeroes*/
init_zero_seg(&kernel_bss_start, &kernel_bss_end);
init_zero_seg(&kernel_ahb_start, &kernel_ahb_end);
SystemInit();
main();
}
И наконец, маленькое замечание по поводу сегмента .data_AHB. В Cortex M3 есть специальный раздел RAM-памяти, к которой можно обращаться и как к обычной и побитово (т.н. Bit-bang region), и, разумеется, там удобно размещать разного рода битовые карты. Поэтому создаем отдельный сегмент, а в коде явно указываем секцию:
#define __BITMAP __attribute__ ((section(".bitmap")))
static __BITMAP char some_bitmap[16];
Ну вот и все, осталось скормить скрипт в свойства проекта и можно тестировать образ.