VPOS 에서 인터럽트 발생시, 어떻게 처리하는지에 대한 내용을 다루고 있다.
ARM 의 경우, 플래시 영역에 인터럽트 벡터 테이블을 만들어 놓고, 인터럽트가 발생하면 자동으로 그 쪽으로 점프하게끔 되어 있다.

인터럽트 벡터 테이블 생성

부트로더 역할을 하는 vpos_bootloader.S 파일을 보자!!

vh_VPOS_Bootloader_Startup: 
(0x0)	b       vh_reset     // 0x20
(0x4)	b       vh_undefined_instruction    // 0xC4
(0x8)	b       vh_software_interrupt       // 0xC8
(0xC)	b       vh_prefetch_abort           // 0xCC
(0x10)	b       vh_data_abort               // 0xD0
(0x14)	b       vh_not_used                 // 0xD4
(0x18)	b       vh_irq                      // 0xD8
(0x1C)	b       vh_fiq                      // 0xDC
...

가장 먼저 vh_reset 이 실행이 되고, 이후 루틴들은 해당 인터럽트가 발생할 때에만 수행된다. 또한 이 루틴들은 프로그램 상에서 호출하는 것이 아니고, CPU 에서 H/W 적으로 자동적으로 해당 인터럽트 주소로 점프한다. 아래의 표를 참고하기 바란다.

인터럽트 종류 점프하는 주소
reset 0x0
undefined_instruction 0x4
software_interrupt 0x8
prefetch_abort 0xC
data_abort 0x10
not_used 0x14
irq 0x18
fiq 0x1C

CPU 는 오로지 해당 인터럽트에 정해진 주소(Flash 영역)으로 점프한다. 이 때 만일 각 영역에 제대로된 인터럽트 서비스 루틴이 없을 경우, 시스템은 죽어버린다.
예를 들어 설명하겠다. 앞에서 보인 vpos_bootloader.S 파일을 그대로 Flash 에 write 하면, CPU 가 알고 있는 주소와 일치하기 때문에 우리가 원하는 대로 제대로 인터럽트 서비스 루틴이 수행될 것이다. 하지만, 만일 vpos_bootloader.S 파일을 아래와 같이

vh_VPOS_Bootloader_Startup: 
	b       vh_reset   
	b       vh_software_interrupt      // 순서가 바뀜
	b       vh_undefined_instruction   // 순서가 바뀜
	b       vh_prefetch_abort
	b       vh_data_abort
	b       vh_not_used
	b       vh_irq     
	b       vh_fiq
...

인터럽트 벡터 테이블의 위치가 바뀌어져 있다면, 시스템 수행도중, 'undefined_instruction' 이 발생했을 때, CPU 는 자동적으로 'b vh_software_interrupt' 이 수행되어, 엉뚱하게도 소프트웨어 인터럽트 서비스 루틴이 수행된다. 이 점을 항상 염두해두기 바란다.

인터럽트 서비스 루틴

여기서는 'undefined_instruction' 인터럽트가 발생했을 때, VPOS 에서 어떤 인터럽트 서비스 루틴이 수행되는지 설명하도록 하겠다.
'undefined_instruction' 인터럽트가 발생하면 자동으로 '0x4' 로 점프한다.

(0x4)	b       vh_undefined_instruction    // 0xC4    ---- ① vpos_bootloader.S
...
...
...
(0xC4)   vh_undefined_instruction:	 ldr	pc, vh_remapped_undefined_instruction   // 0x158    ---- ② vpos_bootloader.S
...
...
...
(0x30000004)	b	vk_undef_handler 	// 0x30001F0C    ---- ③ HAL_arch_startup.S
...
...
...
(0x30001F0C)        
void vk_undef_handler(void)    ---- ④ Exception_handler.c
{
	printk("Undefined instruction exception.\n");
	while(1);
}

1 ~ 4 의 순서가 인터럽트가 처리되는 서비스 루틴이다. 여기서는 'undefined_instruction' 인터럽트를 예로 들었지만, 다른 인터럽트도 마찬가지 방식으로 수행된다.

스케줄링 순서

VPOS_start() 에 의해서 가장 처음 스케줄러가 호출되지만, 그 이후로는 타이머 인터럽트에 의해서 스케줄러가 동작하게 된다.
각각의 쓰레드들을 우선순위에 맞는 ready_queue 에 저장하고 나면, 스케줄러는 우선순위가 높은 순서대로 ready_queue 를 검색한다. 가장 높은 우선순위를 가지면서도, head_list 에 있는 쓰레드를 current_thread 로 저장한다.
가장 처음 스케줄러가 호출된 것이라면, 현재의 쓰레드를 저장하고(save_thread) 하고, 계속 수행한다. 곧 이어, 타이머 인터럽트에 의해서 스케줄러가 동작하면, 다시 ready_queue 에서 검색하고, 현재의 쓰레드와 우선순위를 비교하고, 같지 않으면 컨텍스트 스위칭을 한다.
여기서는 타이머 인터럽트가 발생하는 시점부터 쓰레드가 컨텍스트 스위칭을 하는 시점까지를 설명할 것이다.

인터럽트가 발생하면, irq 모드로 전환된다. 또한, 지정되어진 주소(0x18) 로 점프한다. 또한 스택 포인터(sp)가 0x30900000 로 설정된다.

(0x18)	b       vh_fiq     // 0xD8
...
...
...
(0xD8)   vh_fiq:	ldr	pc, vh_remapped_fiq   // ldr  pc, 0x16C
...
...
...
(0x30000018)	b	vh_irq	 // 0x30000108
...
...
...
(0x30000108)   vh_irq:
vh_entering_interrupt:
	sub	lr, lr, #4           // r14(lr) 에서 0x4 를 빼서 저장
	str	sp, =vk_save_irq_mode_stack_ptr     //  0x30000228 주소에 현재의 sp 값(0x30900000)을 집어 넣는다 
	stmfd	sp!, {r0-r14}^       //  현재 sp 의 값에서 r0 ~ r14 을 0x308FFFFC ~ 0x308FFFC4 에 저장한다(실제로는 r0 ~ r12)
	mrs	r0, spsr_all         //  spsr 을 r0 에 저장
	stmfd	sp!, {r0, lr}        //  r0 와 r14 의 값을 각각 sp 영역(0x308FFFBC, 0x308FFFC0)에 저장한다
	str	sp, =vk_save_irq_current_tcb_bottom      // 0x3000022C 에 현재의 sp 값(0x308FFFBC)을 집어 넣는다 
	bl	vh_hwi_classifier    // 0x3000025E8  ; vh_hwi_classifier 로 점프

이제 어셈레벨에서 C 레벨로 옮겨왔다. 아직 irq 모드이다.

void vh_hwi_classifier(void)
{ 
	vk_dd_table[vk_idt_table[vh_rINTOFFSET]].fop_list->vk_interrupt(); 
                 // vh_rINTOFFSET = 0x4a000014 로서 s3c2410 user manual 을 보면(p358), 인터럽트의 소스(source)를 찾을 수 있는 레지스터라고 나와있다
}

현재 VPOS 에서는 vk_dd_table 에 총 3개의 디바이스를 등록시킨다. timer, rtc, lcd 가 그것이다. 위의 함수는 vk_dd_table 에 등록된, 6 가지의 오퍼레이션 중에서 인터럽트 핸들러 함수를 실행하고 있다.
여기서는 타이머 인터럽트이므로 vh_timer_interrupt_handler 가 호출된다.

int vh_timer_interrupt_handler(void)
{
	vh_rSRCPND = vh_BIT_TIMER;       
	vh_rINTPND = vh_BIT_TIMER;
 
	++(vk_current_thread->cpu_tick);   // 초기값은 vk_current_thread->cpu_tick = 0
	if(vk_sched_lock==0) vk_scheduler();   // 스케줄러 호출
	return 0;
}

마지막에 스케줄러를 호출하고, 결국 vk_scheduler() 를 호출한다. 원래의 스케줄러의 루틴을 수행하고, vk_thread_switch_ctx 함수를 호출한다.

함수 이름대로 컨텍스트 스위칭을 하는 역할을 한다. 아직까지도 여전히 irq 모드이다.

void vk_thread_switch_ctx(vk_thread_t* from_thread, vk_thread_t* to_thread) 
{                           // from_thread : 지금까지 수행한 쓰레드, to_thread : 이제 수행할 쓰레드
        unsigned bottom;
        unsigned dispatcher_arg;
 
	if(from_thread->state == RUNNING) from_thread->state = READY;   // from_thread->state = 1 로 설정
	bottom = (unsigned)(from_thread->tcb_bottom);    // 저장된 쓰레드의 tcb_bottom 값(0x30127FB4) 값 저장
        vh_save_thread_ctx(bottom);   // 함수 호출
        to_thread->state = RUNNING;   // 현재 수행중인 쓰레드의 상태를 0 을 설정
 
	dispatcher_arg = (unsigned)(to_thread->tcb_bottom);  // 현재 수행중인 쓰레드의 tcb_bottom 값(0x30127FB4) 값 저장
	vk_scheduler_unlock();          // 스케줄러를 풀어준다
        vh_restore_thread_ctx(dispatcher_arg);     // 현재 쓰레드를 저장한다
}

다음은 어셈블리어로 구현되어 있는 vh_save_thread_ctx 함수이다. 저장된 쓰레드의 tcb_bottom 값을 인자로 받았다. 참고로 여전히 irq 모드다.

vh_save_thread_ctx:
	mrs	r1, cpsr_all    // cpsr 의 값을 r1 에 저장
	bic	r1, r1, #0xfffffff0    // r1 에 2 저장
        mov	r2, #0x2     // r2 에 2 저장
	cmps     r1, r2      // r1 과 r2 를 비교, 결과가 참이면, ldreq 구문이 실행되고, 거짓이면, ldrne 구문이 실행된다(참고로 지금은 참)
        ldreq	r2, =vk_save_irq_current_tcb_bottom    // 0x3000022C 주소의 데이터 값(0x308FFFBC)이 r2(0x2) 와 같으면, 저장한다 
	ldrne	r2, =vk_save_swi_current_tcb_bottom    // 0x30000224 주소의 데이터 값(0x308FFFBC)이 다르면, r2 에 저장한다
	.rept	17          // 17 번을 반복해서 루프를 돈다, 여기부터
	ldr	r1, [r2], #4  // r2 가 가리키는 0x308FFFBC 에 저장되어 있는 값 0x10 을 r1 에 저장하고, r2 는 0x308FFFBC 에 4 를 더해, r2 에는 0x308FFFC0 가 들어간다
	str	r1, [r0], #4  // r0 가 가리키는 0x30127FB4 주소에 r1 의 값(0x10) 을 저장하고, 4 를 더해, r0 에 0x30127FB8 이 들어간다
	.endr                     // 여기까지  
	mov	pc, lr	  // lr = 0x30000A0B8

위의 루프는 총 17번 동안, 0x308FFFFC 부터의 메모리 영역과 0x30127FF4 부터의 메모리 영역간에 데이터 복사를 한다.

앞에서 호출한 vh_restore_thread_ctx() 는 HAL_arch_startup.S 파일에 구현되어 있다.

vh_restore_thread_ctx:
	mrs	r1, cpsr_all          // cpsr 의 값을 r1 에 저장
 
	bic	r1, r1, #0xfffffff0    // r1 에 3 저장
 
        mov	r2, #0x2             // r2 에 2 저장
 
	cmps     r1, r2             // r1, r2 비교
 
	mov	sp, r0             // sp 에 r0 의 값을 저장, sp 의 주소가 0x30127FB4 로 변경
 
	ldmfd	sp!, {r0, lr}      // r0 와 r14 레지스터에 sp, 즉 0x30127FB4 와 0x30127FB8 에 저장되어 있는 값을 저장한다
	msr	spsr_all, r0      // spsr 에 r0 의 값을 저장, spsr = 10
 
	ldmfd	sp!, {r0-r14}^    // sp 의 주소(0x30127FBC)에서 부터 저장된 값을 r0 ~ r14 까지 저장(실제적으로 r12 까지)
 
	ldreq	sp, =vk_save_irq_mode_stack_ptr        //  r13, 0x30000228, sp 가 0x30000228 와 같으면 (0x30000228 에는 0x3000020C 저장)
 
	ldrne	sp, =vk_save_swi_mode_stack_ptr        //  r13, 0x30000220, sp 가 0x30000220 가 아니면 (0x30000220 에는 0x30800000 저장)
 
	movs	pc, lr     //  lr 에는 앞에서 저장한 vector->func 값(0x300236B4)이 저장되어 있다, 함수 실행을 위해 점프, 또한 이때 svc -> user 모드로 전환된다

마지막에서 lr 이 가리키는 값은 쓰레드의 함수 주소이므로, 함수를 호출했을 때와 마찬가지로 pc 값이 옮겨진다. 수행중에 타이머 인터럽트가 발생하면, vk_scheduler() 가 호출되어, 앞에서 설명한 절차를 다시 실행하게 된다.

  • computer/rtcclab/vpos_분석_-_3.인터럽트.txt
  • Last modified: 3 years ago
  • by likewind