오후 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

+ Recent posts