고급 개발자로 가는 길

Embedded/Linux Kernel

[Linux Kernel] 프로세스 소멸 과정 소스 분석

다크엔지니어 2022. 2. 16. 01:03
반응형

프로세스 종료 방식

프로세스 종료 방식은 크게 두 가지로 나뉜다.

 

1, 유저 프로세스 레벨에서의 Signal 종료

아래 글에서 Signal 방식의 종료에 대하여 ftrace 를 통해 분석하였다.

2022.02.07 - [Embedded/Linux Kernel] - [Linux Kernel] ftrace 를 통한 유저 프로세스 분석

 

[Linux Kernel] ftrace 를 통한 유저 레벨 프로세스 분석

먼저 유저 프로세스 생성을 위한 raspbian_test.c 파일 이다. #include #include #define PROC_TIMES 500 #define SLEEP_DURATION 3 // second unit int main() { int proc_times = 0; for(proc_times = 0; proc_t..

darkengineer.tistory.com

 

2. 유저 프로세스 POSIX exit 시스템 콜을 통한 스스로 종료

아래 글에서는 유저 프로세스가 POSIX exit 시스템 콜을 발생하였을 때, 종료 과정을 ftrace 를 통해 분석하였다.

2022.02.16 - [Embedded/Linux Kernel] - [Linux Kernel] ftrace 를 통한 유저 레벨 프로세스 분석(POSIX exit 종료 방식)

 

[Linux Kernel] ftrace 를 통한 유저 레벨 프로세스 분석(POSIX exit 종료 방식)

프로세스 종료 방식 프로세스 종료 방식은 크게 두 가지로 나뉜다. 1, 유저 레벨에서의 Signal 종료 아래 경로 글에서 Signal 방식의 종료에 대하여 ftrace 를 통해 분석하였다. 2022.02.07 - [Embedded/Linux Ke

darkengineer.tistory.com

 

 

프로세스 종료 소스 분석

먼저 프로세스 종료의 공통적인 함수로 do_exit 함수가 있는데, 이 세부 동작은 자세히 알 필요가 있다.

이유는, 실제 Linux 혹은 Custom OS 에서 개발 시 유저 레벨이나 커널 레벨에서 의도치 않게 프로세스

종료로 문제되는 경우가 있다.

 

do_exit() 함수 분석

do_exit 함수에 대해서는 자세히 알아보겠다. 먼저 ftrace 를 통해 종료 콜 스텍을 확인해 보자.

signal 프로세스 종료 단계의 함수 흐름 

do_exit+0x14/0xa90 <-do_group_exit+0x50/0xe0 45892 raspbian_proc-1440 [003] .... 3299.693248: <stack trace> 45893 => do_group_exit+0x50/0xe0 45894 => get_signal+0x17c/0xa34 45895 => do_work_pending+0x460/0x57c 45896 => slow_work_pending+0xc/0x20 45897 => 0xb6e58eac

slow_work_pending -> do_work_pending -> get_signal -> do_group_exit ->do_exit

 

exit 프로세스 종료 단계의 함수 흐름 

__wake_up_parent 를 통해 sys_exit_group 함수가 호출이 되고 do_group_exit 이후 do_exit 흐름으로 이어진다.

 

__noreturn 키워드는 실행 후 자신을 호출한 함수로 돌아가지 않는다는 것을 뜻한다.

이유는 간단하다. 커널 코드를 실행하는 주인공인 프로세스가 종료되어 당연히 이전 함수로 못돌아 가는 것이다.

do_exit 함수는 프로세스에 대한 리소스 정리 후 do_task_dead 함수를 호출하여 schedule 함수를 실행한다.

즉, 휴면 상태로 들어가게 된다. 이 의미는 do_exit 함수의 시퀀스를 끝까지 다 수행하지 않는다는 것이다.

그래서 __noreturn 키워드를 지정 한 것이다.

더 깊게 들어가면, 왜 프로세스 종료 후 이전 함수로 가지 못할까?

바로, 프로세스는 자신의 스택 메모리 공간을 해제 할 수 없기 때문이다. 프로세스를 소멸하는 동작인 

do_exit 함수를 스택 공간에서 실행한다.

그래서 schedule 을 통해 다음에 실행 될 프로세스에서 이전 프로세스를 종료시켜줘 ~ 라고 

체크를 해 놓는 것이다. 이를 위해 do_task_dead 와 schedule 까지 실행하는 이유이다.

 

실행 단계로는

1. init 프로세스가 종료하면 강제 커널 패닉을 유발로 보통 부팅 과정에서 발생함

2. 이미 프로세스가 do_exit 함수의 실행으로 프로세스가 종료되는 도중 do_exit 함수가 재호출되었는지 확인

3. 프로세스 리소스인 파일 디스크립터 , 가상메모리, 시그널 등을 해제

4. 부모 프로세스에게 자신이 종료됨을 알림

5. 프로세스 실행 상태를 task_struct 의 state 필드에 TASK_DEAD 로 변경

6. do_task_dead 호출하여 스케줄링 실행 그러면 안에서 schedule 이 실행된다.

  -> 그 결과 task_struct 와 스택 메모리를 해제 한다.

 

do_task_dead() 함수 분석

set_special_state 함수를 호출해 프로세스의 상태를 DEAD 로 설정 하고, NOFREEZE 로 flat 연산도 적용한다.

여기서 current 는 현재 프로세스의 task_struct 이다. __schedule 함수에 false 인자는 선점형 스케줄링을 실행 하지 않겠다는 의미이다.

 

schedule() 함수 분석

역시 스케줄링 역활이라 호출되는 함수량이 적지는 않다.

context_switch 함수도 보인다.

여기서 잠시 정리하면,

1. do_exit 함수에서 대부분 자신의 리소스를 커널에게 반납하고, 상태를 DEAD로 변경

2. 컨텍스트 스위칭을 실시

3. 스위칭으로 다음에 실행하는 프로세스는 finish_task_switch 함수에서 이전 DEAD 상태이면 여기서 스택 공간을 해제한다.

결국 프로세스 스케줄링이 진행되므로 당연 컨텍스트 스위칭이 일어나고 다음 프로세스가 대신 죽여준다

 

finish_task_switch() 함수 분석

함수의 부분으로 이전 프로세스의 DEAD 를 확인 후 stack 공간을 해제한다.

 

이렇게 프로세스 종료 과정을 소스를 통해 간략하게 나마 알아보았다.

이 내용은 유저 레벨과 커널 레벨에서의 exit or signal 방식의 공통 과정으로 내용은 같다.

결국 프로세스의 리소스를 해제 후 다음 프로세스에게 스케줄링 후 stack 해제는 다음 프로세스 역할인 것이다.

이게 바로 __noreturn 하는 이유 인 것 이다.

반응형