저희가 지난 일요일 스터디 때, Context Switching 에서 PC 또는 LR을 출력하였을 때, 서로 다른 Task 에서도 바뀌는 부분이 없어서 혼란스러웠었는데요.
이는 BeakOS 가 Thread 방식 RTOS 인 점과 무관하지 않을까 합니다.
우선 Thread 방식이기 때문에 같은 address영역을 공유하는 점을 기억해야 합니다.
이 부분을 기억하고 Context switching 과정을 따라가 보도록 하겠습니다.
이에 앞서, 저희가 기억해야 하는 몇 address 값을 적어보겠습니다.
40018798 B task0
400187c4 B task1
400187f0 B task2
또한 다음에 선택 될 task 를 task1, 현재 task 를 task0 으로 가정합니다.
우선 task0 이 잠드는 시점부터 시작하겠습니다.
int Task0Main(void *args) {
while (1) {
printf("Task : 0\n");
TaskYield();
}
return 0;
}
이 때, TaskYield(); 바로 다음 addr 이 Stack 에 저장되게 됩니다.(lr)
또, 이 상황에서의 fp가 같이 저장됩니다.
(의문점. 분명 ARM스터디 한 책 에선 현대 ARM아키텍쳐에서는 fp를 Stack Frame Pointer 로 사용하지 않는다고 했는데, 실제 코드에선 마치 FP를 사용하는 듯 동작한다.)
int TaskYield(void) {
int flag;
flag = IntSave();
TaskDequeue(currentTask);
TaskEnqueue(currentTask);
DoScheduling();
IntRestore(flag);
return 0;
}
여기서 역시 fp 와 DoScheduling 다음 명령어 주소가 stack 에 저장됩니다.
이때, int flag가 유지됨을 주의하여야 합니다.
void DoScheduling(void) {
struct TaskStruct *current;
current = currentTask;
currentTask = TaskSelect();
if (currentTask == current) {
return;
}
ContextSwitch(current, currentTask);
}
여기서, fp와 역시 다음 주소가 스택에 저장됩니다.
이 때, current가 스택에 저장됩니다.
void ContextSwitch(struct TaskStruct *currentTask, struct TaskStruct *nextTask) {
struct ContextFrame *ctx = (struct ContextFrame *)nextTask->stackPoint;
unsigned int tmp;
이제 컨텍스트 스위칭을 하는 위치 입니다.
이 때, 넘겨받은 인자값을 스택에 저장하므로 주의하여야 합니다.
여기까지 왔을 때, 스택은 아래와 같이 생겼습니다.
Task : 0 sp = 400197fc 400197fc: 0x400187ec r1 40019800: 0x400187c0 r0 40019804: 0x400197fc tmp 40019808: 0x4001a7b4 struct 4001980c: 0x40019820 fp 40019810: 0x4001164c lr (DoScheduling) 40019814: 0x00000000 dumy 40019818: 0x400187c0 str 4001981c: 0x40019830 fp 40019820: 0x40011a4c lr (TaskYield) 40019824: 0x40011db8 flag 40019828: 0x00000000 dumy 4001982c: 0x40019840 fp 40019830: 0x40011b44 lr 40019834: 0x00000000 40019838: 0x00000000 4001983c: 0x00000000 40019840: 0x4001198c 40019844: 0x00000000 40019848: 0x00000000 |
이제, 만약 컨택스트스위칭 과정 없이 각 함수를 빠져나온다면,
최종적으로 40019830: 0x40011b44 lr 이 pc 가 됩니다.
이는, Task0Main에서 TaskYield 다음 위치 입니다.
40011b24 <Task0Main>: 40011b24: e92d4800 push {fp, lr} 40011b28: e28db004 add fp, sp, #4 40011b2c: e24dd008 sub sp, sp, #8 40011b30: e50b0008 str r0, [fp, #-8] 40011b34: e3010db8 movw r0, #7608 ; 0x1db8 40011b38: e3440001 movt r0, #16385 ; 0x4001 40011b3c: ebfffcf0 bl 40010f04 <printf> 40011b40: ebffffb0 bl 40011a08 <TaskYield> 40011b44: eafffffa b 40011b34 <Task0Main+0x10> |
하지만, 컨택스트 스위칭을 하므로, sp가 Task1 의 sp로 변경됩니다.
이 때, Task1 은 Task0과 같은 과정을 거쳐서 해당 위치에 한 번 진입한 적이 있음을 예측 가능합니다.
따라서, Task1 의 스택은 위와 같은 구조입니다.
lr = 4001a7fc 4001a7fc: 0x40018818 r1 4001a800: 0x400187ec r0 4001a804: 0x4001a7fc tmp 4001a808: 0x4001b7b4 struct 4001a80c: 0x4001a820 fp 4001a810: 0x4001164c lr (DoScheduling) 4001a814: 0x00000000 dumy 4001a818: 0x400187ec str 4001a81c: 0x4001a830 fp 4001a820: 0x40011a4c lr (TaskYield) 4001a824: 0x40011dc4 flag 4001a828: 0x00000000 dumy 4001a82c: 0x4001a840 fp 4001a830: 0x40011b68 lr 4001a834: 0x00000000 4001a838: 0x00000000 4001a83c: 0x00000000 4001a840: 0x4001198c 4001a844: 0x00000000 4001a848: 0x00000000 |
이 때, 최종적으로 pc 가 되는 주소는 4001a830: 0x40011b68 입니다.
이는, Task1Main 에서 TaskYield 다음 주소 입니다.
40011b48 <Task1Main>: 40011b48: e92d4800 push {fp, lr} 40011b4c: e28db004 add fp, sp, #4 40011b50: e24dd008 sub sp, sp, #8 40011b54: e50b0008 str r0, [fp, #-8] 40011b58: e3010dc4 movw r0, #7620 ; 0x1dc4 40011b5c: e3440001 movt r0, #16385 ; 0x4001 40011b60: ebfffce7 bl 40010f04 <printf> 40011b64: ebffffa7 bl 40011a08 <TaskYield> 40011b68: eafffffa b 40011b58 <Task1Main+0x10> |
BeakOS 는 위와 같은 과정을 통해 Context Switching 을 하게 됩니다.
감사합니다.
.
정리해주셔서 감사합니다.~
저도 로그를 찍어보면서 따라 가봐야겠네요.