Данная серия статей посвящена описанию работы RTOS– написанной на основе TNKernel. Исходники вы можете скачать по этой ссылке.
Если вы хотите написать свою RTOS или разобраться в TNKernel читайте так-же:
- Задачи
- Работа планировщика
- Последовательность действий при инициализации
- Assembler, переключение контекста
- Чередование задач при помощи отправки задачи в конец очереди для данного приоритета
- Отправление задачи в режим ожидания на определенное время
- Простой семафор
Assembler, переключение контекста
Рассмотрим ассемблерный файл myrtos_cortex_m3.s с самого начала.
Сначала указываются переменные, которые используются как в .c (C код) файлах, так и в .s (assembeler код) файлах.
;-- External references
IMPORT curr_run_task
IMPORT next_task_to_run
IMPORT myrtos_system_state
Таким образом мы получили адреса:
- curr_run_task — структура описания задачи, которая запущена в данный момент.
- next_task_to_run — структура описания задачи, которая должна запуститься следующей.
- myrtos_system_state — состояние RTOS.
После, указываются функции, которые используются как в .c (C код) файлах, так и в .s (assembeler код) файлах. т.е. они вызываются в .с, а их код лежит в .s.
;-- Public functions declared in this file
EXPORT switch_context_exit
EXPORT switch_context
EXPORT cpu_save_sr
EXPORT cpu_restore_sr
EXPORT start_exe
EXPORT chk_irq_disabled
EXPORT PendSV_Handler
EXPORT int_exit
EXPORT ffs_asm
EXPORT arm_disable_interrupts
EXPORT arm_enable_interrupts
Указываем компилятору константы (аналог define в С):
;-- Interrupt Control State Register Address
ICSR_ADDR EQU 0xE000ED04
;-- pendSV bit in the Interrupt Control State Register
PENDSVSET EQU 0x10000000
;-- System Handlers 12-15 Priority Register Address
PR_12_15_ADDR EQU 0xE000ED20
;-- PRI_14 (PendSV) priority in the System Handlers 12-15 Priority Register Address
;-- PendSV priority is minimal (0xFF)
PENDS_VPRIORITY EQU 0x00FF0000
Где:
- ICSR_ADDR — адрес регистра управления и состояния прерываний
- PENDSVSET — значения включения PENDSV прерывания. (При установление PENDSV вызывается прерывание. Кроме того есть SVCall, он отличается от PENDSV, тем что SVСall не может быть отложен. PENDSV может быть отложен другим прерыванием, но это в данной реализации не имеет значение)
- PR_12_15_ADDR — адрес настройки приоритета прерываний PENDSV и SYSTICK
- PENDS_VPRIORITY — уровень приоритета PENDSV минимальный
Функция start_exe вызывается в функции start_myrtos — начало планирования.
; Interrups not yet enabled
;-----
start_exe
ldr r1, =PR_12_15_ADDR ;-- Load the System 12-15 Priority Register (записываем в r1 константу PR_12_15_ADDR, - адрес настройки приоритета прерываний)
ldr r0, [r1] ;-- из памяти по адресу в регистре r1 загружаем в регистр r0
orr r0, r0, #PENDS_VPRIORITY ;-- set PRI_14 (PendSV) to 0xFF - minimal (логическое or c числом PENDS_VPRIORITY - получение приоритетов)
str r0, [r1] ;-- сохраняем значение из r0 в память по адресу r1 т.е. в регистр настройки приоритета
ldr r1, =myrtos_system_state ;-- Indicate that system has started (переменную адрес tn_system_state в r1)
mov r0, #1 ;-- 1 -> TN_SYS_STATE_RUNNING (в r0 помещаем 1)
strb r0, [r1] ;-- сохраняем из r0 в память по адресу r1 т.е. меняем tn_system_state
;-----
ldr r1, =curr_run_task ; =tn_next_task_to_run (адрес переменной в r1)
ldr r2, [r1] ;-- значение переменной в r2 (в данном случае переменная тоже содержит адрес)
ldr r0, [r2] ;-- in r0 - new task SP (значение по адресу в r0) - в r0 верхушка стека для данной задачи
ldmia r0!, {r4-r11} ;-- пишем в регистры r4-r11, значения из памяти по адресу содержащемуся в r0, r0+4, r0+8 и т.д.
msr PSP, r0 ;-- пишем из r0 в PSP (PSP/MSP - два стека cortex m3) r0 вершина стека = PSP
orr lr, lr, #0x04 ;-- Force to new process PSP (LR - регистр связи, логическое or с 4) LR - адрес возрата
;switch_context_exit
ldr r1, =ICSR_ADDR ;-- Trigger PendSV exception (загружаем адрес PENDSV прерывания)
ldr r0, =PENDSVSET ;-- значения для этого регистра-адреса
str r0, [r1] ;-- включаем pendsv - копируя 10000000 в регистр PENDSVSET
cpsie I ;-- Enable core interrupts (вкл. прерываний - уход pendsv прерывание PendSV_Handler)
;--- Never reach this
b .
; bx lr
Здесь происходит установка контекста задачи curr_run_task — для этого из структуры curr_run_task, берется адрес верхушки стека для этой задачи (первый элемент структуры) и из стека переписываются регистры R4 — R11, которые сохранены в стеке. И включение прерывания PENDSV. (подробнее см. код и пробывать отладчиком).
Обработчик прерывания PENDSV тоже написан на ассемблере:
PendSV_Handler
cpsid I ; Disable core interrupts
ldr r3, =curr_run_task ; in R3 - =tn_curr_run_task
ldr r1, [r3] ; in R1 - tn_curr_run_task
ldr r2, =next_task_to_run
ldr r2, [r2] ; in R2 - tn_next_task_to_run
cmp r1, r2 ;-- сравнение адресов стеков tn_next_task_to_run и tn_curr_run_task
beq exit_context_switch ;-- переход если равны ; если таже задача следующая нет смыла менять контекст
;-----
;mrs r0, control
mrs r0, psp ; in PSP - process(task) stack pointer (пишем в r0 значение стека - адрес вершины стека)
stmdb r0!, {r4-r11} ; по адресу в r0, r0+4, ... помещаем регистры r4 - r11
str r0, [r1] ; save own SP in TCB (сохраняем r0 по адресу в r1 - адрес стека данной задачи tn_curr_run_task)
str r2, [r3] ; in r3 - =tn_curr_run_task
ldr r0, [r2] ; in r0 - new task SP (в r0 next_task стек)
ldmia r0!, {r4-r11} ;-- загружаем из r0 d r4 - r11 значения для сл. задачи
msr psp, r0 ;-- меняем стек
orr lr, lr, #0x04 ; Force to new process PSP (LR - регистр связи, логическое or с 4) LR - адрес возрата
В обработчике происходит переключение контекста задачи — с curr_run_task на next_task_to_run, это происходит только в том случае, если curr_run_task и next_task_to_run не одно и тоже. Для переключения контекста регистры r4-r11 задачи curr_run_task сохраняются в его стеке, вершина стека сохраняется в структуре описывающее данную задачу, после регистры r4-r11 загружаются значениями из next_task_to_run и происходит смена стека на стек next_task_to_run. При измененном контексте задачи возврат из прерывания происходит на новую задачу. (подробнее см. код и пробывать отладчиком).
Далее рассмотрим switch_context. Она вызывается из .c кода когда мы хотим сменить контекст задачи (сменить задачу). Например она вызывается в timer_task_func, когда планировщик выбрал next_task_to_run и готов к ее включению или в SysTick_Handler при необходимости переключить контекст на планировщик — timer_task_func. (В данной RTOS timer_task_func вызывается как планировщик и всегда вызывается при переключении задач — см. код и др. статьи из серии).
switch_context
;--- Just Invoke PendSV exception
ldr r1, =ICSR_ADDR
ldr r0, =PENDSVSET
str r0, [r1]
;cpsie I ; Enable core interrupts
bx lr
Как видно данная функция просто разрешает прерывание PENDSV и как только прерывания будут глобально разрешены, то обработчик PENDSV вызовется, он описывался выше.
Функции глобального разрешения и запрещения прерываний:
arm_disable_interrupts
cpsid I
bx lr
;-----
arm_enable_interrupts
cpsie I
bx lr
END