컴퓨터공부/Embedded
[강좌] uC/OS-II EP7209(ARM7) 포팅
Author: 김 효준
Source: HiTEL Digital Sig.
Date: 2004.9.29
µC/OS-II EP7209(ARM7) 포팅
(1) 강좌 소개
잘 아시겠지만 uC/OS-II 는 소스가 공개되어 있는 유명한 RTOS 입니다. 이번 강좌에서는 uC/OS-II를 EP7209 보드에 포팅하는 방법에 대한 내용입니다. 강좌를 이해하시기 위해서는 uC/OS-II에 대한 기본 지식과, EP7209에 대한 지식을 필요로 합니다.
제가 EP7209를 택한 이유는, 얼마전에 이 보드를 구입했기 때문인데요, 이 CPU는 ARM720 Core의 CPU입니다. 따라서... 강좌 내용을 잘 이해하신다면 다른 ARM7 CPU에 포팅할 때에도 적용하실 수 있을 것이라 생각합니다.
해당 보드는 현재 www.nanowit.com에서 약 10만원 정도에 판매되고 있습니다. 저는 그 회사와는 별 상관이 없는 사람이므로 보드에 대한 문의는 해당 사이트에서 하시길 바랍니다.
강좌는 그리 길지 않을 것이라 생각합니다. 제목 그대로 '포팅'만을 다룰 것이기 때문입니다. 차후에 기회가 닿는다면 uC/OS-II 전반에 관한 강좌도 했으면 합니다만, 현재로선 그저 생각일 따름입니다.
다음은 강좌 계획입니다.
- 강좌소개
- 포팅을 위한 환경
- Stack 모양 만들기
- OSTaskStkInit 함수
- IRQ 핸들러와 OSTickISR 함수
- OSStartHighRdy 함수
- OSCtxSw함수
- OSIntCtxSw함수
- 마무리
계획대로 잘 될런지는 모르겠지만... 아무쪼록 많은 성원 부탁드립니다.
(2) 포팅을 위한 환경
포팅을 위해 제가 사용한 환경을 위주로 설명하려고 합니다. 역시 EP7209보드를 기준으로 설명하겠습니다만... 꼭 해당 보드가 있어야 한다는 의미는 아닙니다.
사실 강좌의 핵심 부분은 ARM7이나 StrongARM계열에서의 uC/OS Context Switcher 구현으로, 해당 부분을 잘 이해 하신다면, 다른 CPU에도 충분히 테스트해 보실 수 있으리라 믿습니다.
참... 제가 EP7209에 uC/OS-II를 포팅을 마쳐서 www.nanowit.com에 올려 두었습니다. 강좌가 끝날 무렵에 디동 자료실에도 올리겠습니다.
자... 그럼 본격적으로 uC/OS-II 포팅을 위한 환경을 알아보죠.
- 컴파일러
EP7209 CPU는 정확하게는 ARM720T코어를 사용합니다. 720은 일반 ARM7에 MMU까지 포함한 코어입니다. 동작 방식은 StrongARM의 MMU와도 같습니다. 저는 pSOS 2.5에 포함되어 있던 ARM Software Development 2.51을 사용했는데요... 해당 Tool은 공개용 툴은 아니구요, ARM 사이트에서 판매를 하고 있습니다. 1년 쯤 전에 해당 사이트에 Evaluation CD를 신청해서 받은 기억이 있습니다. 지금도 될려나 모르겠네요...
혹은 Linux에 크로스 컴파일러를 설치해서 사용하기도 하는데요, 제가 Linux를 잘 몰라서... 그냥 SDT를 사용했습니다.
아무튼... 당연히 ARM7 C 및 ASM 컴파일러가 있어야 겠지요.
- Board
계속 언급했듯이 NANOWIT의 EP7209 보드를 사용했습니다. 해당 보드의 특징으로는, 1Mega Byte 의 Flash 메모리와 약 37.5K 의 내부 SRAM을 사용할 수 있습니다. 사용할 수 있는 RAM이 적다는 것이 이 보드의 단점 중의 하나입니다. 하지만 uC/OS-II를 올리기 위해서라면, 그 정도의 메모리면 충분합니다. 참 그리고 Serial 포트를 사용한 Flash Write 기능을 제공하므로, 별도의 장비가 없이 개발환경을 구축할 수 있다는 장점이 있습니다.
uC/OS를 올리기 위해서 기본적으로 필요한 기능은 Serial과 Timer정도면 됩니다.
- Kernel Source
필수적으로 uC/OS-II 커널 소스가 있어야 합니다. 해당 소스는 labrosse의 책에 포함되어 있는 소스로, 책 저자의 재산이므로 책과 함께 구하셔야 합니다.
해당 소스에는 CPU에 독립적인 Kernel 소스와 8086을 위한 포팅 코드 및 예제들이 포함되어 있습니다. 이밖에 www.ucos-ii.com에 가시면 다른 CPU에 대한 포팅 소스들을 다운받을 수 있습니다. 해당 사이트에는 ARM7계열의 다른 CPU에 대한 포팅 코드들도 있습니다만, 제 경우엔 공부도 할 겸해서, 관련 소스를 참조하지 않고 포팅을 했습니다. 따라서 제가 사용한 방법외에도 다른 방법이 있을 수도 있습니다. 관심 있으신 분들은 ARM7을 사용한 다른 코드도 비교해 보시길 바랍니다.
- MicroC/OS-II 책 (labrosse)
포팅에 대한 강좌를 읽으시는 분이라면, 당연히 uC/OS-II에 대한 다른 내용들을 이미 알고 있으리라 생각합니다. 만약 그렇지 않다면, 먼저 위에 언급한 책을 구해서 읽어 보시길 바랍니다. uC/OS-II에 관해서는 처음부터 끝까지를 모두 담고 있는 책입니다. 물론 포팅에 관한 내용도 책의 8장과 9장에 걸쳐 자세히 설명하고 있습니다.
제 강좌를 이해하시기 위해서는 우선 책의 3장에서 7장까지의 내용을 어느정도 이해하고 있어야 합니다. 그리고 8장과 9장을 한번쯤은 읽어 보시는게 강좌의 이해를 도울것이라 믿습니다.
제가 포팅을 하기 위해서 참조한 모든 부분은 해당 책의 내용이었습니다. 즉, 누구든 책을 통해서 포팅을 위한 모든 정보를 얻을 수 있을 것입니다. 물론 특정 CPU에 대한 부분은 스스로 추가하셔야 하는 부분이고, 여기서는 바로 ARM7을 대상으로 했던 그런 저의 작업들을 설명하려 합니다.
- ARM7 Data sheet와 EP7209 Data sheet
전에 ARM7 강좌를 연재할 때 얘기한 적이 있었지만, ARM7은 CPU의 핵심 코어에 대한 부분이고, 특정 CPU를 사용하기 위해서는 해당 칩의 Data Sheet를 따로 봐야 한다고 했었는데요... 그런 이유로 EP7209 Data Sheet를 참조해야 합니다. 타이머 및 IRQ, Serial 등의 장치들을 사용하기 위한 정보를 이 문서로 부터 얻을 수 있습니다. 혹시 ARM7에 아직 익숙하지 않다면, 해당 Core부터 공부하셔야 합니다. uC/OS 포팅의 핵심 부분은 어셈블리로 이루어져 있기 때문입니다.
이상 다섯가지 정도를 들었습니다. 오늘 하고자 했던 얘기는 다 한 셈인데요... 몇 가지 사족을 붙이고 마치려 합니다.
OS를 포팅하는 작업은 어떻게 생각하면 무척 단순한 작업일 수도 있습니다. OS자체를 설계하는 것이 아니므로 커널에 대해서 많은 것을 알지 못해도 상관이 없을 수도 있구요... 하지만 누군가는 꼭 해야 하는 일이고, 아무나 쉽게 할 수 있는일도 아니라는 것이 중요합니다.
점차 사용자 요구 수준이 높아감에 따라 OS를 채택하는 것이 추세가 되어가고 있는 요즘, OS 포팅 자체도 하나의 큰 사업일 수 있습니다.
특정 보드에 WinCE나 Embedded Linux, pSOS, VxWorks 등을 올려주는 전문 업체도 있다고 하더군요.
어떻든... uC/OS-II는 그렇게 뛰어난 OS는 아니지만, 소스를 볼 수 있다는 큰 장점을 가지고 있습니다. Linux도 소스가 공개되기는 하지만, 사실 너무 복잡해서 공부의 목적으로는 적당하지 않은 것 같습니다.
uC/OS를 공부한다는 것은, 물론 그것 자체로서 할 수 있는 일은 많지 않겠지만 아주 좋은 경험을 하는 것이라 생각합니다. 포팅도 마찬가지구요.
그럼 오늘 강좌는 이만 마치겠습니다.
(3) Stack 모양 만들기
제목이 좀 이상하다고 생각 하셨을런지 모르겠습니다. 하지만 이 부분이 uC/OS포팅에 있어 가장 기초가 되는 부분입니다. RTOS의 가장 중요한 특징으로 Multi-Tasking 환경을 지원하는 기능을 들 수 있습니다. 흔히 Time Sharing 방식이라고도 말하는데요, 좀 더 자세히 말하자면, CPU를 시분할 하여 여러개의 프로그램(혹은 타스크)을 동시에 돌리는 방법입니다. OS 수업시간에 귀에 못이 박히도록 들었던 얘기가 아닐까 싶네요.
그러면... 프로그램이 진행되던 중간에, 갑자기 다른 프로그램을 진행 시킬 수 있는 환경으로 바뀔 수 가 있는 걸까요? 이 과정을 Context Switching이라는 용어로 표현하는데요... 이 과정을 이해하는게 중요합니다. 이 과정을 이해하기 위해 우리가 익히 알고 있는 인터럽트의 처리과정을 생각 해보도록 하죠. 인터럽트는 임의의 순간에 발생할 수 있죠. 인터럽트가 발생하면 CPU는 특정 번지로 점프를 하게 됩니다. 해당 번지에는 인터럽트 핸들러 코드가 들어 있어야 하구요... 인트럽트 핸들러에서 가장 먼저 하는 일이 뭐였냐면, 인터럽트 핸들러 내부에서 사용하게될 레지스터들을 어딘가에 보관해 두는 일입니다. 그리고 핸들러 처리가 끝나면 저장해 두었던 레지스터들의 내용을 꺼내어 원상 복구를 시키죠. 그리고 인터럽트 이전의 위치로 돌아가게 됩니다.
이 과정을 살피면, 짧은 순간이나마 일종의 멀티 타스킹 비슷한 일을 했다고도 볼 수 있을 것 같네요. 8086 CPU를 기준으로 인터럽트에의 동작을 잠깐 정리해 보도록 하죠.
- 인터럽트 발생
- 복귀번지를 스택에 저장하고 인터럽트 핸들러로 분기
- 핸들러 내부에서 사용하게될 레지스터들을 스택에 저장
- 실제 인터럽트 처리(레지스터들을 사용- 값이 변함)
- 저장되었던 레지스터들을 스택에서 복구
- 스택에 저장된 인터럽트가 걸리기 전의 복귀번지로 복귀
정확하지는 않을 수도 있겠지만, 대충 위와 같을 것입니다. 위의 과정을 이해 하시는게 중요합니다. 그리고 이 과정에서 스택이 하는 역할을 되세겨 보십시요.
자, 그러면 다시 Multi-Tasking 문제로 돌아와 보겠습니다. 멀티 타스킹을 위해서는 어떤 순간에 CPU의 동작 상태를 완전히 저장할 수 있어야 합니다. 이게 무슨 얘기냐면... 예를 들어 칼로 사과를 깎는 경우를 생각해 보죠. 칼 하나로 사과 10개를 깎는 다고 할 때, 첫번째 방법은 하나씩 깎는 경우입니다. 칼을 CPU로 보고 사과를 프로그램(타스크)으로 생각하면 사과 하나를 끝까지 깎는 것이므로 싱글 Tasking 에 해당하겠군요. 멀티 타스킹은 사과를 1초씩 번갈아 깎는 것입니다. 1번 사과를 1초동안 깎다가... 2번 사과를 다시 깎고, 다시 3번, 4번.. 이런식으로 말이죠. 문제는 10초가 지나서 다시 1번 사과를 깎아야 할 시점이 왔을때 입니다. 칼로 사과를 어디까지 깎았는지 기억해 두지 않는다면...(물론 사람은 눈으로 보고 확인할 수 있지만... CPU는 사람만큼 영리하지 않으니까요) 처음부터 다시 깎거나, 뭐 그래야 겠죠. 즉, 어떤 작업을 하다가 다른 작업으로 전환하기 위해서는 작업 중이던 내용을 그대로 어딘가에 보관할 필요가 있고, 나중에 다시 그대로 가져올 수 있다면 다시 작업을 진행하는데 문제가 없겠죠.
위의 인터럽트 처리 과정에서 3번에 해당하는 것이 바로 작업중이던 내용을 스택에 저장하는 것입니다. 즉, 작업중인 내용들은 CPU의 레지스터에 해당하고, 저장되는 공간은 스택이 되는 것이죠.
Multi-Tasking 과정에서 스택은 사실 보다 큰 의미를 지닙니다. 무슨 얘기냐면, 스택은 사용중이던 레지스터를 보관하는 공간의 의미를 지님과 동시에, 보관되어야 할 중요한 작업 내용 자체의 의미도 지닙니다. 이건 또 무슨 얘길까요?
자... 그럼 앞서의 예를 조금 확장해서 10개의 사과와 10개의 쟁반을 상상해 보도록 하죠. 사과를 깎아서 쟁반에 올려 놓기도 하고, 사과 껍데기를 쟁반에 버리기도 하고... 암튼 쟁반 역시 각 사과마다 따로 따로 있는 것이 편할 것입니다. 손에 칼을 들고 1번 쟁반을 가져다가 쟁반위에 놓여있는 사과를 들어서 깎고, 또 껍데기는 쟁반위에 버리고... 반쯤 깎다가 이쁘게 한조각 잘라서 쟁반에 올려놓기도 하고... 여기선 쟁반이 바로 스택에 해당합니다. CPU는 동작을 하며 계속 스택을 사용하게 됩니다. C의 내부 변수를 위한 공간으로도 사용하고, 어떤 루틴을 호출했을 경우 복귀번지를 넣는 목적으로도 사용합니다.
Single-Tasking 의 경우는 바로 이 쟁반이 하나이고 가끔 인터럽트가 걸려, 옆에서 배좀 깎아달라고 하면 사과를 쟁반위에 올려 놓고 배를 깎아 준 다음 다시 쟁반 위에서 사과를 가져다가 깎게 되겠죠.
Multi-Tasking과정에서... 위에서 언급했듯이 여러개의 스택을 사용한다면 어떨까요? 즉, 각 Task마다 자신의 Stack공간을 두고 어떤 Task를 실행하기 위해서는 해당 스택을 가져다가 그 안에서 실행해야 할 프로그램 포인터도 꺼내고, 그리고 전에 실행하다가 저장 해 두었을 작업상태인 레지스터들도 꺼내어 원래 위치로 보내고... 그리고 실행하면... 간단하겠죠?
즉, 인터럽트와 Multi-Tasking과정의 가장 큰 차이는, 각 Task마다 자신의 Stack을 따로 가진다는 것입니다. 그리고 스케쥴링이라는 것은, 다음에 칼로 어떤 사과를 깎아야 할런지를 결정하는 것이고, Context Switching 이라는 것은 사과가 결정되면 지금까지 깎고 있던 사과를 쟁반위에 내려놓고, 작업 중이던 내용들도 같이 쟁반에 보관한 다음, 깎아야 할 새로운 쟁반을 가져다가 그안에서 작업내용과 사과를 꺼내어 칼이 다른 사과를 깎을 수 있도록 해 주는 과정입니다.
제가 들은 비유가 좀 맘에 안드실런지 모르겠는데요... 어떻든 이제 좀더 학술적인 용어로 현실화 시켜 보겠습니다.
RTOS에서 일반적으로 말하는 Multi-Tasking의 대상을 Task라고 합니다. Task는 Process나 Thread와도 비슷한 개념인데요... 어떻든 동시에 실행될 수 있는 어떤 함수(?) 혹은 CPU흐름 정도로 생각해도 좋을 것입니다.
RTOS 커널에서는 Task를 관리하기 위해, 각 Task마다 관련 정보들을 저장할 수 있는 구조체 자료구조를 사용하는데요... 이 구조체를 TCB라고 부릅니다. 풀어서 표시하면 Task Control Block 의 약자입니다. 위에서 들었던 쟁반정도로 생각할 수도 있겠구요. TCB 는 하나의 구조체로 그 안에는 위에서 말했던 Task가 사용하게 될 Stack 공간을 가리키는 포인터 필드가 있습니다. 물론 Task 스케쥴링에 사용하는 Task의 Priority와 같은 정보나 기타의 것들도 들어가죠.
여기까지 Multi-Tasking을 위해 Stack이 얼마나 중요한지와, Context Switching이 무엇을 의미하는지, 그리고 각 Task 마다 Stack을 따로 두는 이유 등을 설명했습니다. 잘 이해가 안 되신다면... 아무래도 OS 에 대한 지식을 좀더 갖추어야 할 것 같군요. 한 학기 내내 가르치는 내용을 몇 줄의 글로 설명한다는 것 자체가 무리이니까요. 하지만 이해가 안되도 어쩔 수는 없습니다. 애초에 이 강좌가 포팅에 대한 것이지 OS에 대한 것은 아니니까요. 다시 본론으로 돌아와서, uC/OS 의 대부분은 CPU에 상관없는 C 코드로 되어 있습니다. 하지만 Context Switcher부분은 각 시스템에 따라 Stack운영 방식이 다르고, 인터럽트 관련 처리도 해야하며, 또 고속의 성능이 요구되므로 어셈블리어로 씌여 졌으며, 그런 이유로 포팅의 주된 작업이 바로 이 부분을 구현하는 작업이 됩니다.
Linux나 pSOS등의 다른 OS의 경우에 비하면 uC/OS에서의 포팅은 상당히 어려운 편이라고 하겠습니다. 적어도 이런 중요한 코드는 각 CPU 코어별로 제공이 되니까요.
아뭏든... CPU마다 레지스터의 개수도 다르고, 인터럽트 동작 방식도 다르며, 스택을 사용하는 용도도 조금씩 차이가 납니다.
하지만 앞서 얘기했듯이, 우리는 Task의 Context Switching 기능을 구현하기 위해, 어떤 모양으로 레지스터들을 스택에 저장할지를 약속해야 합니다. 그래야 그Task를 다시 실행시키기 위해 어떤 식으로 레지스터들을 꺼내 올 지를 알 수 있기 때문입니다.
* ARM7에서의 Context
Context라는 용어는 바로 작업 상태를 의미합니다. 스택을 포함한 CPU 레지스터들이 그것입니다. 그러면 ARM의 Context는 무엇이 될까요?
ARM은 r0-r12까지를 범용 레지스터로 사용하고, r13을 스택포인터(sp), r14를 Link 레지스터로 사용하며 r15는 프로그램 카운터로 사용합니다.
그래서 기본적으로 r0-r12 는 항상 저장이 되어야 하구요, 나머지 레지스터들은 적절한 고려가 필요합니다. 자... 먼저 제가 그린 Stack의 그림을 보여드리고 나서 설명을 계속 하도록 하겠습니다. 참고로 8086의 Stack모양에 대한 그림은 226Page에 나와있습니다.
----------------------------------------
Stack (High Memory)
----------------------------------------
15 r15(pc)
14 r14(lr)
13 r12
12 r11
11 r10
10 r9
9 r8
8 r7
7 r6
6 r5
5 r4
4 r3
3 r2
2 r1
1 r0
0 CPSR <- pTCB->OSTCBStkPtr
----------------------------------------
Stack (Low Memory)
----------------------------------------
위의 그림을 보면 이해가 되나요? 어떤 Task A 가 있다고 한다면 해당 Task 의 정보를 담고 있는 TCB가 메모리상에 존재할 것입니다. 그 TCB의 포인터를 pTCB라고 할때 pTCB->OSTCBStkPtr에는 해당 Task의 스택 포인터가 담겨있게 됩니다. 그리고 그 스택 안에는 위의 그림과 같은 순서로 CPU의 레지스터들이 저장되도록 하려고 합니다. 이건 순전히 제가 정한 약속입니다. 만약 여러분들이 위의 배치가 맘에 안드신다면 바꾸셔도 상관 없습니다.
CPSR은 잘 아시겠지만 ARM7에서 Flag 레지스터에 해당하는 것입니다.
그런데 자세히 살피니... r13이 없군요. r13은 ARM7에서 스택 포인터로 사용이 된다고 말씀 드렸죠? 즉, 어떤 Task를 실행 시킬 때 가장 먼저 하는 일이, 위에서 언급한 pTCB->OSTCBStkPtr 값을 r13에 넣는 것입니다. 즉, r13은 TCB내에 저장할 수 있는 공간이 따로 있는 것이죠.
그렇게 넣고 나서, r13을 사용해서 스택에서 하나 하나씩 꺼내면서 원래의 레지스터로 넣어주면 됩니다.
한가지 재미있는 것은... r15의 경우입니다. r15는 프로그램 카운터이므로, 해당 값이 바뀌면, 마치 JUMP 와 같은 효과를 나타내게 됩니다. 즉, r15에는 Task가 중단된 지점의 위치를 넣어주면 되겠군요. 그러면 자연스럽게 중단되었던 위치로 다시 돌아가게 되는 것입니다.
자... 앞에 사설이 길었지만... 제가 하고 싶었던 얘기는 바로 위의 그림 하나입니다. 위의 스택 모양을 기초로 나머지 부분들을 완성해 나갈 것입니다. 되도록이면 이 그림을 크게 그려서 모니터 옆에라도 붙여 놓으시길 바랍니다. 수시로 확인하고 참조하게 될테니까요...
그럼 오늘 강좌는 이만 마치도록 하겠습니다.
(제가 쓴 글을 다시 읽어보니... Linux나 pSOS 포팅에 비해 uC/OS 포팅이 더 어렵다는 식으로 쓴거 같네요... 그건 그냥 단편적인 하나의 측면에 대한 얘기구요... 규모 면에서나 다른 부분에 있어서... 상용 OS나 Linux에 비할 바가 못되죠. 그저 핵심 코드를 직접 짜 줘야 한다는 의미였으므로... 이해해 주시길 바랍니다.)
(4) OSTaskStkInit 함수
오늘 강좌는 uC/OS 포팅 부분 중에서 C 소스 코드로 되어 있는 부분에 대한 내용입니다. 제 경우엔 책에 포함되어 있는 8086 포팅 코드를 시작점으로 했는데요, lx86l 이라는 디렉토리에 8086용 포팅 코드가 들어있습니다.
그 중 Os_cpu_c.c 라는 소스 파일이 바로 오늘 강좌의 대상입니다.
해당 파일을 열어보면... 기대와는 달리 달랑 OSTaskStkInit 함수 하나만 들어있음을 알게 되실 겁니다. 물론 이밖에 몇몇 Hook 함수들이 있는데, 함수 틀만 만 들어져 있고, 실제 내용은 비어 있으므로 포팅과는 관련이 없습니다.
결론적으로 C 소스에서는 OSTaskStkInit함수만 만들어주면 됩니다.
자.. 그럼 본격적으로 OSTaskStkInit 함수에 대해서 알아보도록 하죠.
*. OSTaskStkInit 함수의 역할
이름을 풀어보면 Task의 스택을 초기화한다는 의미를 지니고 있습니다. 스택을 초기화 한다는게 어떤 의미를 지니고 있을까요? 지난 강좌에서 들었던 비유를 다시 한번 써먹기로 하죠. 지난번 강좌에서 Task에는 TCB가 존재하고, 그 TCB에는 Task가 사용하게될 Stack 을 가리키는 포인터를 가지고 있다고 말씀 드렸습니다. 비유로 바꾸어 얘기하면, 사과를 깎는 작업 자체가 Task가 되고, 그 작업을 위한 작업 공간이 쟁반이 되며, 이를 TCB 혹은 Stack으로 생각할 수 있겠군요. OSTaskStkInit함수는 이 Task를 처음 생성할 때와 관련이 있는 함수입니다.
uC/OS를 들여다 보면, Task를 생성하기 위해 OSTaskCreate 라는 함수를 사용합니다. RTOS에서 Task는 실행중에 임의로 생성하거나 삭제할 수 있습니다. 흔히 사용하는 Visual C 나 Java에서 Thread를 생성하거나 삭제할 수 있는 것과 같습니다. 이 때 사용하는 시스템 콜 서비스가 OSTaskCreate라는 함수인 것입니다. 해당 함수의 소스는 OS_Task.c 소스안에 포함되어 있습니다. 한번 들여다보는 것도 좋겠죠.
타스크를 생성하기 위해 OSTaskCreate을 호출할 때 넘겨주는 인수를 살펴보도록 하죠.
INT8U OSTaskCreate (void (*task)(void *pd), -> 1
void *pdata, -> 2
OS_STK *ptos, -> 3
INT8U prio) -> 4
인수를 살피면, 우선 첫번째 인수는 Task 로서, 첫째 인수는 Task의 시작번지를 의미하며, 두번째 인수는 Task 시작 시에 건내어지게 될 인수를 의미합니다. 세번째 인수가 바로 Task가 사용하게 될 스택 공간의 포인터이고, 마지막인수가 Task의 우선순위입니다.
즉, Task를 생성할때 단순히 비어있는 어떤 공간의 주소를 넘겨줌으로서, 해당 타스크가 해당 공간을 그 Task의 스택으로 사용하도록 하고 있는 것입니다.
문제는... 단순히 비어있다는 사실입니다. 당연하겠죠... 처음 Task를 만들기 위해 임의의 비어있는 공간(혹은 배열)의 주소를 넘겨줬을 뿐이니, 해당 내용은 비어 있거나 의미 없는 값들이 들어 있을 것입니다.
즉, 아무것도 안들어 있는 쟁반을 하나 넘겨준다는 의미입니다. 근데 이게 왜 문제냐구요? 지난 강좌에서 언급했지만, Context Switching과정을 다시한번 짧게 언급해 보죠.
"Context Switching 이라는 것은 사과가 결정되면 지금까지 깎고 있던 사과를 쟁반위에 내려놓고, 작업 중이던 내용들도 같이 쟁반에 보관한 다음, 깎아야 할 새로운 쟁반을 가져다가 그 안에서 작업내용과 사과를 꺼내어 칼이 다른 사과를 깎을 수 있도록 해 주는 과정입니다."
라고 되어 있군요. 즉, Task를 만든다는 것은 쟁반위에 사과를 올려놓고 다른 쟁반들과 동등한 상태로 만들어 두는 것을 의미합니다. 즉, 언제라도 스케쥴러에 의해 선택이 된다면 즉시 실행될 수 있는 대기 상태로 만들어 져야 하는 것이지요. 쟁반들을 넣어두는 캐비넷 쯤으로 생각해도 좋겠군요. 그 케비넷 안에는, 아까 작업을 하다 중단시켜 놓은 다른 쟁반들이 들어 있구요... Task를 만듦으로서 새로운 사과를 하나 쟁반위에 올려서 그 안에 넣어 두는 것이 Task의 생성이라고 생각해도 좋겠습니다.
그러면 쟁반을 초기화 한다는것은? 즉, 스택을 초기화한다는 것은 무슨 의미를 지니고 있을까요? 계속 힌트를 말했습니다만... 캐비넷에서 하나의 쟁반을 꺼내어 깎는 과정은 항상 동일합니다. 즉, 위에 써 놓은 것처럼... 우선 현재 작업하던 작업 내용을 Stack에 저장해 놓고 작업 중이던 TASK는 Ready Q 로 넣은 다음, 새롭게 실행될 Task의 TCB를 가져다가 그 안에 저장되어 있으리라고 예상되는 작업 내용을 복구하여 CPU를 실행하는 것입니다.
하지만 처음 생성된 Task의 경우엔 Stack에 '작업하던 내용'이 저장되어 있을리가 없겠죠? 물론 중단된 위치도 저장되어 있을리가 없구요....
그래서 OSTaskStkInit 함수를 통해 적당한 값으로 비어있는 Stack 을 채워주는 것입니다. 따라서 Task 생성과정에서 이 함수가 호출 되게 됩니다.
* OSTaskStkInit 의 실체
자.. 역할을 이해하셨다면, 이제 어떻게 Stack을 초기화 시켜야 하는지의 문제로 넘어가 보겠습니다.
여기서 지난 강좌에 사용한 Stack의 모양을 사용해야 합니다. 어제 정했던 스택의 모양을 바로 Ready Q에 들어있는(혹은 대기중인 Task의) 스택모양의 '표준'으로 정했기 때문입니다.
----------------------------------------
Stack (High Memory)
----------------------------------------
15 r15(pc)
14 r14(lr)
13 r12
12 r11
11 r10
10 r9
9 r8
8 r7
7 r6
6 r5
5 r4
4 r3
3 r2
2 r1
1 r0
0 CPSR <- pTCB->OSTCBStkPtr
----------------------------------------
Stack (Low Memory)
----------------------------------------
자... 위의 모양을 만들어 주면 되겠군요. 그럼 구체적으로 OSTaskStkInit의 소스 코드를 살펴보도록 하겠습니다.
01: void * OSTaskStkInit(void (*task)(void *pd),void *pdata,
02: void *ptos,INT16U opt)
03: {
04: INT32U *stk;
05: opt = opt;
06: stk = (INT32U *)ptos;
07: *(--stk) = (INT32U)task; // r15
08: *(--stk) = (INT32U)0; // r14
09: *(--stk) = (INT32U)0; // r12
10: *(--stk) = (INT32U)0; // r11
11: *(--stk) = (INT32U)0; // r10
12: *(--stk) = (INT32U)0; // r9
13: *(--stk) = (INT32U)0; // r8
14: *(--stk) = (INT32U)0; // r7
15: *(--stk) = (INT32U)0; // r6
16: *(--stk) = (INT32U)0; // r5
17: *(--stk) = (INT32U)0; // r4
18: *(--stk) = (INT32U)0; // r3
19: *(--stk) = (INT32U)0; // r2
20: *(--stk) = (INT32U)0; // r1
21: *(--stk) = (INT32U)pdata; // r0
22: *(--stk) = (INT32U)0; // CPSR
23: return ((void *)stk);
24: }
단지 24줄에 불과한 소스를 설명하려고, 그렇게 많은 사설을 늘어놨다는 사실이 놀랍지 않나요?
먼저 OSTaskStkInit 함수의 인수를 살펴보겠습니다. 첫째 인수는 Task의 시작번지입니다. 즉, Task가 CPU에 의해 실행될 때 Program Counter 값으로 지정될 주소를 의미합니다. 두번째 인수는 Task의 인수이구요... 세번째가 Stack의 위치, 마지막은 현재 사용하지 않는 옵션입니다.
우선 4번 줄에서 8086에서는 INT16U *stk; 으로 정의 되어 있던 것을 INI32U로 바꿨습니다. 이유는 ARM7에서 레지스터의 크기가 32비트 이기 때문이구요, 5번줄은 그냥 무시하면 되구요, 6번에서 스택으로 지정받은 공간의 번지를 stk라는 변수로 받는 부분입니다.
다음 7번 부터 22번까지 스택을 채우게 되는 것이죠. 우선 *(--stk)라고 쓴 이유는, 제가 Full & Decrement Stack을 사용했기 때문입니다.
그리고 r15부터 r0, CPSR까지 순서대로 스택에 넣습니다. 여기서 인수를 어떻게 사용했는지 살펴보십시요.
맨 먼저 r15의 자리에, 인수로 건내받은 Task의 시작번지를 넣어 줍니다. 그럼으로서 r15가 복구되며 Task가 시작될 수 있도록 하는 것입니다. r14부터 r1까지는 그냥 쭉 넣어 주는데 r13이 없는 이유는 지난 강좌에서 말씀 드렸습니다. sp로 사용되기 때문이죠... r0의 위치를 보시면 pdata 변수를 넣어준 것을 볼 수 있습니다. 이 부분은 8086의 소스와 차이가 있는데요, C 에서 인수를 어떻게 넘겨 주는가의 문제와 관련이 있습니다.
8086에서는 함수 호출시에 Stack을 통해 인수를 넘깁니다. 하지만 ARM 컴파일러는 적은 개수의 인수에 대해서는 레지스터를 사용해 인수를 넘깁니다. 즉, 첫째 인수는 r0를 통해 넘기게 됩니다.
그런 이유로 pdata를 r0의 위치에 넣어주게 됩니다.
이 부분은 C 가 어떤식으로 어셈으로 변환되는지에 대한 지식이 어느정도 있어야 이해를 하실 수 있을 것 같은데요... 그 얘기는 건너 뛰기로 하지요.
마지막으로 완성된 스택의 현재 주소를 리턴하면 됩니다.
자. 여기까지 입니다. 간단한 코드를 길게 설명하느라... 몹시 힘들군요. 앞으로도 비슷한 과정을 겪게 될 것 같습니다. 대부분의 코드가 그다지 복잡하지는 않습니다. 그저... 그 배경을 설명해야 한다는 강박 관념 때문에...
어떻든... 오늘 강좌는 이쯤에서 마칠까 합니다. 그러기 전에 몇가지 개인적인 얘기를 하겠습니다.
음... 강좌의 저작권에 대한 문제인데요... 지난번 ARM7강좌를 연재할 적에, 겪었던 일입니다만, 제 강좌의 내용을 제 동의도 없이 모 유명 잡지의 기사에 일부 포함하고, 또 대부분의 내용을 '이달의 디스켓'의 내용으로 배포 했었던 적이 있었습니다. 강좌를 그대로 옮긴 것도 아니고, 토시만 바꿔서... 이해하시나요?
사실 강좌를 쓰며 뭔가 댓가를 바라고 하는 것은 아닙니다. 그저, 이렇게 함으로써, 제 이름을 얻기 위해서겠지요. 그런데, 그걸 가져다가... 악용하는 것은, 참 당하는 입장에서는 불쾌한 일입니다.
지금 이런 얘기를 꺼내는 것은... 다시는 그런 일이 없었으면 하는 바램에서 입니다. 제 글이 필요하신 분들이 메일로 허락을 구하면, 항상 허락을 해 드렸었습니다. 적어도 양해 정도는 구하는게 예의라고 생각합니다.
자... 분위기가 별로 안좋아진 것 같은데요, 그저 상식을 넘는 행동만 안 해 주셨으면 합니다. 그럼 다음 강좌에 뵙겠습니다.
(5) IRQ 핸들러와 OSTickISR 함수
오늘 강좌에서는 가장 많은 머리 회전을 필요로 했던 IRQ핸들러 부분에 대한 내용입니다.
uC/OS를 동작시키기 위해서는 타이머 인터럽트를 사용해야 합니다. RTOS에서 가장 중요하게 생각되는 시간 개념을 바로 타이머 인터럽트를 사용하여 구현하기 때문입니다.
오늘은 되도록 요점만 설명하도록 노력해 보겠습니다. 자칫 하다가는, RTOS의 원론 적인 부분분터 전부 언급해야 될지도 모르니까요.
어떻든 RTOS 가 시간을 중요시 하는 만큼, 약 1/100 초 마다 커널의 특정 함수를 호출해 줄 필요가 있습니다. 이 함수가 OSTimeTick 이라는 함수이구요, 이를 위한 타임 인터럽트 핸들러가 OSTickISR 함수입니다.
즉, 1/100 초마다 타이머 인터럽트가 발생하도록 칩의 타이머를 초기화 시키고, 인터럽트가 발생하면 OSTimeTick 함수를 한번 호출해 주는 것입니다.
사실은 말처럼 그렇게 쉽지만은 않은데요, 여러가지 문제들이 있기 때문입니다.
문제는 Timer 인터럽트 종료 시점에 Context Switching 이 일어날 수 있다는 점입니다. 무슨 얘긴고 하니... 우선순이가 높은 TaskA가 있어서 실행되다가, 스스로 100 Tick 동안 Block 되는 시스템 서비스를 호출하여, Block 모드로 바뀌었다면, 그리고 그 다음으로 우선순위가 높은 TaskB가 Ready 상태에 존재했다면 그 시점에서 TaskB가 실행될 것입니다. TaskB가 쭉 실행되다가 100 Tick이 지나면, TaskA가 깨어나야 할 시점이 되겠지요... 그러면 그 순간에 TaskB에서 TaskA로 Context Switching 이 일어나야 합니다.
쓰고나니 전문용어를 많이 사용한 것 같네요. 우선 Tick은 RTOS에서 말하는 시간개념으로, 보통 1회의 Timer 인터럽트를 의미합니다. 따라서 1/100초로 인터럽트 주기를 맞췄다면, 100 Tick이 지나면 1초가 지난 것이 되겠죠... 나머지 용어는 대충 감으로 때려 맞추시길...
암튼... 핵심은, 100 Tick이 지났을 때 Context Switching이 일어나야 한다는 것입니다. 앞서도 말했지만 Tick이란 타이머 인터럽트가 걸리는 것을 의미하므로, 다시 말하자면 100회의 타이머 인터럽트 이후에는, 다른 프로그램이 실행되어야 한다는 의미입니다.
그게 무슨 문제냐구요?
또다시 Stack 얘기를 해야겠군요. 쟁반 A를 가지고 사과 A를 깎던 도중에 인터럽트가 걸렸습니다. 그럼 어떻게 하죠? 당연히 현재 작업을 쟁반(Stack)에 저장하고 인터럽트 핸들러로 분기를 해야 겠죠. 인터럽트 핸들러에서 별 일이 없었다면 갔다 와서 저장된 내용을 꺼내어서 다시 실행을 하면 됩니다. 그러나 만약, 위에서처럼... Context Switching이 일어나야 한다면, 즉... 지금까지 하던 작업을 그만 두고 새로운 작업을 해야 한다면... 어떨까요?
계속 말했지만, Context Switching과정은 기존 것의 저장, 그리고 새로운 것의 복귀... 과정으로 간추려서 생각할 수 있습니다. Context Switching이 일어나려고 보았더니... 작업하던 내용은 인터럽트가 걸리는 시점에서 이미 저장을 했겠군요. 그러면, 새로운 것을 가져다가 실행만 시키면 Context Switching이 되는 것입니다.
개념상으로는 아무런 문제가 없고, 아주 자연스럽게 이루어 질 수 있을 것 같은데요... 그렇게 자연스럽게 이루어지도록 해줘야 하는것은 우리들의 몫입니다.
!!! 결론은 !!!
우리가 처음 정했던 Stack모양을... 인터럽트에도 적용 시켜 줘야 한다는 것입니다.
그래야 Context Switching이 일어났을 때 자연스럽게 진행이 될 수 있기 때문입니다.
자... 그 모양이란 것이 뭐였죠? Stack에 r15부터 r0까지, 그리고 CPSR까지를 쭉 넣어 주면 되는 것이었는데요...
문제는 ARM7이 여러개의 동작 모드를 갖고 있다는 것입니다. 잘 모르시는 분은 ARM7 Data Sheet 를 참조하십시요. EP7209 보드에서는 평상시에 6개의 동작 모드 중에서 Supervisor 모드로 동작이 되도록 하였습니다. - 일부러 그런 것은 아니고, 제가 보드 개발사로 부터 다운 받은 예제 프로그램이 그렇게 되어 있었습니다. 그러다가... IRQ가 걸리면, CPU 동작 모드가 바뀌어 버립니다. IRQ 모드로..
그렇게 되면, r13과 r14 두개의 레지스터가 IRQ 전용 레지스터로 바뀌게 되는 것이죠. r13은 Stack Pointer이므로, 인터럽트가 걸리는 순간... 핸들러 내부에서는 IRQ 전용 Stack을 사용하게 되는 것입니다. 우리의 목적은 뭐였죠? 인터럽트가 걸리면, 사용하던 레지스터들을 해당 Task의 Stack에 보관하는 것이었습니다.
그런데, IRQ모드로 들어오면서 기존에 사용하던 SVC 모드의 r13을 접근할 수 없게 되고, 따라서 레지스터들을 그 안에 넣을 수도 없게 되어 버립니다.
잘 이해가 안될 수도 있을 것 같습니다만... 8086의 인터럽트 핸들러 부분을 가져다 놓고, ARM 용으로 바꾼다고 생각해 보십시요. 쉽지 않음을 알게 되실 겁니다.
자... 그래서, 이제 실전 시간이 돌아왔습니다. 물론 코드는 ARM 어셈 코드입니다.
---------------------------------------------------------------------------
01: IRQHandler
02: stmfd sp!,{r0-r3}
03: mov r1,#REGISTER_BASE
04: ldr r2,=INTSR1
05: ldr r0,[r1,r2]
06: tst r0,#0x100
07: bne TimerIRQ ; Check Timer IRQ
08: ldmfd sp!,{r0-r3}
09: subs pc,lr,#4
10: TimerIRQ
11: ldr r2,=TC1EOI ; Timer 1 Interrupt Clear
12: str r0,[r1,r2]
13:
14: mov r2,sp ; copy IRQ's sp -> r2
15: add sp,sp,#16 ; recover IRQ's sp
16: sub r3,lr,#4 ; copy return address -> r3
17:
18: LDR r0,=IRQ_2
19: MOVS pc,r0
20: IRQ_2
21: stmfd sp!,{r3} ; push SVC's pc
22: stmfd sp!,{r4-r12,lr} ; push SVC's r14, r12-r4
23: mov r4,r2
24: ldmfd r4!,{r0-r3}
25: stmfd sp!,{r0-r3} ; push SVC's r3-r0
26: mrs r5,cpsr
27: stmfd sp!,{r5} ; push SVC's PSR
28: B OSTickISR ; Real Body...
---------------------------------------------------------------------------
코드를 한줄 한줄 따라가 보죠... 먼저 Timer IRQ가 걸리면, Vector Table에 의해 01번 부분으로 진입하게 됩니다.
이때, IRQ의 특성상...
- CPU 동작 모드는 IRQ 모드가 되고,
- r13과 r14는 IRQ 전용 레지스터로 바뀌며,
- IRQ이전의 CPSR은 SPSR로 복사가 되고, CPSR에는 IRQ금지 플래그가 설정됩니다.
- irq 전용 모드의 r14에는 '복귀번지+4' 값이 들어갑니다.
위의 특징은 ARM7의 Exception 의 특징입니다. 이해가 안되시면 꼭 확인하고 넘어가 주십시요.
2번에서 stmfd sp!,{r0-r3} 명령으로 r0에서 r3까지 4개의 레지스터를 스택에 보관합니다. 하지만 이때 사용되는 스택은? 예! 그렇습니다. IRQ 전용 스택입니다. 즉, SVC모드의 Task의 스택과는 상관이 없습니다.
다음 3줄 부터 6번줄 까지는, 현재 걸린 IRQ가 Timer IRQ인지를 체크하는 부분입니다. EP7209에 종속적인 부분입니다. 5번에서 r0에 IRQ 상태 레지스터 값을 읽어서, 6번에서 Timer IRQ인지 체크하고, 맞다면 10번으로 점프하게 됩니다.
8번,9번은 우리가 원하는 IRQ가 아닌 경우 그냥 Return 하는 코드입니다.
타이머 IRQ가 맞다면 10번으로 넘어가게 되는데요, 11번,12번은 타이머 인터럽트를 클리어 해 주는 부분입니다. 인터럽트 클리어는 아시겠지만, 해당 인터럽트가 정상적으로 처리되었음을 CPU에 알려 줌으로서, 다시 IRQ가 걸리지 않도록 해 주는 것입니다. 보통 인터럽트 핸들러 끝 부분에 두는게 일반적인 방법인데요, 왜 시작 부분에 넣었는지는... 곧 말씀드리겠습니다. 괜히 그런것은 아니구요...
14번 부터는 Stack 모양을 만들기 위한 트릭 부분입니다. 핸들러 시작 부분에서 r0부터 r3까지를 IRQ스택에 보관했었습니다. IRQ체크 등의 작업을 위해서 해당 레지스터들을 사용해야 했기 때문인데요, 즉, 14번 시점에서 보면 저장되어야 할 레지스터 중, r0-r3까지는 IRQ 스택에 들어 있는 상태입니다.
14번에서 mov r2,sp 명령을 통해 r2에 IRQ 스택 포인터를 넘겨줍니다. 이것은 잠시 후에 IRQ모드에서 SVC 모드로 전환한 다음, r0부터 r3까지를 꺼내기 위해서 입니다.
15번에서 add sp,sp,#16 명령을 실행하면, IRQ 스택 포인터를 초기상태로 만들어 주는 역할을 합니다. 이것은, IRQ 모드의 sp를 더이상 사용하지 않을 것이기 때문인데요, 지금 이해가 잘 안되면, 나중에 전체적으로 코드의 역할에 대해 고민해 보도록 하세요.
16번을 살펴보죠. sub r3,lr,#4 명령을 수행할때... lr, 즉 r14_irq에는 무엇이 들어있을까요? '복귀번지+4' 값이 들어 있다고 말씀드렸었습니다. 그러면... 명령 후에 r3에는? 예... 인터럽트 종료후에 돌아갈 번지 값이 들어있게 됩니다.
왜 이 값을 r3로 옮기냐면... 곧 IRQ모드를 SVC모드로 전환할 것이기 때문입니다. 모드가 바뀌면 IRQ모드의 r14는 더이상 참조할 수 없기 때문이죠... 같은 이유로 r13_irq(sp)역시 r2로 옮겼었습니다.
18: LDR r0,=IRQ_2
19: MOVS pc,r0
20: IRQ_2
위의 세줄은 IRQ 모드에서 SVC 모드로 빠져 나오는 코드입니다. MOVS를 썼지요.. pc에 IRQ_2를 넣었으므로, 어딘가로 점프를 하는 것은 아니고, 단지 IRQ 모드에서 SVC 모드로 전환되는 것입니다. 즉, IRQ종료 방법을 여기서 사용했습니다.
자... 그럼 이때 어떤 일이 일어날지 생각해 보죠.
- SPSR에 들어있던 IRQ이전의 PSR이 CPSR로 복구됩니다.
- r13과 r14는 SVC모드용 r13과 r14로 바뀌게 됩니다.
자.. (1)에서 이전 CPSR값이 복구되면서 IRQ금지도 풀리게 됩니다. 여기서... 만약 위에서 IRQ 클리어를 해 주지 않았었다면... 다시 IRQ가 발생하여 무한 루프에 빠지게 됩니다. 그래서 윗 부분에서 IRQ 클리어를 넣어 준 것이지요...
(2)번 과정을 보면, r13의 경우... add sp,sp,#16 명령으로 맨 처음 했던 stmfd 명령에 의한 스택 포인터 변화를 원상태로 해 놓은 것을 볼 수 있습니다.
21번 줄로 넘어가지요..
stmfd sp!,{r3} 명령으로 스택에 r3를 저장하는군요... 이번의 Stack은 어떤 Stack일까요??? 방금 IRQ모드에서 SVC모드로 빠져나왔으므로... Task의 쟁반이군요!!! 그럼... 우리가 그렸던 스택 모양에 따르면, 가장 위에 들어가야 하는 것은 r15였는데요... 코드에서는 r3를 넣습니다. 왜냐구요? 위에서 sub r3,lr,#4명령을 통해 인터럽트 복귀 번지를 r3에 넣어 두었었으니까요. (점점 머리가 복잡해 지고 계실것 같군요... 인내를 가지고...!!!)
그럼 이번에는 나머지 레지스터들을 저장할 차례입니다. 22번 줄이지요.
stmfd sp!,{r4-r12,lr} 명령입니다. ARM의 멀티 레지스터 전송 기능입니다. 막강하죠? 보시다시피... r14와 r4부터 r12까지를 하나의 명령으로 스택에 넣습니다. 들어가는 순서는 항상 높은 레지스터 번호가 높은 주소에 들어가게 되므로, 우리들의 Stack모양과 일치합니다.
이제는 ? r0부터 r3까지를 저장해야 합니다. 그런데... 해당 값은 아까 irq 스택에 넣었었습니다... 기억하시나요? IRQ 스택은... 이미 IRQ 모드가 아니므로 접근할 수 없을텐데... 하고 걱정이 되시지는 않나요?
그걸 위해서 r2에 IRQ모드의 sp를 넣어 두었더랬습니다. 그 포인터를 이용해서 r0부터 r3까지를 IRQ스택에서 꺼내는 부분이 바로 23,24번 줄입니다.
그리고 25번에서는 Task의 스택에 r0-r3을 저장하는 부분이구요... 자... 이제 하나 남았습니다. 그게 뭐였죠? CPSR입니다.
CPSR은 바로 Stack으로 저장할 수 없으므로, r5로 옮겨서 Stack으로 넣기로 하지요. 바로 26, 27번 줄입니다.
예!!! 여기까지가 IRQ를 위한 Stack 모양 만들기였습니다.
너무 복잡하죠? 제가 썼지만 쓰면서도 복잡했습니다... 물론 처음 코딩할 때도 복잡했구요... 이해가 안되시더라도... 차근차근 따져 보시길 바랍니다. 무슨 일이든... 노력이 필요할 때도 있는 것이니까요. 제가 전부 알아듣기 쉽게 설명 드릴 수 있으면 좋겠지만, 어쩌겠습니까... 제 능력의 한계가...T.T
이쯤되면... 아마도 실제 포팅을 시도해보지 않으신 분들은, 거의 포기 단계에 이르렀을 것 같군요. 그래두 별 수 없습니다. 오직 끈기!!! 기분전환을 위해 8086 포팅 코드나, 책의 관련 부분을 읽어 보시는 것도 좋겠구요...
28번에서 OSTickISR 함수로 넘어가게 되는데요... 여기서 하는 일은 비교적 간단합니다. 책에 나온 Pseudocode를 보면요.. OSTaskISR에서 해야 할 일은...
- 레지스터들을 저장한다.
- OSIntEnter() 함수를 호출하거나, OSIntNesting변수를 증가시킨다.
- OSTimeTick() 함수를 호출한다.
- OSIntExit() 함수를 호출한다.
- 레지스터들을 복구한다.
- 인터럽트 종료...
이중에서 (1)번과정은 IRQ 핸들러 시작 부분에서 처리했습니다. 그래서... (2)번 이후의 부분이 OSTickISR 함수 안에 구현되면 됩니다.
자... 이제 마지막으로 해당 소스를 살펴보고 끝내도록 하겠습니다.
---------------------------------------------------------------------------
01: OSTickISR
02: LDR r0,=OSIntNesting ; Notify uC/OS-II of ISR
03: LDRB r1,[r0]
04: ADD r1,r1,#1
05: STRB r1,[r0]
06: &n
'컴퓨터공부/Embedded'의 다른글
- 현재글[강좌] uC/OS-II EP7209(ARM7) 포팅