티스토리 뷰
이번글에서는 바로 앞의 글에서 생성한 Idle task에 대한 context를 실제로 CPU에 load 하여 예상대로 동작되는지 확인해 보도록 하겠다.
먼저 task_init() 함수에 다음과 같은 코드를 추가한다.
arch_load_context() 함수 호출을 추가하였는데, arch_load_context()는 어셈블러 코드로 작성된다.
context.S 파일을 만들고 다음과 같은 코드를 추가한다.
task_init() 함수에서 arch_load_context() 함수를 호출할 때 인자 값으로, 생성한 idle_tcb에 대한 포인터 주소를 넘겨 주었다.
EABI 글을 참고하면 알겠지만, 함수 호출 할 때 들어가는 첫번째 인자는 r24, r25를 이용해서 전달하도록 되어 있다.
그리고 TCB 구조체의 첫번째 필드는 stack_ptr로써 생성된 타스크의 스택 주소값을 가지고 있다. 그러므로 idle_tcb가 가리키는 주소에서 값을 읽어 오면 곧바로 idle task의 스택 주소를 읽어 오게 된다.
5~10은 idle_tcb에 기록된 스택 주소를 읽어서 CPU의 스택 포인터 레지스터에 넣어 주는 동작을 수행한다.
12~31은 idle_tcb의 스택에서 초기 레지스터 값들을 읽어 해당 레지스터에 전달하는 동작을 수행한다. os_task_create() 에서 해당 레지스터의 초기값은 레지스터 번호와 동일한 값을 넣어 주었기 때문에 그 값이 그대로 들어갈 것이다.
33~34는 타스크 함수가 실행될 때 전달되는 입력 인자값이 된다. idle task의 경우 0x1234가 될것이다. 역시 인자 전달을 위해서는 r24,r25가 사용되는것을 알 수 있다.
35~36은 r18을 이용해서 SREG 레지스터의 초기값인 0x80을 넣어주는 동작을 수행한다. 즉, interrupt enable 이 되는 것이다.
38번줄에서 "ret" 명령어를 수행하도록 하였는데, 그렇다면 스택메모리에 context 초기화 할때 SREG 레지스터 다음에 있던 pc[] 값들은 왜 처리하지 않았을까?
AVR은 "ret" 명령어를 수행하면 현재의 스택 레지스터가 가리키는 주소 바로 다음 위치에 return address가 있다고 간주하고 그 주소값을 PC 레지스터로 설정하도록 되어 있다.
그러므로 idle_task() 함수가 실행 될것으로 예상하고 있는것이다.
참고로, AVR에서 사용하는 "push"와 "pop" 명령을 수행할 때 어떤 순서로 명령이 수행되는지 알아야 한다.
"push" 명령을 수행하면 현재 스택 포인터 레지스터가 가리키고 있는 주소에 CPU 레지스터 값을 넣은 후 스택 포인터값을 하나 감소 시킨다.
반대로 "pop" 명령을 수행하면 먼저 스택 포인터를 하나 증가 시킨다음 그 주소에서 값을 읽어 와서 CPU 레지스터에 복사하도록 되어 있다.
프로젝트를 빌드 한 후 바로 실행 시켜 결과를 확인할 수도 있겠지만, 그 전에 실제로 CPU 내부에서는 어떻게 처리되는지 한단계씩 디버깅 해보는것이 좋을것 같다.
다음과 같은 방법으로 AtmelStudio에서 제공하는 시뮬레이터를 이용해서 코드를 디버깅 해보도록 하겠다.
AtmelStudio의 프로젝트 속성창을 열어서 Tool 탭을 선택한 후 빨간 박스와 같이 Simulator를 선택한다.
위의 그림에 보이는 버튼을 누르거나 Alt+F5를 눌러서 Debuggin 모드로 들어간다.
디버깅이 시작되면 main() 함수의 시작지점에서 프로그램이 멈춘 상태가 될 것이다.
C소스코드에서 디버깅이 안되고 어셈블러 모드에서 디버깅을 하여야 하므로 어셈블러 화면으로 전환한다.
AtmelStudio의 메뉴에서 Debug > Windows > Disassembly를 선택하거나 단축키 Alt+8을 선택하면 디버거 화면이 어셈블리 창으로 바뀌게 된다.
Debug폴더에서 lss 파일을 열어서 task_init() 함수의 어셈블 코드를 찾고, arch_load_context() 함수를 호출하는 지점의 주소를 확인한다.
위의 경우 18번째 줄이 arch_load_context()를 호출하는 지점이다. 이 주소를 AtmelStduio 어셈블 창에서 찾아본다. 단지 주의 할 점은 어셈블러 창은 16비트 주소체계로 표시되므로 8비트 주소를 2로 나눈 주소를 찾아야 한다.
위의 예의 경우 0x2A6번지 이므로 어셈블 창에서는 0x153을 찾아야 한다.
위의 그림처럼 0x153에 arch_load_context()를 call 하는것을 볼 수 있다. arch_load_context()의 주소가 0x20이므로 그 주소로 점프하게 된다. 8비트 주소로 표현하면 0x40번지이다.
위의 그림에서 00000153이라고 되어 있는 앞부분(회색부분)을 마우스 왼쪽 버튼으로 클릭하면 빨간색 점이 찍히게 된다. 그 주소에 breakpoint가 설정되는 것을 의미한다.
breakpoint를 설정한 후 F5키를 누르거나 툴바의 초록색 삼각형 버튼을 누르면 arch_load_context()를 call 하는 부분까지 프로그램이 실행된다.
위의 그림처럼 빨간색 마크위에 노란색 화살표가 멈추어져 있는것을 볼 수 있다.
AtmelStudio메뉴에서 Debug > Windows > Process Status를 선택하면 다음과 같은 화면을 볼 수 있다.
위의 그림처럼 PC(Program Counter) 값이 0x153에서 멈춘것을 볼 수 있고, arch_load_context()에 들어가는 입력 인자 값이 0x01E2인것을 알 수 있다.
즉, idle task의 TCB 정보는 0x1E2에 있다는 뜻이다. 실제 메모리에 어떤 값들이 들어있는지 확인해 보도록 하겠다.
위의 그림에서 빨간색 박스가 idle task의 TCB의 처음 세개의 필드값이다.
stack_ptr은 0x0218, stack_base는 0x01F2, stack_size는 0x0040 이라고 되어 있다.
그럼 0x0218부터 idle task의 context들이 들어 있을것이므로 실제로 메모리에 들어가 있는지 확인해 보도록 하겠다.
위의 그림에서 빨간색으로 선택된 영역의 값들이 idel task의 초기 context 값들이다.
실제로 arch_load_context를 수행하였을때 위의 context들이 CPU레지스터에 잘 들어가는지 확인해 보도록 하겠다.
참고로, idle tcb의 stack_base가 0x1F2이고, 크기가 0x40이므로 스택의 끝 부분은 0x231이 된다. 그런데 위의 그림에서는 0x230이 끝으로 되어 있다. 그 이유는 attach_stack() 함수에서 stack_ptr 계산할 때 마지막에 -1을 해 주었기 때문이다. 굳이 -1을 해주지 않아도 된다는 것을 의미하므로 attach_stack()함수를 수정하도록 해야겠다.
이제, 현재 상태에서 부터는 한 명령어씩 차례로 수행해가며 CPU내부 레지스터들이 어떻게 변해 가는지 확인해 보도록 하겠다.
F11키를 누르면 한스텝 프로그램이 진행된다. 0x20번지로 점프 되어서 멈추게 된다.
계속해서 F11을 한번씩 눌러보도록 하겠다.
위의 그림은 "ret" 명령을 수행하기 직전까지 진행된 결과이다. 스택 포인터 레지스터와 SREG 레지스터가 context 초기화 한대로 변경되었음을 볼 수 있고, r24,r25에 0x1234가 되었음을 알 수 있다.
이제 마지막으로 "ret" 명령을 F11키를 눌러서 실행 시켜 보도록 하겠다.
위의 그림처럼 idle_task의 시작점이 0x9D로 점프되어 왔음을 확인할 수 있다.
이제, 시뮬레이터를 멈추고 실제 보드에서 프로그램을 실행 시켜 보고 그 결과를 확인해 보도록 하겠다.
위의 그림처럼 idle_task까지 잘 실행되어 입력 인자로 들어온 0x1234를 제대로 출력하는것을 확인할 수 있다.
idle task의 TCB와 스택을 위한 할당된 메모리 주소와 크기도 확인할 수 있다.
'심화' 카테고리의 다른 글
megaOS - 8. Interrupt (0) | 2016.10.29 |
---|---|
megaOS - 7. Context(Task) Switching (0) | 2016.10.27 |
megaOS - 5. TCB와 Context (2) | 2016.10.22 |
megaOS - 4. Scheduler(스케줄러) (0) | 2016.10.22 |
megaOS - 3. Double linked list (0) | 2016.10.20 |