본문 바로가기

리눅스 커널의 구조와 원리/2. 라즈베리 파이 설정

[라즈베리파이] 라즈비안(Raspbian) 리눅스 커널(LinuxKernel) 전처리 파일 생성하기

이번 절에서는 리눅스 커널을 빌드하는 과정에서 전처리 코드를 생성하는 방법을 소개합니다.
 
리눅스 커널 소스코드를 분석하다 보면 수많은 매크로를 만납니다. 리눅스 커널을 캡슐화나 다형성과 같은 객체지향 방식으로 구현하다 보니 매크로로 구현된 코드가 많습니다. 그런데 이 매크로가 소스 분석의 걸림돌 중 하나로 작용합니다. 
 
전처리 코드는 이러한 매크로를 모두 풀어서 표현합니다. 따라서 훨씬 편하게 소스코드를 분석할 수 있으며, 리눅스 커널 코드를 분석할 때는 전처리 코드를 함께 보시기를 바랍니다.
 
전처리 코드는 GCC 컴파일 오브젝트를 생성하는 과정에서 추출됩니다. 커널에서 전처리 코드를 추출하는 방법은 크게 두 가지가 있습니다. 
 
전체 전처리 파일을 추출
특정 전처리 파일을 추출
 
전체 전처리 파일을 추출하는 방법
 
먼저 리눅스 커널의 전체 소스코드를 전처리 파일로 추출하는 방법은 다음과 같습니다.
 
01 diff --git a/Makefile b/Makefile
02 index 3da5790..0414cb2 100644
03  --- a/Makefile
04 +++ b/Makefile
05 @@ -419,6 +419,7 @@ KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
06            -fno-strict-aliasing -fno-common -fshort-wchar \
07            -Werror-implicit-function-declaration \
08            -Wno-format-security \
09 +          -save-temps=obj \
10            -std=gnu89
11  KBUILD_CPPFLAGS := -D__KERNEL__
12  KBUILD_AFLAGS_KERNEL :=
 
위 패치 코드에서 어떤 옵션이 전처리 코드를 추출하는 역할을 수행할까요? 바로 다음 09번째 줄의 '-save-temps=obj \' 구문입니다. 
 
09 +          -save-temps=obj \
 
패치 코드를 입력하는 방식의 이해를 돕기 위해 패치 코드를 반영하기 전 코드를 살펴보겠습니다.
 
Makefile
408 # Use LINUXINCLUDE when you must reference the include/ directory.
409 # Needed to be compatible with the O= option
410 LINUXINCLUDE    := \
411         -I$(srctree)/arch/$(hdr-arch)/include \
412         -I$(objtree)/arch/$(hdr-arch)/include/generated \
413         $(if $(KBUILD_SRC), -I$(srctree)/include) \
414         -I$(objtree)/include \
415         $(USERINCLUDE)
416
417 KBUILD_AFLAGS   := -D__ASSEMBLY__
418 KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
419            -fno-strict-aliasing -fno-common -fshort-wchar \
420            -Werror-implicit-function-declaration \
421            -Wno-format-security \
422            -std=gnu89
423 KBUILD_CPPFLAGS := -D__KERNEL__
424 KBUILD_AFLAGS_KERNEL :=
425 KBUILD_CFLAGS_KERNEL :=
426 KBUILD_AFLAGS_MODULE  := -DMODULE
 
위 Makefile 파일의 421번째 줄 다음에 다음 코드를 입력하면 됩니다.
 
-save-temps=obj \
 
-save-temps=obj \를 입력한 후의 Makefile은 다음과 같습니다. 
 
417 KBUILD_AFLAGS   := -D__ASSEMBLY__
418 KBUILD_CFLAGS   := -Wall -Wundef -Wstrict-prototypes -Wno-trigraphs \
419            -fno-strict-aliasing -fno-common -fshort-wchar \
420            -Werror-implicit-function-declaration \
421            -Wno-format-security \
422            -save-temps=obj \
423            -std=gnu89
424 KBUILD_CPPFLAGS := -D__KERNEL__
425 KBUILD_AFLAGS_KERNEL :=
426 KBUILD_CFLAGS_KERNEL :=
427 KBUILD_AFLAGS_MODULE  := -DMODULE
 
이 같은 방식으로 Makefile 을 수정한 다음, 이전 절에 소개한 build_rpi_kernel.sh 커널 빌드 스크립트를 실행합시다. 그럼 전처리 코드가 out 폴더에 생성됩니다. 전처리 코드가 어떻게 생성됐는지 확인해 보겠습니다.
 
먼저 커널 스케줄링과 관련된 공통 코드가 담긴 kernel/sched/core.c 파일에 대한 전처리 코드를 찾아보겠습니다.
 
root@raspberrypi:/home/pi/rpi_kernel_src/out/kernel/sched# ls -al
total 33692
...
-rw-r--r--  1 root home  613852 Mar 19 09:35 .tmp_completion.i
-rw-r--r--  1 root home 20953 Mar 19 09:35 .tmp_completion.s
-rw-r--r--  1 root home 2800883 Mar 19 09:57 .tmp_core.i
-rw-r--r--  1 root home 368699 Mar 19 09:57 .tmp_core.s
-rw-r--r--  1 root home 1262723 Mar 19 09:35 .tmp_cpuacct.i
-rw-r--r--  1 root home 17772 Mar 19 09:35 .tmp_cpuacct.s
 
C 언어로 작성된 리눅스 커널 소스 파일은 다음 위치에서 전처리 파일로 생성됩니다. 
 
linux/kernel/sched/core.c
out/kernel/sched/.tmp_core.i
 
소스 파일명 앞에 ".tmp_"란 접두사와 맨 끝에 “i”가 붙습니다.
 
그럼 다른 리눅스 커널 소스코드는 어떻게 전처리 파일로 생성될까요? linux/init 폴더에 있는 아래 파일을 예로 들겠습니다.
 
calibrate.c  do_mounts.c do_mounts_initrd.c
 
위 파일들은 전처리 과정에서 out/init 폴더에 다음과 같은 이름으로 생성됩니다.
 
.tmp_calibrate.i  .tmp_do_mounts.i  .tmp_do_mounts_initrd.i
 
root@raspberrypi:/home/pi/rpi_kernel_src/out/init# ls -al
total 12912
-rw-r--r--  1 312538 Mar 19 09:34 .tmp_calibrate.i
-rw-r--r--  1 10928 Mar 19 09:34 .tmp_calibrate.s
-rw-r--r--  1 2830878 Mar 19 09:34 .tmp_do_mounts.i
-rw-r--r--  1 1616189 Mar 19 09:34 .tmp_do_mounts_initrd.i
-rw-r--r--  1 7311 Mar 19 09:34 .tmp_do_mounts_initrd.s
-rw-r--r--  1 1621237 Mar 19 09:34 .tmp_do_mounts_rd.i
-rw-r--r--  1 14425 Mar 19 09:34 .tmp_do_mounts_rd.s
 
위와 같이 커널 Makefile을 수정해 모든 리눅스 커널 소스 파일을 전처리 코드가 담긴 *.i 파일로 변환할 수 있습니다. 문제는 "*.i"뿐만 아니라 "*.s" 파일로 생성돼 용량이 5GB까지 늘어난다는 점입니다. 
 
특정 처리 파일을 추출하는 방법
 
대부분 특정 커널 소스 파일의 전처리 파일을 열어 보고 싶을 때가 많을 것입니다. 이번에는 특정 소스 파일만 전처리 파일로 생성하는 방법을 알아보겠습니다. 먼저 다음과 같은 셸 스크립트 코드를 실행해야 합니다.
 
build_preprocess_RPi_kernel.sh
01 #!/bin/bash
02
03 echo "configure build output path"
04 
05 KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
06 OUTPUT="$KERNEL_TOP_PATH/out"
07 echo "$OUTPUT"
08 
09 KERNEL=kernel7
10 BUILD_LOG="$KERNEL_TOP_PATH/rpi_preproccess_build_log.txt"
11 
12 PREPROCESS_FILE=$1
13 echo "build preprocessed file: $PREPROCESS_FILE"
14
15 echo "move kernel source"
16 cd linux
17
18 echo "make defconfig"
19 make O=$OUTPUT bcm2709_defconfig
20
21 echo "kernel build"
22 make $PREPROCESS_FILE O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG
 
위와 같은 코드를 입력한 후 build_preprocess_rpi_kernel.sh라는 이름으로 저장합니다. 파일을 저장한 후 다음 명령어로 실행 권한을 줍니다.
 
root@raspberrypi:/home/pi/rpi_kernel_src# chmod +x build_preprocess_RPi_kernel.sh
 
앞에서 소개한 build_rpi_kernel.sh 빌드 스크립트에 세 줄 정도를 추가했는데, 아래의 12~13번째 줄은 셸 스크립트를 실행할 때 전달하는 소스코드의 이름입니다.
 
12 PREPROCESS_FILE=$1
13 echo "build preprocessed file: $PREPROCESS_FILE"
 
다음으로 22번째 줄의 코드를 보겠습니다.
 
22 make $PREPROCESS_FILE O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG
 
커널 코드를 빌드하는 명령어에 "$PREPROCESS_FILE"이라는 구문이 추가됐습니다. 이는 지정한 파일만 전처리 파일로 추출하라는 뜻입니다.
 
이번에는 build_preprocess_rpi_kernel.sh 셸 스크립트를 실행하는 방법을 알아보겠습니다. build_preprocess_rpi_kernel.sh 셸 스크립트를 실행할 때는 디렉터리를 포함한 파일 이름을 지정해야 합니다. 
 
build_preprocess_rpi_kernel.sh [파일이름.i]
 
예를 들어, linux/sched/core.c 파일을 전처리 코드로 추출하려면 다음 형식으로 셸 스크립트를 실행하면 됩니다.
 
build_preprocess_rpi_kernel.sh kernel/sched/core.i
 
이번에는 라즈베리 파이에서 다음 명령어로 실행해 봅시다.
 
root@raspberrypi:/home/pi/Rpi_kernel_src# build_preprocess_rpi_kernel.sh kernel/sched/core.i
configure build output path
build preprocessed file: kernel/sched/core.i
make[1]: Entering directory ' root@raspberrypi:/home/pi/rpi_kernel_src/out '
  GEN     ./Makefile
#
# configuration written to .config
#
make[1]: Leaving directory '/home/pi/rpi_kernel_src/out'
make[1]: Entering directory '/home/pi/rpi_kernel_src/out'
  GEN     ./Makefile
scripts/kconfig/conf  --silentoldconfig Kconfig
  CHK     include/config/kernel.release
  GEN     ./Makefile
  CHK     include/generated/uapi/linux/version.h
  Using  /home/pi/Rpi_kernel_src/linux as source for kernel
  CHK     include/generated/utsrelease.h
  CHK     scripts/mod/devicetable-offsets.h
  CHK     include/generated/timeconst.h
  CHK     include/generated/bounds.h
  CHK     include/generated/asm-offsets.h
  CALL    /home/pi/rpi_kernel_src/linux/scripts/checksyscalls.sh
  CHK     include/generated/compile.h
  CPP     kernel/sched/core.i
  GZIP    kernel/config_data.gz
  CHK     kernel/config_data.h
  Kernel: arch/arm/boot/Image is ready
  Building modules, stage 2.
  Kernel: arch/arm/boot/zImage is ready
  MODPOST 1506 modules
make[1]: Leaving directory '/home/pi/rpi_kernel_src/out'
 
전처리 파일을 문제 없이 생성하면 위와 같은 빌드 메시지를 볼 수 있습니다. out/kernel/sched 디렉터리로 가면 core.i 파일만 전처리 코드로 생성된 것을 확인할 수 있습니다. 
 
root@raspberrypi:/home/pi/rpi_kernel_src/out/kernel/sched# ls
autogroup.o  clock.o       core.i  cpuacct.o      cpufreq.o            cpupri.o
 
하지만 소스코드 디렉터리를 잘못 지정하면 다음과 같은 에러 메시지와 함께 빌드가 중단됩니다.
 
root@raspberrypi:/home/pi/rpi_kernel_src# ./build_preprocess_rpi_kernel.sh  sched/core.i
configure build output path
build preprocessed file: sched/core.i
make[1]: Entering directory '/home001/austin.kim/src/book_RPi_kernel/out'
  GEN     ./Makefile
#
# configuration written to .config
#
make[1]: Leaving directory '/home/pi/rpi_kernel_src/out'
make[1]: Entering directory '/home/pi/rpi_kernel_src/out'
  GEN     ./Makefile
scripts/kconfig/conf  --silentoldconfig Kconfig
make[1]: *** No rule to make target 'sched/core.i'.  Stop.
make[1]: Leaving directory '/home/pi/rpi_kernel_src/out'
Makefile:146: recipe for target 'sub-make' failed
make: *** [sub-make] Error 2
 
따라서 이 셸 스크립트를 실행할 때는 디렉터리와 소스 파일을 이름을 정확히 지정해야 합니다.
 
* 유튜브 강의 동영상도 있으니 같이 들으시면 더 많은 걸 배울 수 있습니다. 


 
 
 
 
 
라즈베리 파이 설정 
 
라즈베리 파이 설치하기
라즈베리 파이 기본 설정하기 
라즈비안 리눅스 커널 빌드
 
"혹시 궁금한 점이 있으면 댓글로 질문 남겨주세요. 아는 한 성실히 답변 올려드리겠습니다!" 
 
Thanks,
Austin Kim(austindh.kim@gmail.com)
 
 
# Reference: For more information on 'Linux Kernel';
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 1
 
디버깅을 통해 배우는 리눅스 커널의 구조와 원리. 2
 
 
Thanks,
Austin Kim