티스토리 뷰
지금까지 진행된 내용을 정리하면, 프로그램이 시작되어 여러가지 초기화를 진행한 후, idle task를 생성하여 실행 시키고, idle task에서는 main task를 생성, 실행 시켰다. idle task나 main task 둘 다 while(1); 로 되어 있으므로 두 타스크 모두 무한 루프 상태로 타스크가 실행된다. 하지만, 실제로는 main task가 우선 순위가 높으므로 main task만 CPU를 독점하고 idle task는 단 한 순간도 CPU를 점유하지 못하여 기아 상태에 빠지게 된다.
RTOS를 사용하는 이유가 CPU를 효율적으로 이용하기 위해서이므로 어떤 이벤트가 발생하였을 때 그 이벤트를 처리하기 위한 타스크가 동작하고 다른 이벤트를 수행하기 위하여 CPU 점유권을 넘겨 주는 구조를 가져야 한다.
여기에서 말하는 이벤트란 대부분 인터럽트가 발생할 때를 의미한다.
이번 글에서는 megaOS에서 인터럽트를 처리하는 방법에 대해서 설명하도록 하겠다.
인터럽트란 말 그대로 언제 일어날 지 모르는 상황에서 발생하게 되므로 인터럽트를 처리하는 코드는 최대한 간결하게 작성하여 다른 인터럽트의 발생을 처리하는데 지장을 받지 않게 하여야 한다.
megaOS에서는 이러한 이유로 인터럽트를 두 단계로 나누어서 처리하도록 되어 있다.
인터럽트가 발생하였을 때, 이를 즉시 처리하기 위한 ISR 코드가 있고, ISR에서는 어떤 인터럽트가 발생했는지만 기록해 둔 다음 인터럽트 코드를 끝내고, 기록해 둔 인터럽트를 처리하기 위한 DSR 코드가 있다.
AVR에서의 인터럽트 관련 내용은 입문 과정과 심화 과정 앞 부분에서 설명하였으므로 인터럽트 자체에 대한 설명은 생략하고, megaOS에서의 인터럽트 처리 순서에 대해서만 설명하도록 하겠다.
위의 그림이 megaOS에서 인터럽트를 처리하는 절차를 설명하는 것이다.
1. 어떤 타스크가 실행 중인 상태에서
2. 하드웨어에서 인터럽트가 발생하였다.
3. 인터럽트가 발생하면 해당 벡터 주소에서 프로그램이 시작된다.
4. Context들을 저장한 후
5. ISR 을 실행한다.
6. ISR이 끝난 후 DSR이 필요할 경우 이를 처리하기 위하여 interupt_end()를 실행한다.
7. DSR이 필요할 경우 post_dsr()을 실행하여 나중에 DSR을 실행할 수 있도록 해 둔다.
8. 인터럽트 이벤트를 처리하기 위하여 필요한 경우 스케줄러 스위칭이 발생한다.
9. DSR을 실행 한다.
10. 인터럽트 때문에 실행을 잠시 멈추었던 타스크의 context들을 restore하여
11. 이전에 멈추었던 위치에서부터 타스크 프로그램이 계속 진행된다.
위의 그림에서 5번 단계가 끝난 후 6번 단계로 진입하게 되는 시점이 ISR이 끝나는 시점이다. 6번에서 10번 단계까지가 DSR이 되는 것이다.
대략적인 인터럽트 처리 순서를 설명하였으므로 실제 코드를 만들어 보도록 하겠다.
startup.S 파일에 ISR 글을 참고하여 벡터 코드를 만든다.
벡터 코드에서는 공통의 인터럽트 처리를 실행하기 위하여 process_irq() 함수를 사용하는데, process_irq()를 다음과 같이 작성한다.
18~21은 인터럽트를 처리하는 도중에 스케줄링이 일어나지 않게 하기 위하여 os_sched_lock_count를 증가시키는 코드이다.
23~24는 인터럽트가 발생한 이후 인터럽트를 처리하는데 사용하게될 레지스터들을 스택 메모리에 저장한 다음 순간의 스택 포인터를 r18,r19에 임시로 저장하는 코드이다. 즉, 인터럽트가 발생하는 순간에 동작 중이던 타스크가 가지고 있는 스택메모리 영역이 되는 것이다.
25~28은 ISR을 수행할 때 필요한 스택 메모리를 제공하기 위하여 SRAM의 마지막 주소를 제공하는 코드이다. 즉, 인터럽트를 처리하는 동안 사용하게될 스택 영역을 타스크의 스택을 사용하지 않게 해주기 위해서 이런 방법을 사용하는 것이다.
29~30은 타스크의 스택 주소를 인터럽트가 사용하는 스택에 기록해 두는 코드이다.
32는 ISR 코드로 진입하기 위하여 isr_handler()를 부르고 있다.
34~35는 인터럽트 스택 영역에서 저장해 두었던 타스크의 스택 메모리 주소를 읽어 오는 코드이다.
36~37은 타스크 스택메모리를 CPU의 스택포인터 레지스터에 설정하는 코드이다. 이렇게 되면 다시 인터럽트가 발생할 때의 타스크가 가지고 있던 스택메모리를 사용하게 되는 것이다.
39는 DSR을 처리하기 위하여 interrupt_end() 함수를 부르는 것이다.
DSR이 끝나면 인터럽트로 인하여 멈추어졌던 타스크를 실행하기 위하여, 저장해 두었던 context들을 CPU로 복사 한 후 reti 명령어를 통하여 타스크를 계속 수행하도록 한다.
megaOS에서 인터럽트를 사용하기 위해서 구조체와 간단한 초기화 코드가 필요하다.
os.h 파일에 다음과 같은 내용을 추가한다.
os_init() 함수에 interrupt_init();를 추가한다.
kernel/os_interrupt.c 파일을 만들고 인터럽트 코드에서 사용하게 될 변수들을 선언한다.
interrupt_table[]은 ISR을 생성 시켰을 때 그 instance를 저장하기 위하여 필요하다.
dsr_list는 DSR이 필요한 인터럽트를 링크 시키기 위해서 사용한다.
intr_state와 disable_counter는 인터럽트를 disable하거나 enable할 때 사용된다.
interrupt_init()를 다음과 같이 만든다.
인터럽트 코드가 잘 동작되는지 확인하기 위하여 간단한 테스크 코드를 만들어 보도록 하겠다.
main_task()에 create_uart_isr();를 추가한다.
create_uart_isr()를 다음과 같이 작성한다.
os_interrupt_create()를 이용하여 UART 인터럽트를 생성하고 그 결과를 os_interrupt_attach()에 전달하여 인터럽트를 테이블에 등록한다. os_interrupt_unmask()를 이용하여 UART 블럭에서 인터럽트를 발생시키도록 한다.
UART에서 RX 인터럽트가 발생하면, uart_isr()이 실행되고, 뒤 따라서 uart_dsr()이 실행되어야 한다.
uart_isr()은 다음과 같다.
UART_ISR을 터미널에 출력하고 수신한 UART 데이터를 uart_data변수에 저장해 놓는다.
uart_dsr()은 다음과 같다.
UART_DSR 메시지와 함께, uart_isr()에서 저장해 두었던 uart_data변수에 저장되어 있던 값을 출력하게 만들었다.
os_interrupt_create()은 다음과 같다.
인자로 들어온 값들을 인터럽트 instance에 저장한다.
os_interrupt_attach()는 다음과 같다.
생성된 인터럽트 instance를 interrupt_table[]에 등록한다.
os_interrupt_unmask()는 인자로 들어온 인터럽트 번호에 해당되는 인터럽트를 발생시키도록 허락하는 코드라고 보면 된다. 이 동작을 수행할때는 master interrupt를 disable 시켜서 인터럽트가 발생하지 않도록 하였다가 모든 동작이 완료된 다음 다시 enable 시키도록 한다.
master interrut를 제어하느 코드는 CPU마다 다르므로 arch에 별도로 정의하도록 하고 있다. arch.h 파일을 참고하기 바란다.
start.S 파일에 있는 벡터 코드에서 부르는 irq_handler()는 다음과 같다.
isr_handler()는 인자로 벡터 번호를 받도록 되어 있다. interrupt_table[]에서 벡터 번호에 해당되는 내용을 가지고 와서 등록된 인터럽트인지 아닌지 확인해서 등록되어 있는 경우 ISR callback함수를 호출한다. 이번글의 예에서는 uart_isr이 불리워지게 되는 것이다. 그리고 interrupt_table[]에서 가지고 온 인터럽트 instance를 리턴값으로 넣어 주도록 하였다.
이 리턴값은 곧이어 interrupt_end()의 입력 인자로 들어가게 된다. interrupt_end()는 다음과 같다.
isr_ret에 OS_ISR_CALL_DSR 비트 플래그가 설정되어 있으면 이 인터럽트를 dsr_list에 연결되도록 한다. 그리고 난 다음 os_sched_unlock()을 이용하여 DSR을 실행하도록 만든다.
post_dsr()은 다음과 같다.
발생한 인터럽의 dsr_link가 아직 dsr_list에 연결되어 있지 않으면 dsr_list에 연결 시킨다.
스케줄러에서 pending된 dsr을 처리하기 위한 코드를 수행해 주어야 한다.
unlock() 함수를 다음과 같이 수정한다.
interrupt_dsr_pending()과 interrupt_call_pending_dsr()는 다음과 같다.
19번 줄에서 DSR callback함수를 부르는것을 볼 수 있다.
프로젝트를 빌드하여 실행 시켜서 인터럽트가 제대로 동작되는지 확인해 본다.
키보드에서 'm','e','g','a'를 차례로 눌렀을 때의 결과가 위의 그림처럼 나오는것을 볼 수 있다. 키보드 한번 누를때마다 ISR과 DSR이 실행된 것을 볼 수 있고, uart_isr에서 처리하였던 수신 데이터를 uart_dsr에서 제대로 처리한것도 볼 수 있다.
이 글에서 설명되지 않은 코드들도 있는데, 그 부분들은 아래 첨부 파일을 보면 이해할 수 있을 것이다.
'심화' 카테고리의 다른 글
megaOS - 10. System Timer (0) | 2016.10.31 |
---|---|
megaOS - 9. Counter (0) | 2016.10.29 |
megaOS - 7. Context(Task) Switching (0) | 2016.10.27 |
megaOS - 6. Context load (0) | 2016.10.24 |
megaOS - 5. TCB와 Context (2) | 2016.10.22 |