Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

97258
1323
114599


[리눅스커널][시그널] 커널에서 시그널을 전달하는 진입점은 어디일까? 12. Signal

시그널 전달 진입점

시그널 생성 과정에서 스케줄러에게 시그널을 받을 프로세스 실행 요청을 했습니다.

리눅스 커널은 언제 시그널을 타겟 프로세스에게 전달할까요?
출발점은 두 가지 실행 흐름입니다.
1> 시스템 콜 핸들러 실행 마무리 후 ret_fast_syscall 레이블
2> 인터럽트 핸들링 후 __irq_usr 레이블

이 동작은 리눅스 커널보다 ARM 프로세스가 많은 관여를 합니다.

ret_fast_syscall 레이블 분석

유저 프로세스가 시스템 콜 핸들러 실행을 마치면 ret_fast_syscall 레이블로 복귀합니다.

ret_fast_syscall와 slow_work_pending 레이블에서 프로세스에게 시그널이 전달됐는지 점검합니다.

해당 코드 분석을 시작하겠습니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/arch/arm/kernel/entry-common.S]
1 ret_fast_syscall:
2 str r0, [sp, #S_R0 + S_OFF]! @ save returned r0
3 disable_irq_notrace @ disable interrupts
4 ldr r2, [tsk, #TI_ADDR_LIMIT]
5 cmp r2, #TASK_SIZE
6 blne addr_limit_check_failed
7 ldr r1, [tsk, #TI_FLAGS] @ re-check for syscall tracing
8 tst r1, #_TIF_SYSCALL_WORK | _TIF_WORK_MASK
9 beq no_work_pending
10 ENDPROC(ret_fast_syscall)
11
12 tst r1, #_TIF_SYSCALL_WORK
13 bne __sys_trace_return_nosave
14 slow_work_pending:
15 mov r0, sp @ 'regs'
16 mov r2, why @ 'syscall'
17 bl do_work_pending

먼저 7~8번째 줄 코드를 보겠습니다.
7 ldr r1, [tsk, #TI_FLAGS] @ re-check for syscall tracing
8 tst r1, #_TIF_SYSCALL_WORK | _TIF_WORK_MASK

프로세스 최상단 주소에 있는 struct thread_info 구조체 flags 멤버를 읽어서 _TIF_WORK_MASK 매크로가 설정됐는지 점검합니다.

_TIF_WORK_MASK 매크로는 다음과 같이 정의되어 있는데 아래 4개 매크로를 OR 비트 연산한 결과입니다.
#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \
 _TIF_NOTIFY_RESUME | _TIF_UPROBE)

이 중에 _TIF_SIGPENDING 매크로가 struct thread_info 구조체 flags 멤버에 설정되어 있는지 체크하는 것입니다.


_TIF_SIGPENDING 매크로는 struct thread_info 구조체 flags 멤버에 언제 저장할까요?

시그널을 생성하면 시그널을 받을 프로세스 struct thread_info 구조체 flags 멤버에 _TIF_SIGPENDING 매크로를 설정합니다.

complete_signal() 함수에서 시그널을 생성한 후 signal_wake_up() 함수를 거처 
signal_wake_up_state() 함수에서 시그널을 전달 받을 프로세스 struct thread_info flags에 _TIF_SIGPENDING 매크로를 설정하는 것입니다.
[https://elixir.bootlin.com/linux/v4.19.30/source/kernel/signal.c]
1 void signal_wake_up_state(struct task_struct *t, unsigned int state)
2 {
3 set_tsk_thread_flag(t, TIF_SIGPENDING);

위 함수 3번째 줄 코드에서 set_tsk_thread_flag() 함수 호출로 설정하는 것입니다. 

만약 struct thread_info 구조체 flags 멤버에 _TIF_SIGPENDING 매크로가 설정되어 있지 않으면 no_work_pending 레이블을 실행한 다음 유저 공간으로 복귀합니다.

만약 struct thread_info 구조체 flags 멤버에 _TIF_SIGPENDING 매크로가 저장됐으면 어떤 코드를 실행할까요? slow_work_pending 레이블을 실행한 다음 do_work_pending() 함수로 브랜치합니다.
14 slow_work_pending:
15 mov r0, sp @ 'regs'
16 mov r2, why @ 'syscall'
17 bl do_work_pending

시스템 콜 핸들러 실행 마무리 후 ret_fast_syscall 레이블을 실행하는 동작은 시스템 콜 챕터를 참고하세요.

do_work_pending() 함수에서 get_signal() 함수를 호출한 후 시그널에 대한 상세 처리 루틴을 실행합니다. 

다음 소절에서는 인터럽트가 발생했을 때 시그널을 처리하는 흐름에 대해 배워 보겠습니다.

인터럽트 벡터

유저 공간에서 코드를 실행 중 인터럽트가 발생하면 __irq_usr 레이블을 실행합니다. __irq_usr 레이블에서 인터럽트 처리를 마친 후 프로세스는 전달된 시그널이 있는지 점검합니다.

유저 공간에서 코드 실행 중 인터럽트가 발생했을 때 __irq_usr 이란 인터럽트 벡터를 실행합니다.

이 동작은 다음과 같은 ftrace 로그로 확인할 수 있습니다.
1  chromium-browse-1011  [000] d.h.   953.149220: irq_handler_entry: irq=92 name=mmc1
2  chromium-browse-1011  [000] d.h.   953.149222: _raw_spin_lock+0x10/0x54 <-bcm2835_mmc_irq+0x24/0x754
3  chromium-browse-1011  [000] d.h.   953.149233: <stack trace>
4  => handle_irq_event_percpu+0x2c/0x68
5  => handle_irq_event+0x54/0x78
6  => handle_level_irq+0xb4/0x160
7  => generic_handle_irq+0x34/0x44
8  => bcm2836_chained_handle_irq+0x38/0x50
9  => generic_handle_irq+0x34/0x44
10 => __handle_domain_irq+0x6c/0xc4
11 => bcm2836_arm_irqchip_handle_irq+0xac/0xb0
12 => __irq_usr+0x4c/0x60
13 => 0x76f9fed8

chromium-browse(pid: 1011) 프로세스 실행 도중 92번 mmc1 인터럽트가 발생했을 때 콜스택입니다.
12번 줄 로그를 보면 __irq_usr 레이블을 시작으로 인터럽트에 대한 처리를 시작하며 인터럽트 핸들러 함수인 bcm2835_mmc_irq() 함수는 2번째 줄 로그에서 볼 수 있습니다.

코드 분석으로 이 동작을 살펴보겠습니다. 분석할 어셈블리 코드는 다음과 같습니다.
1 __irq_usr:
2 usr_entry
3 kuser_cmpxchg_check
4 irq_handler
5 get_thread_info tsk
6 mov why, #0
7 b ret_to_user_from_irq
8 UNWIND(.fnend )
9 ENDPROC(__irq_usr)
10
11 ENTRY(ret_to_user_from_irq)
12 ldr r2, [tsk, #TI_ADDR_LIMIT]
13 cmp r2, #TASK_SIZE
14 blne addr_limit_check_failed
15 ldr r1, [tsk, #TI_FLAGS]
16 tst r1, #_TIF_WORK_MASK
17 bne slow_work_pending
18 slow_work_pending:
19 mov r0, sp @ 'regs'
20 mov r2, why @ 'syscall'
21 bl do_work_pending

먼저 1번째 줄 코드를 봅시다.
1 __irq_usr:
2 usr_entry
3 kuser_cmpxchg_check
4 irq_handler
5 get_thread_info tsk
6 mov why, #0
7 b ret_to_user_from_irq

1~4번째 줄 코드에서 인터럽트 핸들링을 실행합니다. 이후 7번째 줄 코드와 같이 ret_to_user_from_irq 레이블로 브랜치합니다.

유저 모드에서 인터럽트가 발생하면 인터럽트 핸들링 후 ret_to_user_from_irq 레이블을 실행합니다. 여기서 중요한 점은 tsk(r9) 레지스터에 프로세스 스택 최상단 주소를 저장한 후 ret_to_user_from_irq 레이블로 브랜치합니다.

다음 ret_to_user_from_irq 레이블 코드를 보겠습니다.
11 ENTRY(ret_to_user_from_irq)
12 ldr r2, [tsk, #TI_ADDR_LIMIT]
13 cmp r2, #TASK_SIZE
14 blne addr_limit_check_failed
15 ldr r1, [tsk, #TI_FLAGS]
16 tst r1, #_TIF_WORK_MASK
17 bne slow_work_pending

프로세스 최상단 주소를 갖고 있는 tsk(r9) 레지스터에 접근해서 struct thread_info 구조체 flags 멤버에 저장된 값을 읽습니다. 이 값이 _TIF_WORK_MASK 매크로로 설정됐는지 점검합니다.

이번에도 ret_fast_syscall 레이블에서와 같이 flags 멤버에 _TIF_WORK_MASK 매크로가 설정됐는지 확인합니다.
#define _TIF_WORK_MASK (_TIF_NEED_RESCHED | _TIF_SIGPENDING | \
 _TIF_NOTIFY_RESUME | _TIF_UPROBE)

struct thread_info flags 멤버가 _TIF_WORK_MASK 매크로에 선언된 매크로 중하나이면 slow_work_pending 레이블로 브랜치합니다. 시그널 처리 관점으로 보면 struct thread_info flags 멤버가 _TIF_SIGPENDING로 설정됐으면 slow_work_pending 레이블로 브랜치합니다. 

slow_work_pending 레이블 코드를 봅시다. 
18 slow_work_pending:
19 mov r0, sp @ 'regs'
20 mov r2, why @ 'syscall'
21 bl do_work_pending

r0 레지스터에 스택 주소, r2에는 시스템 콜 번호를 저장한 후 do_work_pending() 함수로 브랜치합니다.

여기까지 시그널을 생성하면 언제 해당 프로세스가 시그널 처리를 시작하는지 알아 봤습니다.
시스템 콜과 인터럽트 핸들링이 끝나고 프로세스가 시그널을 받았는지 점검하는 것입니다.

이전 절에서 다룬 바와 같이 시그널을 생성하면 스케줄러에 해당 프로세스 실행 요청을 했습니다.
ftrace 로그를 보면 스케줄러 큐에 실행을 기다리는 프로세스가 많을 경우를 제외하고 프로세스 실행 요청 후 0.01 밀리 초 내에 프로세스가 실행을 시작합니다.


유저 레벨 프로세스가 실행을 하면 크게 2가지로 흐름으로 실행할 것입니다.
1> 시스템 콜을 발생
2> 유저 공간에서 어떤 코드 실행

#Referene 시그널
시그널이란
시그널 설정은 어떻게 할까
시그널 생성 과정 함수 분석
프로세스는 언제 시그널을 받을까
시그널 전달과 처리는 어떻게 할까?
시그널 제어 suspend() 함수 분석 
시그널 ftrace 디버깅

"혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답글 올려드리겠습니다!" 



핑백

덧글

댓글 입력 영역