Skip to content

Latest commit

 

History

History
292 lines (204 loc) · 13.6 KB

lab8.textile

File metadata and controls

292 lines (204 loc) · 13.6 KB

ЛАБОРАТОРНАЯ РАБОТА 8

Вектора прерываний. Основы работы с модулями на языках высокого уровня

Краткие теоретические сведения.

Обработка прерываний

У приведенных ранее примеров есть еще одна ошибка: первые 8 слов в памяти зарезервированы под вектора прерываний. Когда происходит прерывание, управление передаётся по одному из этих восьми адресов. Прерывания и адреса их векторов приведены в таблице:

Прерывание Адрес
reset (сброс) 0×00
undef (неопределённая инструкция) 0×04
swi (программное прерывание) 0×08
pabt (прерывание предварительной выборки) 0×0C
dabt (сброс данных) 0×10
зарезервировано (не используется) 0×14
IRQ 0×18
FIQ 0×1C

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


.section “vectors”
reset: b start
undef: b undef
swi: b swi
pabt: b pabt
dabt: b dabt
nop
irq: b irq
fiq: b fiq

Можно видеть, что вектор прерывания reset передает управление на адрес start. Все остальные вектора содержат переходы на свой собственный адрес, т. е. если произойдет какое-нибудь прерывание кроме reset, процессор окажется в бесконечном цикле. Такое прерывание можно будет опознать по значению r15 (Program Counter), посмотрев его через отладчик (в нашем случае через интерфейс монитора QEMU).

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


SECTIONS {
. = 0×00000000;
.text : {
* (vectors);
* (.text);

}

}

Обратите внимание, что секция vectors размещена до всего прочего кода: так мы гарантируем, что она располагается по адресу 0x0.

Запуск кода, написанного на языке высокого уровня

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

В качестве примера мы возьмём программу на С, вычисляющую сумму элементов массива, и напишем для нее загрузчик.


static int arr[] = { 1, 10, 4, 5, 6, 7 };
static int sum;
static const int n = sizeof(arr) / sizeof(arr0);
int main() {
int i;
for (i = 0; i < n; i++)
sum += arr[i];
}

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

Стек*

Язык C использует стек для хранения локальных переменных, передачи аргументов функций и хранения адресов возврата. Поэтому важно, чтобы стек был настроен правильно, перед передачей управления C-коду. В архитектуре ARM реализация стека полностью передаётся программному обеспечению.

Для гарантии совместимости кода, создаваемого различными компиляторами, компания ARM создала стандарт архитектурных вызовов процедур ARM (AAPCS). В соответствии с AAPCS, в качестве указателя стека должен использоваться регистр r13 (Stack Pointer), а сам стек должен расти в сторону убывания адресов.

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

Таким образом, в стартовом коде нужно установить в r13 самый большой адрес ОЗУ, чтобы стек мог «увеличиваться вниз», к меньшим адресам. Для платы Connex это можно сделать с помощью следующей инструкции:

LDR SP, =0xA4000000

Заметим, что мы обратились к регистру r13 по псевдониму SP.

Глобальные переменные

При компиляции программы на C, компилятор помещает инициализированные глобальные переменные в секцию .data. Так же, как и в ассемблере, секция .data должна быть скопирована из флэш-памяти в ОЗУ.

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

Данные, доступные только для чтения

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

Поскольку содержание секции .rodata не изменяется, ее можно размещать во flash-памяти, соответствующим образом модифицировав скрипт компоновщика.

Код запуска

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

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

SECTIONS {
. = 0×00000000;
.text : {
* (vectors);
* (.text);
}
.rodata : {
* (.rodata);
}
flash_sdata = .;

. = 0xA0000000;
ram_sdata = .;
.data : AT (flash_sdata) {
* (.data);
}
ram_edata = .;
data_size = ram_edata – ram_sdata;

sbss = .;
.bss : {
* (.bss);
}
ebss = .;
bss_size = ebss – sbss;
}

Загрузочный код будет состоять из следующих частей:

  1. Вектора прерываний
  2. Код для копирования .data из flash-памяти в ОЗУ
  3. Код для обнуления .bss
  4. код для установки указателя стека
  5. переход к функции main

.section “vectors”
reset: b start
undef: b undef
swi: b swi
pabt: b pabt
dabt: b dabt
nop
irq: b irq
fiq: b fiq

.text

start:
@@ Копирование данных в ОЗУ
ldr r0, =flash_sdata
ldr r1, =ram_sdata
ldr r2, =data_size

@@ Если data_size == 0
cmp r2, #0
beq init_bss
copy:
ldrb r4, [r0], #1
strb r4, [r1], #1
subs r2, r2, #1
bne copy

init_bss:
@@ Инициализация .bss
ldr r0, =sbss
ldr r1, =ebss
ldr r2, =bss_size

@@ Если bss_size == 0
cmp r2, #0
beq init_stack

mov r4, #0
zero:
strb r4, [r0], #1
subs r2, r2, #1
bne zero

init_stack:
@@ Инициализация указателя стека
ldr sp, =0xA4000000

bl main

stop: b stop

Скомпилировать текст программы на С, приведенной в самом начале, можно следующей командой:

$ arm-none-gnueabi-gcc -nostdlib -o csum.elf -T csum.lds csum.c startup.s

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

Дамп таблицы символов даст более полную картину того, как элементы были размещены в памяти.


$ arm-none-eabi-nm -n csum.elf

00000000 t reset ❶
00000004 A bss_size
00000004 t undef
00000008 t swi
0000000c t pabt
00000010 t dabt
00000018 A data_size
00000018 t irq
0000001c t fiq
00000020 T main ❷
00000090 t start
000000a0 t copy
000000b0 t init_bss
000000c4 t zero
000000d0 t init_stack
000000d8 t stop
000000f4 r n ❸
000000f8 A flash_sdata
a0000000 d arr ❹
a0000000 A ram_sdata
a0000018 A ram_edata
a0000018 A sbss
a0000018 b sum ❺
a000001c A ebss

Цифровыми метками в дампе были помечены следующие элементы:

❶ reset и прочие вектора прерываний, помещённые по адресу 0х0.

❷ Исполняемый код, размещённый сразу после восьми векторов прерываний (8 * 4 = 32 = 0×20).

❸ Данные только для чтения «n», размещённые во flash-памяти сразу за кодом.

❹ Инициализированные данные «arr», массив из шести целых чисел, помещённых в ОЗУ по адресу 0xA0 00 00 00.

❺ Неинициализированные данные «sum» помещённые сразу после шести целых чисел. (6 * 4 = 24 = 0×18)

Для исполнения конвертируем программу в формат .bin, выполним ее в QEMU и проверим значение переменной sum (т. е. ячейки памяти по адресу 0xA0 00 00 18).


$ arm-none-gnueabi-objcopy -O binary csum.elf csum.bin
$ dd if=csum.bin of=flash.bin bs=4096 conv=notrunc
$ qemu-system-arm -M connex -pflash flash.bin -nographic -serial /dev/null
(qemu) xp /6dw 0xa0000000
a0000000: 1 10 4 5
a0000010: 6 7
(qemu) xp /1dw 0xa0000018
a0000018: 33

Задание

Написать программу поиска подстроки в строке со следующим алгоритмом:

  • вывести в последовательную консоль QEMU приглашение и ввести с клавиатуры слово для поиска;
  • вывести еще одно приглашение и ввести с клавиатуры символьную строку для поиска в ней слова;
  • вывести информацию о том, найдено ли в строке искомое слово (поиск выполнять в подпрограмме на языке С).

Контрольные вопросы

  1. Каковы особенности использования таблицы векторов прерываний?
  2. Какие секции необходимы для корректной работы процедур, скомпилированных С-компилятором, и каковы особенности их использования?
  3. Каковы особенности стандарта архитектурных вызовов процедур ARM?