오후 4:45 2006-06-01
preemtive switch 완성
조경민 bro@shinbiro.com
http://neri.cafe24.com
============================================================
인터럽트 핸들러에서 어셈 짤때 주의 할점
-----------------------------------------
1. SVC모드 태스크를 위해 msr cpsr로 직접 인터럽트를 활성화 시키면
바로 인터럽트가 발생될 수 있으므로, IRQ모드로 전환 후 msr spsr을 해야함
2. IRQ모드에서 r0-r12는 SVC모드와 공유된다. 즉, 사용하면 다시 복원해야한다.
stmfd sp!, {r0}
r0 사용
ldmfd sp!, {r0}
이렇게 사용 후 복원을 고민해야한다.
3. IRQ모드의 lr값을 SVC모드에게 전달하려면 방법이 현재 없다. 특정 메모리를
사용하게 하였다.
lr_irq EQU 0x33000004
; IRQ 모드의 lr_irq를 특정 위치 =lr_irq로 저장
stmfd sp!, {r0}
; *MEM_lr_irq = lr_svc
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0} ; sp_irq임
; SVC모드에서 =lr_irq를 받아다가 lr_svc로 복원할때
stmfd sp!, {r0}
; lr_irq = MEM_lr_irq
ldr r0, =lr_irq
ldr lr, [r0]
ldmfd sp!, {r0} ; sp_svc임
4. IRQ모드로 들어오면
a. IRQ는 비활성화
b. lr에는 복귀할 주소+4값 (마지막 실행한 주소+8)
c. SVC모드의 psr은 spsr에 들어있음
SVC모드로 돌아가기 위해서 아래와 같은 코드 중 하나를 실행하면 된다.
movs pc, lr
subs pc, lr, #4
ldmfd sp!, {r0-r3,pc}^
기존의 taskswitch 코드는 아래와 같다.
1. cpu context를 태스크 스택에 넣는다.
2. 스케줄링으로 새로운 태스크 스택을 얻는다.
3. 새로운 태스크 스택 정보로 cpu context를 채운다.
task_switch
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
mrs r0, CPSR
stmfd sp!, {r0}
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록
mrs r0, CPSR
ORR r0, r0, #0x80
msr CPSR_c, r0
; do schdule and get next task stack top pointer
mov r0, sp
bl schedule
mov sp, r0
; 다시 인터럽트 살려주고..
mov r0, #1
MRS r1,CPSR
ORR r1,r1,#0x80
BIC r0,r1,r0,LSL #7
MSR CPSR_c,r0
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0
ldmfd sp!, {r0-r12,pc}
그런데, 이 코드는 여전히 문제가 있다.
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0 <--- A 부분
ldmfd sp!, {r0-r12,pc}
A부분에서 새로운 태스크의 cpsr이 들어가는데 0x53 즉, IRQ를 받도록 된 svc모드
인 경우, atomic하게 ldmfd sp!, {r0-r12,pc} 도 실행되어야 하는데
그렇게 안되고 msr 명령 실행 한 후 바로 인터럽트가 걸려 태스크 스위치가
중복되어 실행될 문제가 있었다.;
이를 방지하기 위해서는 직접 태스크의 cpsr을 msr하기 보다는 IRQ 모드로 변환후
spsr에 값을 넣어 movs pc, lr을 시켜 arm이 IRQ모드에서 SVC모드로 전환 시
spsr의 값을 SVC모드의 새로운 태스크의 cpsr로 복원하게 만들어야 한다.
이렇게 하기 위해서 또 IRQ, SVC 모드간 공유할 수 있는 메모리인 cpsr_svc를
만들었다. -_-;
그리고 create_task시 func+4를 하는 불상사를 막기 위해서 boot.s의 IRQ핸들러에서
미리 lr -4를 하게 하였다. arm 책을 보니 IRQ 모드로 왔을때 lr_irq에는 마지막
실행된 주소+8이 되어 있다고 하는 걸 읽었다 -_-
즉, lr - 4가 진짜 실행할 주소라는 말이다. 이제 create_task에서 다시 원래대로
+ 4 안하고 바로 func를 넣을 수 있게 되었다.
irq
; IRQ shadow reg: r13, r14, spsr
; r14_svc
; save cpu context of old task
; 이미 cpsr의 IRQ는 꺼져있게 된다. irq 복원시 자동 온됨
; irq_lr에는 마지막 실행된 주소 + 8이 들어 있으므로 미리 -4를 함
sub lr, lr, #4
stmfd sp!, {r0-r3,lr} ; APCS 규약에 맞게 r0 - r3, lr만 저장
bl irq_handler
cmp r0, #1 ; need schdule
bne irq_return
ldmfd sp!, {r0-r3,lr}
b need_schedule
irq_return
ldmfd sp!, {r0-r3, pc}^
어쨋든, task_switch에서도 아래처럼 바뀌었다. 엄청 길어졌다 -_-;;
cpsr_svc EQU 0x33000010
lr_irq EQU 0x33000004
이제 좀 길다;
task_switch
stmfd sp!, {r0}
; 오후 12:44 2006-06-01 이부분은 창우의 조언에 따르기로 한다.
; task_switch() 함수는 정책적으로 native function이다.
; 무조건 IRQ를 Disable 시킨다. 유저는 반드시 task_switch()호출후
; irq_enable()해야 한다.
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록 irq disable한다.
mrs r0, CPSR
ORR r0, r0, #0x80
msr CPSR_c, r0
ldmfd sp!, {r0}
; 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_cf, r0 ; 바로 psr_svc에 IRQ 활성화하면 안됨
; *cpsr_svc = r0 task's psr
ldr r1, =cpsr_svc
str r0, [r1]
ldmfd sp!, {r0-r12,lr}
stmfd sp!, {r0}
; *MEM_lr_irq = lr_svc
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
mov lr, #IRQMODE | NOINT
msr cpsr_c,lr
; in irq: lr = lr_irq, spsr = cpsr_svc
stmfd sp!, {r0}
; lr_irq = MEM_lr_irq
ldr r0, =lr_irq
ldr lr, [r0]
; spsr = MEM_cpsr_svc
ldr r0, =cpsr_svc
ldr r0, [r0]
msr spsr_cf, r0
ldmfd sp!, {r0}
movs pc, lr
게다가 task_start 시키는 것 역시 msr을 쓰므로 길어졌다.;
task_start
mov sp, r0
; restore cpu context of new task
ldmfd sp!, {r0}
;msr CPSR_cf, r0
;ldmfd sp!, {r0-r12,pc}
; *cpsr_svc = r0 task's psr
ldr r1, =cpsr_svc
str r0, [r1]
ldmfd sp!, {r0-r12,lr}
stmfd sp!, {r0}
; *MEM_lr_irq = lr_svc
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
mov lr, #IRQMODE | NOINT
msr cpsr_c,lr
; in irq: lr = lr_irq, spsr = cpsr_svc
stmfd sp!, {r0}
; lr_irq = MEM_lr_irq
ldr r0, =lr_irq
ldr lr, [r0]
; spsr = MEM_cpsr_svc
ldr r0, =cpsr_svc
ldr r0, [r0]
msr spsr_cf, r0
ldmfd sp!, {r0}
movs pc, lr
태스크 생성에서 func+4가 빠졌다. 이제 보기 좋아졌다 ㅠㅠ
그리고 바로 0xD3 (no irq, svc모드)이 아닌 0x53(irq on, svc모드)로 태스크를
활성화 할 수 있다!
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 = 0x53; // irq enable, 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)
*/
}
이제 타이머를 이용해 선점형 태스크 스위치와 명시적 태스크 스위치를 할 수
있게 되었다.
void test1()
{
int i = 0;
while(1)
{
task_switch();
irq_enable();
i++;
}
}
void test2()
{
while(1)
{
//task_switch();
}
}
:
void main()
{
init_timer0();
create_task( &g_Tasks[0], (int)test1 );
create_task( &g_Tasks[1], (int)test2 );
create_task( &g_Tasks[2], (int)test3 );
create_task( &g_Tasks[3], (int)test4 );
g_Tasks[0].next = &g_Tasks[1];
g_Tasks[1].next = &g_Tasks[2];
g_Tasks[2].next = &g_Tasks[3];
g_Tasks[3].next = &g_Tasks[0];
current_task = &g_Tasks[0];
task_start((int)current_task->stack);
}
스터디 용이라 이해하기 쉬운 소스로 짜려다 보니, 많이 길어졌다;
물론 실무에서는 이렇게 길게 하면 안되고 optimization을 해야겠지..
preemtive switch 완성
조경민 bro@shinbiro.com
http://neri.cafe24.com
============================================================
인터럽트 핸들러에서 어셈 짤때 주의 할점
-----------------------------------------
1. SVC모드 태스크를 위해 msr cpsr로 직접 인터럽트를 활성화 시키면
바로 인터럽트가 발생될 수 있으므로, IRQ모드로 전환 후 msr spsr을 해야함
2. IRQ모드에서 r0-r12는 SVC모드와 공유된다. 즉, 사용하면 다시 복원해야한다.
stmfd sp!, {r0}
r0 사용
ldmfd sp!, {r0}
이렇게 사용 후 복원을 고민해야한다.
3. IRQ모드의 lr값을 SVC모드에게 전달하려면 방법이 현재 없다. 특정 메모리를
사용하게 하였다.
lr_irq EQU 0x33000004
; IRQ 모드의 lr_irq를 특정 위치 =lr_irq로 저장
stmfd sp!, {r0}
; *MEM_lr_irq = lr_svc
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0} ; sp_irq임
; SVC모드에서 =lr_irq를 받아다가 lr_svc로 복원할때
stmfd sp!, {r0}
; lr_irq = MEM_lr_irq
ldr r0, =lr_irq
ldr lr, [r0]
ldmfd sp!, {r0} ; sp_svc임
4. IRQ모드로 들어오면
a. IRQ는 비활성화
b. lr에는 복귀할 주소+4값 (마지막 실행한 주소+8)
c. SVC모드의 psr은 spsr에 들어있음
SVC모드로 돌아가기 위해서 아래와 같은 코드 중 하나를 실행하면 된다.
movs pc, lr
subs pc, lr, #4
ldmfd sp!, {r0-r3,pc}^
기존의 taskswitch 코드는 아래와 같다.
1. cpu context를 태스크 스택에 넣는다.
2. 스케줄링으로 새로운 태스크 스택을 얻는다.
3. 새로운 태스크 스택 정보로 cpu context를 채운다.
task_switch
; save cpu context of old task
stmfd sp!, {r0-r12,lr}
mrs r0, CPSR
stmfd sp!, {r0}
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록
mrs r0, CPSR
ORR r0, r0, #0x80
msr CPSR_c, r0
; do schdule and get next task stack top pointer
mov r0, sp
bl schedule
mov sp, r0
; 다시 인터럽트 살려주고..
mov r0, #1
MRS r1,CPSR
ORR r1,r1,#0x80
BIC r0,r1,r0,LSL #7
MSR CPSR_c,r0
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0
ldmfd sp!, {r0-r12,pc}
그런데, 이 코드는 여전히 문제가 있다.
; restore cpu context of new task
ldmfd sp!, {r0}
msr CPSR_cxsf, r0 <--- A 부분
ldmfd sp!, {r0-r12,pc}
A부분에서 새로운 태스크의 cpsr이 들어가는데 0x53 즉, IRQ를 받도록 된 svc모드
인 경우, atomic하게 ldmfd sp!, {r0-r12,pc} 도 실행되어야 하는데
그렇게 안되고 msr 명령 실행 한 후 바로 인터럽트가 걸려 태스크 스위치가
중복되어 실행될 문제가 있었다.;
이를 방지하기 위해서는 직접 태스크의 cpsr을 msr하기 보다는 IRQ 모드로 변환후
spsr에 값을 넣어 movs pc, lr을 시켜 arm이 IRQ모드에서 SVC모드로 전환 시
spsr의 값을 SVC모드의 새로운 태스크의 cpsr로 복원하게 만들어야 한다.
이렇게 하기 위해서 또 IRQ, SVC 모드간 공유할 수 있는 메모리인 cpsr_svc를
만들었다. -_-;
그리고 create_task시 func+4를 하는 불상사를 막기 위해서 boot.s의 IRQ핸들러에서
미리 lr -4를 하게 하였다. arm 책을 보니 IRQ 모드로 왔을때 lr_irq에는 마지막
실행된 주소+8이 되어 있다고 하는 걸 읽었다 -_-
즉, lr - 4가 진짜 실행할 주소라는 말이다. 이제 create_task에서 다시 원래대로
+ 4 안하고 바로 func를 넣을 수 있게 되었다.
irq
; IRQ shadow reg: r13, r14, spsr
; r14_svc
; save cpu context of old task
; 이미 cpsr의 IRQ는 꺼져있게 된다. irq 복원시 자동 온됨
; irq_lr에는 마지막 실행된 주소 + 8이 들어 있으므로 미리 -4를 함
sub lr, lr, #4
stmfd sp!, {r0-r3,lr} ; APCS 규약에 맞게 r0 - r3, lr만 저장
bl irq_handler
cmp r0, #1 ; need schdule
bne irq_return
ldmfd sp!, {r0-r3,lr}
b need_schedule
irq_return
ldmfd sp!, {r0-r3, pc}^
어쨋든, task_switch에서도 아래처럼 바뀌었다. 엄청 길어졌다 -_-;;
cpsr_svc EQU 0x33000010
lr_irq EQU 0x33000004
이제 좀 길다;
task_switch
stmfd sp!, {r0}
; 오후 12:44 2006-06-01 이부분은 창우의 조언에 따르기로 한다.
; task_switch() 함수는 정책적으로 native function이다.
; 무조건 IRQ를 Disable 시킨다. 유저는 반드시 task_switch()호출후
; irq_enable()해야 한다.
; 수동 스케줄링 중에 갑자기 interrupt가 들어와서
; r14를 0으로 바꾸는 일이 없도록 irq disable한다.
mrs r0, CPSR
ORR r0, r0, #0x80
msr CPSR_c, r0
ldmfd sp!, {r0}
; 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_cf, r0 ; 바로 psr_svc에 IRQ 활성화하면 안됨
; *cpsr_svc = r0 task's psr
ldr r1, =cpsr_svc
str r0, [r1]
ldmfd sp!, {r0-r12,lr}
stmfd sp!, {r0}
; *MEM_lr_irq = lr_svc
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
mov lr, #IRQMODE | NOINT
msr cpsr_c,lr
; in irq: lr = lr_irq, spsr = cpsr_svc
stmfd sp!, {r0}
; lr_irq = MEM_lr_irq
ldr r0, =lr_irq
ldr lr, [r0]
; spsr = MEM_cpsr_svc
ldr r0, =cpsr_svc
ldr r0, [r0]
msr spsr_cf, r0
ldmfd sp!, {r0}
movs pc, lr
게다가 task_start 시키는 것 역시 msr을 쓰므로 길어졌다.;
task_start
mov sp, r0
; restore cpu context of new task
ldmfd sp!, {r0}
;msr CPSR_cf, r0
;ldmfd sp!, {r0-r12,pc}
; *cpsr_svc = r0 task's psr
ldr r1, =cpsr_svc
str r0, [r1]
ldmfd sp!, {r0-r12,lr}
stmfd sp!, {r0}
; *MEM_lr_irq = lr_svc
ldr r0, =lr_irq
str lr, [r0] ; MEM_lr_irq <= new_task.lr_svc
ldmfd sp!, {r0}
mov lr, #IRQMODE | NOINT
msr cpsr_c,lr
; in irq: lr = lr_irq, spsr = cpsr_svc
stmfd sp!, {r0}
; lr_irq = MEM_lr_irq
ldr r0, =lr_irq
ldr lr, [r0]
; spsr = MEM_cpsr_svc
ldr r0, =cpsr_svc
ldr r0, [r0]
msr spsr_cf, r0
ldmfd sp!, {r0}
movs pc, lr
태스크 생성에서 func+4가 빠졌다. 이제 보기 좋아졌다 ㅠㅠ
그리고 바로 0xD3 (no irq, svc모드)이 아닌 0x53(irq on, svc모드)로 태스크를
활성화 할 수 있다!
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 = 0x53; // irq enable, 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)
*/
}
이제 타이머를 이용해 선점형 태스크 스위치와 명시적 태스크 스위치를 할 수
있게 되었다.
void test1()
{
int i = 0;
while(1)
{
task_switch();
irq_enable();
i++;
}
}
void test2()
{
while(1)
{
//task_switch();
}
}
:
void main()
{
init_timer0();
create_task( &g_Tasks[0], (int)test1 );
create_task( &g_Tasks[1], (int)test2 );
create_task( &g_Tasks[2], (int)test3 );
create_task( &g_Tasks[3], (int)test4 );
g_Tasks[0].next = &g_Tasks[1];
g_Tasks[1].next = &g_Tasks[2];
g_Tasks[2].next = &g_Tasks[3];
g_Tasks[3].next = &g_Tasks[0];
current_task = &g_Tasks[0];
task_start((int)current_task->stack);
}
스터디 용이라 이해하기 쉬운 소스로 짜려다 보니, 많이 길어졌다;
물론 실무에서는 이렇게 길게 하면 안되고 optimization을 해야겠지..
'KB > embbeded sw' 카테고리의 다른 글
IQ Online ARM current Issue (0) | 2006.06.12 |
---|---|
Priority Inversion 해결 (0) | 2006.06.08 |
arm-8: boot.s에서 사용한 smdk2440을 위한 코드 (0) | 2006.05.30 |
arm-8: timer 상에서 taskswitch하기 (preemptive, round-robin) (0) | 2006.05.30 |
Reconfigurable Architecture (0) | 2006.05.26 |