오후 2:16 2006-05-02
task switch 하기
조경민 bro@shinbiro.com
http://neri.cafe24.com
============================================================
;=====================================================
; boot test
; by bro, bro@shinbiro.com 오전 10:23 2006-04-26
;
; armasm main.s -o main.o
; armlink main.o --ro-base 0x0 -o main.axf
;=====================================================
; main.c의 커널 main 함수 심볼 쓰기 위해서
IMPORT main
IMPORT ||Image$$KERNEL$$ZI$$Limit||
EXPORT __user_initial_stackheap
EXPORT vector
; KERNEL region 복사를 위해 심볼 사용
IMPORT ||Image$$KERNEL$$Base||
IMPORT ||Image$$KERNEL$$Length||
IMPORT ||Load$$KERNEL$$Base||
PRESERVE8
AREA boot, CODE, READONLY
ENTRY
vector ; 인터럽트 벡터 테이블
b reset ; 주소 0번지에 reset 인터럽트가 오면 바로 reset레이블로 점프
b . ; undefined_instruction
b . ; software_interrupt
b . ; prefetch_abort
b . ; data_abort
b . ; not_used
b . ; IRQ
b . ; FIQ
reset
; 현재 모드에서 IRQ|FIQ를 끈다.
mrs r0, CPSR
bic r0, r0, #0x1f
orr r0, r0, #(0x13 | 0xc0) ; 0x13(0001 0011) | 0xC0 (1101 0000) = 0xD3(1101 0011)
msr CPSR_cxsf, r0
; 스택 초기화 0x9000
mov sp, #0x00080000
; 커널 복사 (memory.map에서 커널영역은 0x10000에서 하겠다고 했으니 복사해야함;)
bl copy_kernel
ldr pc, =main
b end
__user_initial_stackheap
LDR r0, =||Image$$KERNEL$$ZI$$Limit||
MOV pc, lr
copy_kernel
ldr r0, =||Load$$KERNEL$$Base|| ; source (FLASH ROM같은 영역)
ldr r1, =||Image$$KERNEL$$Base|| ; destination (SDRAM 같은 영역)
ldr r2, =||Image$$KERNEL$$Length|| ; kernel length
mov r3, #0 ; counter
loop
ldr r4, [r0], #4
str r4, [r1], #4
add r3, r3, #4
cmp r3, r2
bne loop
mov pc, lr
end
b .
END
-----------------------------------------------------------------------------
main.c
#define TASK_STACK_SIZE 0x200
#define TASK_NUMBER 3
typedef struct _task
{
char base[TASK_STACK_SIZE];
int* stack;
int func;
}task;
task g_Tasks[TASK_NUMBER];
task* current_task;
void task_switch(void); // in int.s
void task_start(int task_sp);
void memset( char* src, int c, int size )
{
int i;
for( i = 0; i < size; i ++)
src[i] = c;
}
void create_task( task* pTask, int func )
{
memset( pTask->base, 0, TASK_STACK_SIZE );
pTask->func = func;
pTask->stack = (int*)(pTask->base + TASK_STACK_SIZE - 4); // t0
pTask->stack -= 1; // pc
*pTask->stack = func; // 시작할 pc값 넣음
pTask->stack -= 13; // r0-r12 적음
pTask->stack -= 1; // CPSR flag기록
*pTask->stack = 0xD3; // IF_svc mode (t1)
// full decending (start empty)
/*
empty
+--------+ (0x19C) +--------+ TASK_STACK_SIZE (0x200)
| | <- stack (t0) | func |
| | +--------+
| | | r12 |
| | +--------+
| | | ... |
| | +--------+
| | | r0 |
| | +--------+
| | | 0xD3 |
| | +--------+ <- stack top (t1)
| | | |
+--------+ <- base (0x0) +--------+ <- base (0x0)
*/
}
task* select_task()
{
static int i = 0;
if( i >= TASK_NUMBER ) i = 0;
return &g_Tasks[i++];
}
void* schedule(void* current_sp)
{
task* pTask;
// 만일 task_start()없이 바로 task_switch()한다면
// if문이 필요하다. current_task없이 커널 main() 코드의 sp가 넘어오기 때문.
// if(current_task)
current_task->stack = (int*)current_sp;
pTask = select_task();
current_task = pTask;
return pTask->stack;
}
void test1()
{
int i = 0;
while(1)
{
task_switch();
i++;
}
}
void test2()
{
while(1)
{
task_switch();
}
}
void test3()
{
int c = 0;
while(1)
{
c +=2;
task_switch();
}
}
void main()
{
create_task( &g_Tasks[0], (int)test1 );
create_task( &g_Tasks[1], (int)test2 );
create_task( &g_Tasks[2], (int)test3 );
current_task = &g_Tasks[0];
task_start((int)current_task->stack);
}
-----------------------------------------------------------------------------
int.s
EXPORT task_switch
EXPORT task_start
IMPORT schedule
PRESERVE8
AREA intr, CODE, READONLY
ENTRY
task_switch
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
mrs r0, CPSR
stmfd sp!, {r0}
; do schdule and get next task stack top pointer
mov r0, sp
bl schedule
mov sp, r0
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0
ldmfd sp!, {r0-r12,pc}
task_start
mov sp, r0
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0
ldmfd sp!, {r0-r12,pc}
END
-----------------------------------------------------------------------------
memory.map
MEMORY_MAP 0x0
{
BOOT 0x0
{
boot.o (boot, +First)
* (+RO, +RW, +ZI)
}
KERNEL 0x10000
{
main.o (+RO, +RW, +ZI)
int.o (+RO, +RW, +ZI)
}
}
-----------------------------------------------------------------------------
빌드 과정
armasm boot.s -G -o boot.o
armasm int.s -G -o int.o
armcc -c main.c -arm -debug -g -o main.o
armlink boot.o main.o int.o -map -noremove -scatter memory.map -entry vector -o kernel.axf
이번에는 본격적으로 커널에서 task switch를 구현하였다.
태스크 스위치 과정은 가장 간단하게 표현하자면, 각 태스크가 자신의 실행 상태 즉, cpu의 context를
기억하고 있다가 자신이 실행되어야할 시점이 되면 자신이 실행되었을 마지막 시점의 cpu context를
복원시키는 과정이다.
cpu context는 주로 general register와 PC(program counter), stack, 그리고 cpu state register이다.
x86중 i386의 경우 EAX, EBX, ECX, EDX, gs,fs, ss, cs, flag register 등이다.
arm의 경우에는 r0에서 r12, 그리고 sp, lr, pc, cpsr값이다.
처음 task1의 cpu context 값이 CPU에 들어가면 task1 함수가 동작되게 된다.
task1 CPU
-------- --------
r0-r12 r0-12
pc => pc
cpsr cpsr
이 후 적절한 시점에 스케줄링이 일어나서 task2가 실행되어야 한다면 task1에 현재 cpu context값을
저장한다.
task1 CPU
-------- --------
r0-r12 r0-12
pc <= pc
cpsr cpsr
그리고 task2의 마지막 실행한 cpu context값을 CPU에 복원함으로써 task2가 재실행된다.
task2 CPU
-------- --------
r0-r12 r0-12
pc => pc
cpsr cpsr
각 태스크들은 자신의 cpu context를 보통 태스크마다 존재하는 스택 영역에 저장하게 된다.
task_switch
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
mrs r0, CPSR
stmfd sp!, {r0}
; do schdule and get next task stack top pointer
mov r0, sp
bl schedule
mov sp, r0
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0
ldmfd sp!, {r0-r12,pc}
본 구현에서는 stmfd/ldmfd를 사용하여 각 태스크의 Full Decending stack에 cpu context를 저장하였다.
가장 처음 태스크가 생성되는 시점에 태스크 스택 영역을 확보하고, 스택에 수동으로 실행될 pc값과
초기 r0-r12값, cpsr값을 저장해두어야 한다.
그러면 task_switch의 restore 과정에서 태스크의 스택에서 이 cpu context를 얻어와 cpu에 넣게 된다.
void create_task( task* pTask, int func )
{
memset( pTask->base, 0, TASK_STACK_SIZE );
pTask->func = func;
pTask->stack = (int*)(pTask->base + TASK_STACK_SIZE); // t0
pTask->stack -= 1; // pc
*pTask->stack = func; // 시작할 pc값 넣음
pTask->stack -= 13; // r0-r12 적음
pTask->stack -= 1; // CPSR flag기록
*pTask->stack = 0xD3; // IF_svc mode (t1)
// full decending (start empty)
/*
empty
+--------+ <- stack (t0) +--------+ TASK_STACK_SIZE
| | (0x200) | func |
| | +--------+
| | | r12 |
| | +--------+
| | | ... |
| | +--------+
| | | r0 |
| | +--------+
| | | 0xD3 |
| | +--------+ <- stack top (t1)
| | | |
+--------+ <- base (0x0) +--------+ <- base (0x0)
*/
'KB > embbeded sw' 카테고리의 다른 글
arm-7: task switch 하기 on smdk2440 board (0) | 2006.05.04 |
---|---|
arm-6: TRACE32 사용법 익히기 (0) | 2006.05.03 |
arm-4: 커널 영역 올리기 (0) | 2006.04.27 |
arm-3 : c코드로 점프~ (0) | 2006.04.26 |
arm-2: boot 테스트 (0) | 2006.04.26 |