Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

110199
1107
135838


[라즈베리파이] 프로세스 - 스케줄링(Preemption): 커널 모드 인터럽트 발생 4. 프로세스(Process) 관리

이번에는 커널 모드에서 인터럽트가 발생했을때 스케줄링(Preemption)하는 동작을 살펴봅니다.

커널 모드에서 커널 프로세스가 실행 중 인터럽트가 발생하면 __irq_svc 이란 인터럽트 벡터로 PC를 바꿉니다.
__irq_svc 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/arch/arm/kernel/entry-armv.S]
1 __irq_svc:
2 svc_entry
3 irq_handler
4
5 #ifdef CONFIG_PREEMPT
6 ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
7 ldr r0, [tsk, #TI_FLAGS] @ get flags
8 teq r8, #0 @ if preempt count != 0
9 movne r0, #0 @ force flags to 0
10 tst r0, #_TIF_NEED_RESCHED
11 blne svc_preempt
#endif

6~7번째 줄 코드부터 봅시다.
6 ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
7 ldr r0, [tsk, #TI_FLAGS] @ get flags

프로세스 스택 주소로 struct thread_info 구조체에 접근해서
struct thread_info.preempt_count를 r8, struct thread_info.flags를 r0에 저장합니다.

이번에 8~9번째 줄 코드를 봅시다.
8 teq r8, #0 @ if preempt count != 0
9 movne r0, #0 @ force flags to 0

r8에는 struct thread_info.preempt_count 값이 저장돼 있습니다.
이 값이 0이 아니면 r0를 0으로 강제로 바꿔 버립니다.

만약 r8이 0이면 r0 레지스터는 값을 그대로 갖고 있습니다.

r8이 struct thread_info.preempt_count이고 r0이 struct thread_info.flags이라면 다음과 같이 쉽게 어셈블리 코드를 C 언어 형태로 바꿀 수 있습니다.
if (struct thread_info.preempt_count == 0 ) {
if (struct thread_info.flags == _TIF_NEED_RESCHED) {
 svc_preempt();
}
else {
thread_info_flags = 0;
}

이해를 돕게 위해 작성한 코드이니 정말 위와 같이 코드를 입력한 후 컴파일은 하지 맙시다.

struct thread_info.preempt_count 값이 0이어야 struct thread_info.flags 멤버가 _TIF_NEED_RESCHED(2) 인지 점검하는 겁니다.

svc_preempt 레이블 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/arch/arm/kernel/entry-armv.S]
1 svc_preempt:
2 mov r8, lr
3 1: bl preempt_schedule_irq @ irq en/disable is done inside

3번째 줄 코드와 같이 preempt_schedule_irq() 함수를 호출합니다.

preempt_schedule_irq() 함수 구현부 코드를 봅시다.
[https://elixir.bootlin.com/linux/v4.14.49/source/kernel/sched/core.c#L3596]
1 asmlinkage __visible void __sched preempt_schedule_irq(void)
2 {
3 enum ctx_state prev_state;
4
5 /* Catch callers which need to be fixed */
6 BUG_ON(preempt_count() || !irqs_disabled());
7
8 prev_state = exception_enter();
9
10 do {
11 preempt_disable();
12 local_irq_enable();
13 __schedule(true);
14 local_irq_disable();
15 sched_preempt_enable_no_resched();
16 } while (need_resched());

preempt_schedule_irq() 함수를 보면 13번째 줄 코드에서 __schedule() 함수를 호출합니다.

분석한 내용을 정리합시다.
1> 인터럽트 발생(커널 공간에서 실행 중인 프로세스)
2> 인터럽트 핸들러 실행
3> struct thread_info.preempt_count 값이 0인지 점검
  3.1> struct thread_info.preempt_count 값이 0이면,  struct thread_info.preempt_count 값 _TIF_NEED_RESCHED인지 점검
  3.2> struct thread_info.preempt_count 값이 _TIF_NEED_RESCHED 이면  svc_preempt 레이블 실행
4> svc_preempt 레이블에서 preempt_schedule_irq() 함수를 호출해서 __schedule() 함수 호출

이번에는 Trace32 프로그램으로 위에서 분석한 함수 흐름을 확인해봤습니다. __schedule() 함수에 브레이크 포인트를 걸고 정말 인터럽트 발생 후 스케줄링을 하는지 점검했습니다.
-000|__schedule()
-001|preempt_schedule_irq()
-002|svc_preempt(asm)
-003|__irq_svc(asm)
 -->|exception
-004|blk_flush_plug_list()
-005|current_thread_info(inline)
-005|blk_finish_plug()
-006|ext4_writepages()
-007|__filemap_fdatawrite_range()
-008|filemap_write_and_wait_range()
-009|ext4_sync_file()
-010|vfs_fsync()
-011|fdput(inline)
-011|do_fsync()
-012|ret_fast_syscall(asm)

004번째 콜스택을 보면 blk_flush_plug_list() 함수 실행 중이었습니다.
003번째 콜스택에 __irq_svc() 이란 레이블이 보이니 인터럽트가 발생했음을 알 수 있습니다.

이후 svc_preempt() -> preempt_schedule_irq() -> __schedule() 흐름으로 스케줄링을 하는 겁니다.


인터럽트가 발생했을때 스케줄링(Preemption) 하는 조건을 살펴봤습니다.

덧글

댓글 입력 영역