:::: MENU ::::

Assembler, переключение контекста

Данная серия статей посвящена описанию работы RTOS– написанной на основе TNKernel. Исходники вы можете скачать по этой ссылке.

Если вы хотите написать свою RTOS или разобраться в TNKernel читайте так-же:

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