컨텍스트 스위칭(Context Switching) 과정 정리
컨텍스트 스위칭은 현재 실행 중인 태스크(Task)의 실행 상태(레지스터 값, 스택 포인터 등)를 저장하고, 다음 실행할 태스크의 상태를 복구하여 실행을 이어가는 과정이다.
아래는 Task1 → Task2로 전환되는 과정을 상세히 설명한 것이다.
1. Task1이 실행 중
- Task1이 실행 중이며, Task1의 스택에는 현재 실행 중이던 함수 호출 정보, 지역 변수 등이 저장되어 있다.
- Task1의 레지스터(r0~r12, lr, cpsr)는 현재 Task1의 연산 상태를 유지하고 있다.
2. 컨텍스트 저장 (Save_context)
라운드 로빈 스케줄러가 Task1의 실행을 중단하고 Task2로 전환하려고 할 때,
현재 Task1의 컨텍스트를 저장하는 과정이다.
1. 현재 실행 중인 태스크(Task1)의 LR(링크 레지스터)과 범용 레지스터(r0~r12)를 스택에 저장
PUSH {lr}
PUSH {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}
2. CPSR(프로세서 상태 레지스터)을 저장하여 Task1의 실행 상태를 기억
MRS r0, cpsr
PUSH {r0}
3. Task1의 현재 스택 포인터(sp)를 Task1의 TCB(sCurrent_tcb)에 저장
LDR r0, =sCurrent_tcb // 현재 실행 중인 태스크의 TCB 주소 로드
LDR r0, [r0] // 현재 TCB 주소를 r0에 로드
STMIA r0!, {sp} // 현재 태스크의 sp를 저장
✅ 결과: Task1의 모든 실행 상태(레지스터, 스택, CPSR)가 Task1의 스택 및 TCB에 저장됨.
✅ 추가 질문
1) STMIA 명령어란?
STMIA (Store Multiple Increment After)는 레지스터의 여러 값을 메모리에 저장하는 명령어다.
- STMIA Rn!, {Rlist}
- Rn(베이스 레지스터): 메모리 주소를 가리키는 레지스터
- Rlist(레지스터 리스트): 메모리에 저장할 레지스터 목록
- ! (Writeback) 옵션이 있으면, Rn이 자동으로 증가하여 다음 메모리 주소를 가리킴
- 메모리 저장 후 Rn이 증가 (Increment After)
STMIA r0!, {r1, r2, r3}
- 예제:
- r1, r2, r3의 값을 r0이 가리키는 메모리에 저장
- 이후 r0가 3 * 4byte = 12byte 증가함
2) LDR r0, =sCurrent_tcb 이후, LDR r0,[r0]를 하는 이유
LDR r0, =sCurrent_tcb 수행시, r0에는 sCurrent_tcb의 주소값이 저장되어있다.
따라서 해당 주소값의 []를 처리해줘야 실제 tcb의 주소가 r0에 전달된다.
3) __attribute__ ((naked))의 의미
__attribute__ 는 GCC 컴파일러 어트리뷰트 지시어를 사용한다는 의미이다. 이때, ((nake))를 사용하면 자동적으로 stack에 현재 주소를 push 및 pop 하는작업이 제외된다.
((naked))를 선언하지 않는다면 push 및 pop 하는 작업이 수행된다.
* naked 선언하지 않은경우 disassembly
push {fp} ; (1) 프레임 포인터(fp)를 스택에 저장
add fp, sp, #0 ; (2) 현재 sp 값을 fp에 복사 → 새로운 프레임 설정
b <Save_context>; (3) Save_context() 호출
b <Restore_context>; (4) Restore_context() 호출
sub sp, fp, #0 ; (5) 스택을 원래 위치로 되돌림 (실제로는 효과 없음)
pop {fp} ; (6) 저장해둔 fp를 복원
bx lr ; (7) 원래 함수 호출 지점으로 복귀
✅ naked가 없을 때의 동작
__attribute__((naked))를 사용하지 않으면, **컴파일러가 함수 호출 규약(Calling Convention)**을 자동으로 적용하여 프롤로그(함수 진입)와 에필로그(함수 종료) 코드를 삽입합니다.
1️⃣ 프롤로그 (Function Prologue)
- push {fp} : 기존 프레임 포인터(fp, 즉 r7 또는 r11 depending on the ABI)를 스택에 저장
- add fp, sp, #0 : fp를 현재 sp로 설정하여 새로운 스택 프레임 생성
2️⃣ 함수 본문 실행
- b <Save_context> → 문맥 저장
- b <Restore_context> → 문맥 복구
3️⃣ 에필로그 (Function Epilogue)
- sub sp, fp, #0 : (의미 없는 동작, 보통은 mov sp, fp가 와야 함)
- pop {fp} : 저장했던 fp를 복원
- bx lr : lr로 복귀 (즉, 원래 호출한 함수로 돌아감)
🔥 __attribute__((naked))가 없을 때의 특징
✅ 함수 호출 규약을 자동으로 적용 (스택 프레임 자동 관리)
✅ 함수가 종료될 때 정확한 리턴을 보장
🚨 하지만 인터럽트 핸들러나 컨텍스트 스위칭처럼 직접 스택을 다루는 경우 불필요한 코드가 추가될 수 있음
3. 다음 태스크(Task2) 선택
라운드 로빈 알고리즘을 이용해 다음 실행할 태스크(Task2)를 결정한다.
sCurrent_tcb_index++;
sCurrent_tcb_index %= sAllocated_tcb_index;
sNext_tcb = &sTask_list[sCurrent_tcb_index];
✅ 결과: Task2가 다음 실행할 태스크로 결정됨.
4. 컨텍스트 복구 (Restore_context)
Task2의 실행을 복원하는 과정이다.
1. Task2의 스택 포인터(sp)를 Task2의 TCB에서 가져와서 현재 sp로 설정
LDR r0, =sNext_tcb // 다음 실행할 태스크의 TCB 주소 로드
LDR r0, [r0] // 다음 실행할 TCB 주소를 r0에 로드
LDMIA r0!, {sp} // Task2의 sp를 설정
2. Task2의 CPSR 값을 복구하여 Task2가 중단된 시점의 CPU 상태를 원래대로 돌림
POP {r0} // 스택에서 CPSR 값 가져오기
MSR cpsr, r0 // CPSR 설정 (Task2의 실행 상태 복원)
3. Task2의 범용 레지스터(r0~r12)와 LR을 복원
POP {r0, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10, r11, r12}
4. Task2가 실행 중이던 위치(PC)로 복귀하여 실행을 이어감
POP {pc} // PC 복구 → Task2의 실행 위치로 복귀
✅ 결과: Task2의 실행 상태가 복구되어 Task2가 실행됨.
✅ 추가 질문
1) LDMIA(Load Multiple Increment After)
LDMIA (Load Multiple Increment After)는 메모리에서 여러 개의 값을 로드하여 레지스터에 저장하는 명령어다.
- LDMIA Rn!, {Rlist}
- Rn(베이스 레지스터): 메모리 주소를 가리키는 레지스터
- Rlist(레지스터 리스트): 메모리에서 로드할 레지스터 목록
- ! (Writeback) 옵션이 있으면, Rn이 자동으로 증가하여 다음 메모리 주소를 가리킴
- 메모리에서 로드한 후 Rn이 증가 (Increment After)
예제 1: 기본 동작
LDMIA r0! , {r1,r2,r3}
동작 과정:
- r0가 가리키는 메모리 주소에서 r1을 가져온다.
- r0 + 4에서 r2를 가져온다.
- r0 + 8에서 r3를 가져온다.
- r0 값이 r0 + 12로 증가한다.
5. Task2 실행
- Task2는 이전에 실행 중이던 위치에서 그대로 실행을 이어간다.
- Task2의 스택과 레지스터는 이전에 저장된 상태 그대로 복구되어 Task2는 자신의 코드가 계속 실행되고 있다고 인식한다.
6. 이후 스케줄링 (Task2 → Task3)
- 이후에도 타이머 인터럽트가 발생하면 Task2의 컨텍스트를 저장하고, Task3를 선택하여 실행하는 방식으로 반복된다.
- 태스크가 라운드 로빈 방식으로 순환하면서 실행된다.
📌 컨텍스트 스위칭 과정 요약
1️⃣ Task1이 실행 중
2️⃣ Save_context()
- Task1의 레지스터(r0~r12, lr)와 CPSR 값을 스택에 저장
- Task1의 스택 포인터(sp)를 TCB에 저장
3️⃣ 다음 실행할 태스크(Task2)를 선택
4️⃣ Restore_context() - Task2의 스택 포인터(sp)를 TCB에서 복원
- Task2의 CPSR을 복원
- Task2의 레지스터(r0~r12, lr)와 PC를 복원하여 실행 재개
5️⃣ Task2가 실행됨
이 과정을 반복하여 여러 태스크가 순환하면서 실행된다.
📌 컨텍스트 스위칭이 필요한 이유
✅ 멀티태스킹 지원: 여러 태스크가 실행될 수 있도록 함
✅ CPU 리소스 분배: 특정 태스크가 CPU를 독점하지 않도록 함
✅ 인터럽트 처리 가능: 특정 태스크가 중단되더라도 실행을 이어갈 수 있음
'SW 개발 공부 > OS 개발 프로젝트' 카테고리의 다른 글
[나빌로스] RTOS 컨텍스트 스위칭 정리 (Round Robin + 선점형 기반) (0) | 2025.04.08 |
---|---|
[나빌로스] 8.태스크 (0) | 2025.04.02 |
[나빌로스] 7.타이머 (0) | 2025.04.02 |
[나빌로스] 5-1. UART printf 구현하기 (0) | 2025.04.01 |
[나빌로스] 5. UART (0) | 2025.03.29 |