ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

199239
1625
172596


[리눅스커널] 시그널 전달: do_work_pending()/do_signal() 함수 분석 12. 시그널

이번 소절에서는 다음 함수 분석으로 프로세스가 자신에게 전달된 시그널을 처리하는 과정을 살펴보겠습니다.
do_work_pending()
do_signal()

do_work_pending() 함수 분석
do_work_pending() 함수 코드는 다음과 같습니다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/kernel/signal.c]
1 asmlinkage int
2 do_work_pending(struct pt_regs *regs, unsigned int thread_flags, int syscall)
3 {
4 trace_hardirqs_off();
5 do {
6 if (likely(thread_flags & _TIF_NEED_RESCHED)) {
7 schedule();
8 } else {
9 if (unlikely(!user_mode(regs)))
10 return 0;
11 local_irq_enable();
12 if (thread_flags & _TIF_SIGPENDING) {
13 int restart = do_signal(regs, syscall);

12번째 줄 코드를 보겠습니다.
다시 한번 thread_flags 인자와 _TIF_SIGPENDING 플래그를 AND 비트 연산을 합니다. 결과가 true면 do_signal() 함수를 호출합니다. 이렇게 프로세스가 시그널을 받을 때 가장 먼저 실행하는 함수는 do_signal() 입니다.

do_signal() 함수 분석
do_signal() 함수 코드를 읽기 전에 이 함수에 전달하는 인자를 점검합시다.
struct pt_regs *regs: 프로세스 최하단 스택 공간에 푸시한 유저 프로세스 레지스터 세트
int syscall: 시스템 콜 테이블 주소

만약 시스템 콜 핸들러는 실행한 후 ret_fast_syscall 레이블로 복귀해 do_signal() 함수를 실행합니다. 이 때 syscall인자는 시스템 콜 테이블 주소를 저장하고 있습니다.

다음은 do_signal() 함수 구현부입니다.
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/arch/arm/kernel/signal.c]
1 static int do_signal(struct pt_regs *regs, int syscall)
2 {
3 unsigned int retval = 0, continue_addr = 0, restart_addr = 0;
4 struct ksignal ksig;
5 int restart = 0;
6
7 if (syscall) {
8 continue_addr = regs->ARM_pc;
9 restart_addr = continue_addr - (thumb_mode(regs) ? 2 : 4);
10 retval = regs->ARM_r0;
11
12 switch (retval) {
13 case -ERESTART_RESTARTBLOCK:
14 restart -= 2;
15 case -ERESTARTNOHAND:
16 case -ERESTARTSYS:
17 case -ERESTARTNOINTR:
18 restart++;
19 regs->ARM_r0 = regs->ARM_ORIG_r0;
20 regs->ARM_pc = restart_addr;
21 break;
22 }
23 }
24
25 if (get_signal(&ksig)) {
26 /* handler */
27 if (unlikely(restart) && regs->ARM_pc == restart_addr) {
28 if (retval == -ERESTARTNOHAND ||
29     retval == -ERESTART_RESTARTBLOCK
30     || (retval == -ERESTARTSYS
31 && !(ksig.ka.sa.sa_flags & SA_RESTART))) {
32 regs->ARM_r0 = -EINTR;
33 regs->ARM_pc = continue_addr;
34 }
35 }
36 handle_signal(&ksig, regs);
37 } else {
38 /* no handler */
39 restore_saved_sigmask();
40 if (unlikely(restart) && regs->ARM_pc == restart_addr) {
41 regs->ARM_pc = continue_addr;
42 return restart;
43 }
44 }
45 return 0;
46}

먼저 7번째 줄 코드를 보겠습니다.
7 if (syscall) {
8 continue_addr = regs->ARM_pc;
9 restart_addr = continue_addr - (thumb_mode(regs) ? 2 : 4);
10 retval = regs->ARM_r0;
11
12 switch (retval) {
13 case -ERESTART_RESTARTBLOCK:
14 restart -= 2;
15 case -ERESTARTNOHAND:
16 case -ERESTARTSYS:
17 case -ERESTARTNOINTR:
18 restart++;
19 regs->ARM_r0 = regs->ARM_ORIG_r0;
20 regs->ARM_pc = restart_addr;
21 break;
22 }
23 }

유저 공간에서 실행 중인 레지스터 중 ARM_pc 레지스터를 로딩해서 continue_addr 변수에 저장한 후, restart_addr 지역변수에 다시 저장합니다. 유저 공간에서 실행 중인 프로그램 카운터의 주소 정보는 ARM_pc 필드에 저장돼 있습니다. 이 주소에서 ARM 모드에 따라 2 혹은 4를 빼서 유저 공간에서 다시 실행할 프로그램 카운터 주소를 보정합니다. 

이는 시스템 콜 재실행과 연관된 루틴인데 시그널 관점으로 한 가지 예를 들겠습니다.
커널 내부에서 프로세스가 실행하는 도중 signal_pending() 함수를 사용해 프로세스에게 시그널이 전송이 됐는지 점검합니다. 만약 시그널이 전달이 됐다면 ‘-ERESTARTSYS’ 매크로를 반환해 함수의 실행을 종료하게 되며 결국 do_signal() 함수가 호출됩니다. 그런데 do_signal() 함수 내부에서 시그널을 받는 동작을 처리한 후 프로세스는 시그널이 자신에 전송이 됐는지 체크하는 루틴으로 다시 복귀를 해야 합니다. 이를 위해 시스템 콜을 재실행(Restart)하는 동작을 수행하는 것입니다.

원래 리눅스 커널의 오리지널 소스에는 7번째 줄 윗 부분에 다음과 같은 주석이 있었습니다.
* If we were from a system call, check for system call restarting...

더 자세한 내용은 이 책의 범위를 벗어나니 더 많은 내용을 알고 싶으신 분은 아래 링크(리눅스 커널 뉴스레터, 저자의 블로그)를 참고하세요.

https://lwn.net/Articles/17744/
http://rousalome.egloos.com/10009481

25~46번째 줄 코드를 보겠습니다. 
25 if (get_signal(&ksig)) {
26 /* handler */
27 if (unlikely(restart) && regs->ARM_pc == restart_addr) {
28 if (retval == -ERESTARTNOHAND ||
29     retval == -ERESTART_RESTARTBLOCK
30     || (retval == -ERESTARTSYS
31 && !(ksig.ka.sa.sa_flags & SA_RESTART))) {
32 regs->ARM_r0 = -EINTR;
33 regs->ARM_pc = continue_addr;
34 }
35 }
36 handle_signal(&ksig, regs);
37 } else {
38 /* no handler */
39 restore_saved_sigmask();
40 if (unlikely(restart) && regs->ARM_pc == restart_addr) {
41 regs->ARM_pc = continue_addr;
42 return restart;
43 }
44 }
45 return 0;
46}

이 코드 실행 흐름은 다음 2가지로 나눌 수 있습니다. 
25번째 줄 코드에서 호출되는 get_signal() 함수 반환값에 따라 실행 흐름이 나뉩니다. 
27~36번째 줄: 시그널 핸들러를 지정했을 경우
37~44번째 줄: 시그널 핸들러를 지정하지 않았을 경우

따라서 시그널 처리 흐름을 나누는 get_signal() 함수를 분석하는 것이 중요합니다.

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

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



핑백

덧글

댓글 입력 영역