ARM Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

201239
1625
172598


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

get_signal() 함수는 시그널 처리 과정의 핵심 역할이며 다음과 같은 처리를 합니다. 
펜딩된 시그널 정보 가져오기
ftrace 메세지 출력
시그널 핸들러 설정 유무 파악
스레드 그룹 종료

이어서 get_signal() 함수를 분석합시다. 
[https://github.com/raspberrypi/linux/blob/rpi-4.19.y/kernel/signal.c]
1 int get_signal(struct ksignal *ksig)
2 {
3 struct sighand_struct *sighand = current->sighand;
4 struct signal_struct *signal = current->signal;
5 int signr;
...
6 for (;;) {
7 struct k_sigaction *ka;
...
8 signr = dequeue_signal(current, &current->blocked, &ksig->info);
9
10 if (!signr)
11 break; /* will return 0 */
...
12 ka = &sighand->action[signr-1];
13
14 /* Trace actually delivered signals. */
15 trace_signal_deliver(signr, &ksig->info, ka);
16
17 if (ka->sa.sa_handler == SIG_IGN) /* Do nothing.  */
18 continue;
19 if (ka->sa.sa_handler != SIG_DFL) {
20 /* Run the handler.  */
21 ksig->ka = *ka;
22
23 if (ka->sa.sa_flags & SA_ONESHOT)
24 ka->sa.sa_handler = SIG_DFL;
25
26 break; /* will return non-zero "signr" value */
27 }
28
29 if (sig_kernel_ignore(signr)) /* Default is nothing. */
30 continue;
...
31 current->flags |= PF_SIGNALED;
...
32 do_group_exit(ksig->info.si_signo);
33 /* NOTREACHED */
34 }
35 spin_unlock_irq(&sighand->siglock);
36
37 ksig->sig = signr;
38 return ksig->sig > 0;
39 }

각 단계 별로 소스 코드를 분석하겠습니다.

펜딩된 시그널 정보 가져오기
먼저 8번째 줄 코드를 보겠습니다.
8 signr = dequeue_signal(current, &current->blocked, &ksig->info);
9
10 if (!signr)
11 break; /* will return 0 */

dequeue_signal() 함수를 실행해 다음과 같은 처리를 합니다.
 
    프로세스 태스크 디스크립터 구조체 struct task_struct 필드 중 pending 혹은 
   signal->shared_pending로 펜딩된 시그널 정보를 &ksig->info로 저장한다.

만약 시그널 번호가 0이면 10~11번째 줄 코드와 같이 break 문을 실행해 for 루프를 빠져 나옵니다. 

12번째 줄 코드를 보겠습니다.
12 ka = &sighand->action[signr-1];

시그널 타입별로 시그널 핸들링 정보가 있는 &sighand->action[] 배열에 접근해서 ka 지역 변수에 저장합니다.

누군가 여러분에게 다음과 같이 질문을 할 수 있습니다. 

    프로세스가 시그널 정보를 받아서 처리를 시작하는 코드 위치는 어디인가?

이 질문에 다음과 같이 대답을 하면 좋겠습니다.

     get_signal() 함수에서 dequeue_signal() 함수를 호출한 후 시그널 정보를 읽는 
     코드

필자가 자신있게 대답할 수 있는 이유는 다음 코드에 trace_signal_deliver() 함수가 있기 때문입니다.

ftrace 메세지 출력
다음은 ftrace 로그를 출력하는 코드입니다.
15 trace_signal_deliver(signr, &ksig->info, ka);

signal_deliver 이란 ftrace 이벤트를 켰을 때 실행하는 코드입니다.

signal_deliver 이벤트를 키면 다음과 같은 ftrace 로그를 확인할 수 있습니다.
signal_handle-12151 [001] d..1  6207.473891: signal_deliver: sig=2 errno=0 code=128 sa_handler=400398 sa_flags=10000000

시그널 핸들러 함수 주소와 전달하는 시그널 종류를 출력합니다. 


ftrace로 시그널을 디버깅하는 방법은 12.8절에서 자세히 다룹니다.


시그널 핸들러 설정 유무 파악
19~26번째 줄 코드는 시그널 핸들러를 지정했으면 실행하는 코드입니다.
19 if (ka->sa.sa_handler != SIG_DFL) {
20 /* Run the handler.  */
21 ksig->ka = *ka;
22
23 if (ka->sa.sa_flags & SA_ONESHOT)
24 ka->sa.sa_handler = SIG_DFL;
25
26 break; /* will return non-zero "signr" value */
27 }
..
35 spin_unlock_irq(&sighand->siglock);
36
37 ksig->sig = signr;
38 return ksig->sig > 0;

21번째 줄 코드를 보면 시그널 정보를 ksig->sig 저장합니다. 다음 26번째 줄 코드를 실행해서 for(;;) 루프에서 빠져나와 37~38번째 줄 코드를 실행해서 1을 반환합니다. 일반적으로 시그널 번호는 0보다 크니 1을 반환하게 됩니다.


그렇다면 유저 프로세스에서 시그널 핸들러를 등록했다면 이를 어느 함수에서 처리할까요? get_signal() 함수 다음에 실행하는 handle_signal() 함수입니다. 유저 어플리케이션에서 시그널 별로 설정한 시그널 핸들러를 실행하기 위한 동작은 handle_signal() 함수에서 살펴보겠습니다.


시그널 핸들러를 지정하지 않았을 경우 실행하는 31번째 줄 코드를 보겠습니다.
31 current->flags |= PF_SIGNALED;
...
32 do_group_exit(ksig->info.si_signo);
33 /* NOTREACHED */

실행 중인 프로세스 태스크 디스크립터 flags 필드에 PF_SIGNALED 플래그를 저장합니다. 
“|=” 연산자를 썼으니 이미 저장된 flags 필드 값은 그대로 유지합니다.

스레드 그룹 종료
32번째 줄 코드를 보면 do_group_exit() 함수를 호출해 다음 동작을 수행합니다. 

    실행 중인 프로세스와 프로세스가 속한 스레드 그룹 내 다른 프로세스들을 종료시킨다.

코드 분석으로 대부분 시그널 처리 결과는 프로세스 종료란 사실을 알 수 있습니다.

리눅스 커널에서는 유저 프로세스 관점으로 생성된 스레드도 프로세스로 간주합니다. 그래서 커널 입장에서 위와 같이 표현한 것입니다. 유저 프로세스 관점으로는 해당 스레드와 스레그 그룹에 속한 스레드들을 종료한다라고 보면 됩니다.

33번째 줄 코드를 보면 “NOTREACHED” 이란 주석문을 볼 수 있습니다. do_group_exit() 함수는 해당 프로세스와 스레드 그룹에 속한 다른 프로세스도 종료했으니 33번째 줄 코드는 다시 실행될 수 없습니다.

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

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


핑백

덧글

댓글 입력 영역