디버깅을 통해 배우는 리눅스 커널의 구조와 원리 1, 도서 공부하기 11 - _do_fork() 흐름 파악과 ftrace 메시지 추출
안녕하세요.
_do_fork() 함수를 알아보기 위한 사전 정보가 조금 필요에 _do_fork() 마무리하지 못해서 추가 부분을 정리해 봅니다.
1. 유저 레벨 프로세스 생성 시 _do_fork() 함수처리 흐름
유저 레벨 프로세서 생성할 때 흐름은 아래와 같이 fork() 함수 호출하면 glibc 통해 시스템콜을 발생하고 이에 대응하는 sys_clone() 함수 호출합니다. 이후 호출을 따라가면 _do_fork() 함수를 호출합니다.
1) sys_clone 함수
sys_clone() 함수 내부를 보면 return 값(PID)을 받기 위해 _do_fork() 함수를 호출합니다.
SYSCALL_DEFINE5 매크로와 함께 커널 소스 빌드하는 과정에서 sys_ 접두사가 붙여 sys_clone으로 심벌을 생성합니다.
rpi_kernel_src/linux $ vim kernel/fork.c
최근 커널 버전에서는 sys_fork와 sys_vfork를 호출하면 sys_clone() 함수를 하며, sys_clone()은 프로세스 생성에 필요한 다양한 플래그를 제공합니다. 이 플래그들을 조합하여 fork()와 vfork()의 동작을 완벽하게 재현할 수 있기 때문입니다.
2. 커널 프로세스 생성 시 _do_fork() 함수 흐름
커널 프로세스란, 시스템 콜 없이 커널 함수로 생성되어 커널 공간에서만 실행되는 프로세스입니다. 유저 프로세스와 같이 마지막 단에서는 _do_fork() 함수를 호출합니다.
커널 프로세스는 커널 스레드가 되고 커널 공간에서 시스템 리소스관리를 수행합니다.
커널 스레드가 생성되는 과정은 2단계로 kthreadd 프로세스에게 커널 프로세스 생성을 요청하는 것과 커널 프로세스 생성 단계로 나눠집니다.
커널 스레드는 대부분 부팅과정에서 커널 스레드를 생성하며, ps 명령어로 하면 나타나는 것 중 PPID가 2인 프로세스들이 있습니다.
모든 커널 프로세스가 부팅 시에만 생성되는 것이 아니라, 리눅스 드라이버에서 많은 워커를 워크큐에 큐잉할 경우, 커널에서 메모리가 부족하면 페이지를 확보하는 일을 하는 kswapd 스레드를 깨워 실행합니다. 즉, 커널 시스템이 더 많을 일을 해야 할 때 커널 스레드를 생성합니다.
3. 유저레벨 프로세스 실행 실습
ps 명령어로 프로세스 목록에서 grep으로 bash 이름의 프로세스를 확인합니다.
linux $ ps -ely | grep bash
터미널을 하나 더 열고 위와 같이 명령어를 실행하면, PID가 8512인 프로세스가 하나 더 생성된 것을 볼 수 있습니다. 또 하나 창을 열고 한번 더 실행하면 PID가 8633인 프로세스가 생성되는 것을 알 수 있습니다.
이것으로 새로운 프로그램을 실행하면 이에 해당하는 프로세스가 생성된다는 사실을 알 수 있습니다.
추가로 하나 더 실험해 보겠습니다. 새로 만든 2개의 터미널 창에 geany라는 에디터 프로그램을 실행합니다. 그리고 ps와 grep 명령어를 입력합니다.
PID가 8790, 8800 프로세스인 geany 프로세스가 생성되었습니다. 또 하나 PPID는 위에서 본 각 터미널의 PID로 되어 있는 것을 알 수 있습니다. 여기서 알 수 있는 것은 대부분의 유저 레벨 프로세스는 셀이나 다른 프로세스를 통해 시작하고, 스스로 실행하지 못한다는 것입니다.
linux $ ps -ely | grep geany
터미널, geany 프로그램 같은 프로그램을 실행하면 메모리에 로딩돼 동작하는 것이 프로세스이고, 유저 레벨에서 실행하는 프로세스는 유저의 액션에 의해 대부분 생성된다는 것입니다.
4. 유저 프로세스 실습 코드
1) 유저 프로세스 소스 코드
ftrace 실습을 위해서 리눅스 시스템 프로그램 만들어 프로세스를 생성합니다.
그러기 위해서 c언어로 간단한 소스 코드를 하나 작성합니다.
소스 코드를 위한 디렉터리를 하나 만들어 c파일 하나를 생성합니다.
rpi_kernel_src# mkdir app_src; cd app_src
rpi_kernel_src/app_src# vim 4_4_user_process.c
--- 4_4_user_process.c 내용
#include <stdio.h>
#include <unistd.h>
#define PROC_TIMES 500
#define SLEEP_DURATION 3 //second unit
int main()
{
int proc_times = 0;
for(proc_times=0; proc_times < PROC_TIMES;proc_times++)
{
printf("Raspberry Pi OS tracing \n");
sleep(SLEEP_DURATION);
}//for
return 0;
} //main
소스코드의 내용은, for 루프문에서 "Raspberry Pi OS tracing" 문자열을 출력하고 3초 동안 휴면 상태로 진입한 후 다시 깨는 작업을 500번 반복을 하는 것입니다.
sleep과 fork 같은 시스템 관리하는 함수를 직접 호출하면 저수준 프로그래밍이라고 하며, 이는 저수준 함수(API)를 사용했다고 합니다.
2) Makefile
소스코드를 좀 더 쉽게 컴파일하기 위해서 Makefile을 만듭니다. 출력 파일명과 소스코드 파일 명을 gcc에게 알려줍니다.
--- Makefile 내용
4_4_user_process: 4_4_user_process.c
gcc -o 4_4_user_process 4_4_user_process.c
3) 컴파일과 실행
Makefile을 작성했다면 컴파일하고, 에러가 없다면 프로그램을 실행합니다.
입력한 문자가 3초마다 출력됩니다.
rpi_kernel_src/app_src# make
rpi_kernel_src/app_src# ./4_4_user_process
5. ftrace 설정
이전 글에서 사용했던 ftrace_setting.sh파일을 수정해서 사용합니다.
파일 이름을 clone_process_debug_ftrace_setting.sh 바꿔 놓습니다.
핵심적으로 수정한 부분은 콜 스택에 출력할 함수를 설정하는 것입니다.
echo sys_clone do_exit > /sys/kernel/debug/tracing/set_ftrace_filter
echo _do_fork copy_process* >> /sys/kernel/debug/tracing/set_ftrace_filter
sched_process_fork, sched_process_exit 이벤트를 활성화하고 이를 통해 프로세스가 종료하고 생성하는 동작을 추적합니다.
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_process_fork/enable
echo 1 > /sys/kernel/debug/tracing/events/sched/sched_process_exit/enable
6. 유저 프로세스와 ftrace 로그확보
위에서 작업한 ftrace 스크립트와 4_4_user_process파일 이용해서 프로세스에 분석에 필요한 로그를 만들어 보겠습니다.
두 개의 터미널 창이 필요합니다. 첫 번째 터미널에는 유저 프로세스 생성을 위해 만든 프로그램과 ftrace 실행합니다. 두 번째 터미널에 프로세스 확인하고 종료하는 하는 작업을 할 것입니다.
1) 첫 번째 터미널 작업
첫 번째 터미널 창에서 ftrace 설정 셀 스크립트를 실행하고 만든 프로그램을 실행합니다.
두 번째 터미널 의해 프로그램이 종료되면 ftrace를 종료합니다.
rpi_kernel_src# sh clone_process_debug_ftrace_setting.sh
rpi_kernel_src# ./app_src/4_4_user_process
rpi_kernel_src# sh get_ftrace.sh
2) 두 번째 터미널 작업
첫 번째 터미널에서 실행한 프로그램의 프로세스를 확인합니다. 몇 3~5초 기다린 다음에 kill 명령으로 프로세스를 종료합니다.
~ $ ps -ely | grep 4_4_user_proc
~ $ sudo kill -9 15752
3) ftrace 메시지 확인
get_ftrace.sh 파일을 수행한 디렉터리에 ftrace_log.c 파일이 생성된 것을 알 수 있습니다.
rpi_kernel_src# vim ftrace_log.c
이것으로 원하는 유저 프로세스의 ftrace 메시지를 가져오는 것까지 확인해 보았습니다.
책의 파일 내용 분석은 다음 글에서 보도록 하겠습니다.
감사합니다.
<참고 자료>
1. [도서] 디버깅을 통해 배우는 리눅스 커널의 구조와 원리 p143~p155, wikibook