Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

230224
1178
109352


[라즈베리파이] 라즈비안(Raspbian) 리눅스 커널(LinuxKernel) 빌드하기 2. Raspberry pi configuration

이번 절에서는 라즈베리파이에서 라즈비안 리눅스 커널 소스 코드를 내려받고 빌드하는 방법을 알아보겠습니다. 

필자는 라즈베리파이를 쓸 때 다음 명령어로 root로 변경해 커널 빌드를 합니다.
$ sudo su

명령어 입력 과정에서 불필요한 권한 설정을 피하기 위해서입니다.

라즈비안 커널 소스 코드 내려받기

리눅스 커널 소스 코드를 다운로드 받는 방법을 소개합니다.

다음 명령어를 입력하면 라즈비안 최신 커널 소스를 내려 받을 수 있습니다.
git clone --depth=3000 https://github.com/raspberrypi/linux

위 명령어를 라즈베리파이 터미널에서 입력하면 다음 화면을 볼 수 있습니다.
root@raspberrypi:/home/pi/RPi_kernel_src# git clone --depth=3000 https://github.com/raspberrypi/linux
Cloning into 'linux'...
remote: Enumerating objects: 85646, done.
remote: Counting objects: 100% (85646/85646), done.
remote: Compressing objects: 100% (65774/65774), done.
Receiving objects:  33% (28569/85646), 41.45 MiB | 1.22 MiB/s    

소스 코드를 내려 받는데 약 10분 정도가 걸립니다.

커널 소스를 다 내려받고 나서 브랜치를 확인하니 rpi-4.19.y입니다.
root@raspberrypi:/home/pi/RPi_kernel_src# git branch
* rpi-4.19.y

linux 폴더에 생성되는 .git/config 파일을 열어보면 기본으로 rpi-4.19.y 브랜치를 Fetch합니다.
root@raspberrypi:/home/pi/RPi_kernel_src/linux# vi .git/config
.git/config
[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
[remote "origin"]
    url = https://github.com/raspberrypi/linux
    fetch = +refs/heads/rpi-4.19.y:refs/remotes/origin/rpi-4.19.y
[branch "rpi-4.19.y"]
    remote = origin
    merge = refs/heads/rpi-4.19.y

이렇게 git clone 명령어를 써서 라즈비안 리눅스 커널 소스 코드를 내려 받으면 최신 버전 브랜치를 선택합니다.

리눅스 커널은 git 유틸리티 프로그램을 써서 소스 코드를 관리합니다. 브랜치는 git에서 자주 쓰는 개념인데 프로젝트 소스 코드의 큰 줄기라고 볼 수 있습니다. 라즈비안은 리눅스 커널 버전에 따라 각기 다른 브랜치를 운영합니다.

이 블로그에서 리눅스 커널 4.19.30 버전 기준으로 빌드와 테스트를 했습니다. 따라서 이 블로그에서 다루는 리눅스 커널 버전에 맞춰서 커널 코드를 내려 받기를 권장합니다. 이를 위해 rpi-4.19.y 브랜치를 선택해야 합니다.

이번에는 브랜치를 rpi-4.19.y로 지정해 라즈비안 리눅스 커널 코드를 다운로드하는 방식을 소개합니다.
git clone --depth=1000 --branch rpi-4.19.y https://github.com/raspberrypi/linux

root@raspberrypi:/home/pi/RPi_kernel_src# git clone --depth=1000 --branch rpi-4.19.y https://github.com/raspberrypi/linux
Cloning into 'linux'...
remote: Enumerating objects: 122739, done.
remote: Counting objects: 100% (122739/122739), done.
remote: Compressing objects: 100% (74357/74357), done.
remote: Total 122739 (delta 54284), reused 72109 (delta 47307), pack-reused 0
Receiving objects: 100% (122739/122739), 185.97 MiB | 1.17 MiB/s, done.
Resolving deltas: 100% (54284/54284), done.
Checking connectivity... done.
Checking out files: 100% (61885/61885), done.

독자 분들은 가급적이면 본 도서에서 입력한 소스 코드 버전인 4.19에 맞춰서 라즈비안 리눅스 커널 소스 코드를 다운로드하길 희망합니다. 시간이 흘러 2021년이 되면 라즈비안 기본 브랜치는 아마 5.4 로 설정될 수 있습니다. 즉, git clone 명령어로 브랜치 이름을 설정하지 않으면 최신 브랜치 소스 코드를 다운로드하게 됩니다.

리눅스 커널 4.19는 LTS(Long Term Relelase) 버전입니다. 리눅스 커널 커뮤니티에서 업데이트된 버그 패치를 꾸준히 반영하면서 관리하는 것이 LTS입니다.

라즈비안 리눅스 커널 빌드하기

라즈비안 리눅스 커널 소스 코드를 내려 받았으니 이제 소스를 빌드하는 방법을 알아볼 차례입니다.

다음 라즈베리파이 홈페이지에 가면 커널 빌드 방법을 확인할 수 있습니다.
[https://www.raspberrypi.org/documentation/linux/kernel/building.md]
Raspberry Pi 2, Pi 3, Pi 3+, and Compute Module 3 default build configuration
cd linux
KERNEL=kernel7
make bcm2709_defconfig

Build and install the kernel, modules, and Device Tree blobs; this step takes a long time:
make -j4 zImage modules dtbs
sudo make modules_install
sudo cp arch/arm/boot/dts/*.dtb /boot/
sudo cp arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
sudo cp arch/arm/boot/dts/overlays/README /boot/overlays/
sudo cp arch/arm/boot/zImage /boot/$KERNEL.img

위 방식으로 빌드 명령어를 터미널에서 입력할 수 있습니다.  
커널 빌드를 할 때마다 위와 같은 명령어를 입력하면 어떨까요? 시간이 오래 걸립니다.
그래서 위와 같은 커널 빌드 명령어를 모아 파일을 하나 생성하며 커널 빌드를 할 때 실행합니다. 이를 빌드 스크립트라고 부르며 대부분 임베디드 리눅스 개발에서 활용합니다.

다음은 라즈베리파이에서 커널 빌드를 할 수 있는 빌드 스크립트 코드입니다.
01 #!/bin/sh
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_build_log.txt"
11 
12 echo "move kernel source"
13 cd linux
14
15 echo "make defconfig"
16 make O=$OUTPUT bcm2709_defconfig
17
18 echo "kernel build"
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

위 코드를 입력한 다음 build_RPi_kernel.sh 파일로 저장합시다. 참고로 가장 왼쪽에 있는 번호는 입력하면 안됩니다.

파일을 저장한 다음 아래 "chmod +x" 명령어를 입력해 파일에 실행 권한을 부여합시다.
root@raspberrypi:/home/pi/RPi_kernel_src# chmod +x build_RPi_kernel.sh

빌드 스크립트 코드에 대해서 조금 더 살펴볼까요?
05 KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
06 OUTPUT="$KERNEL_TOP_PATH/out"
07 echo "$OUTPUT"

05 번째 줄은 현재 실행 디렉토리를 KERNEL_TOP_PATH 에 저장합니다. 난해해 보이는 이 코드는 셸 스크립트 명령어입니다. 

06 번째 줄은 KERNEL_TOP_PATH 경로에 out 폴더 추가해 OUTPUT 매크로에 저장합니다. 
OUTPUT=root@raspberrypi:/home/pi/RPi_kernel_src

05~06 번째 줄 코드를 실행하면 OUTPUT 폴더를 위와 같이 지정할 수 있습니다.

OUTPUT은 16 번째와 19번째 줄과 같이 커널 컨피그와 커널 빌드 명령어에 "O=$OUTPUT" 형식으로 추가합니다. 
16 make O=$OUTPUT bcm2709_defconfig
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

커널을 빌드하면 추출되는 오브젝트 파일을 out 폴더에 생성하겠다는 의도입니다.

다음 커널 컨피그 파일을 생성하는 코드를 소개합니다.
16 make O=$OUTPUT bcm2709_defconfig

위 명령어는 다음 경로에 있는 bcm2709_defconfig 파일에 선언된 컨피그 파일을 참고해 .config 파일을 생성합니다.
root@raspberrypi:/home/pi/RPi_kernel_src/linux/arch/arm/configs/bcm2709_defconfig

make 옵션으로 "O=$OUTPUT" 을 추가하니 다음과 같이 out 폴더에 .config가 생성됩니다.
root@raspberrypi:/home/pi/RPi_kernel_src/out/.config

다음 19 번째 줄입니다.
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

리눅스 커널 소스 코드를 빌드하는 명령어입니다.

다음은 커널 빌드 로그를 저장하는 코드를 함께 보겠습니다.
10 BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG

10 번째 줄은 $KERNEL_TOP_PATH 커널 코드 디렉토리에 rpi_build_log.txt 파일을 지정해 BUILD_LOG에 저장합니다. 19 번째 줄 커널 빌드 명령어입니다. 가장 오른쪽에 "2>&1 | tee $BUILD_LOG" 코드를 추가됐습니다.

커널 빌드 시 출력하는 메시지를 $BUILD_LOG 파일에 저장하는 동작입니다.
커널 빌드 도중 컴파일 에러가 발생하면 rpi_build_log.txt 파일을 열어 어디서 문제가 생겼는지 확인합니다.

실전 개발에서 리눅스 커널 빌드 도중 문제가 발생하면 이 방식으로 커널 빌드 로그를 다른 개발자에게 전달합니다. 커널 로그에 커널 빌드 옵션 등을 볼 수 있기 때문입니다.

빌드 스크립트 코드를 설명드렸으니 이번에는 빌드 스크립트를 실행하는 방법을 소개합니다.

먼저 라즈비안 커널 소스 코드로 이동합니다. 소스 코드에 이동한 상태는 다음과 같습니다.
root@raspberrypi:/home/pi/RPi_kernel_src# ls
build_RPi_kernel.sh linux  

다음 "./build_RPi_kernel.sh" 명령어를 입력해 커널 빌드를 시작합시다. 
root@raspberrypi:/home/pi/RPi_kernel_src# ./build_RPi_kernel.sh
configure build output path
make[1]: Entering directory ' /home/pi/RPi_kernel_src/out'
  GEN     ./Makefile
  HOSTCC  scripts/basic/fixdep
  HOSTCC  scripts/kconfig/conf.o
  SHIPPED scripts/kconfig/zconf.tab.c
  SHIPPED scripts/kconfig/zconf.lex.c
  HOSTCC  scripts/kconfig/zconf.tab.o
  HOSTLD  scripts/kconfig/conf
...
  CC [M]  net/bridge/netfilter/ebt_nflog.o
  AR      net/bluetooth/bnep/built-in.o
  CC [M]  net/bluetooth/bnep/core.o
  CC [M]  fs/dlm/recover.o
  CC [M]  sound/soc/codecs/cs42xx8-i2c.o
  AR      drivers/i2c/busses/built-in.o
  CC [M]  drivers/i2c/busses/i2c-bcm2708.o
  CC [M]  fs/dlm/recoverd.o
...
  LD [M]  sound/soc/snd-soc-core.ko
  LD [M]  sound/usb/6fire/snd-usb-6fire.ko
  LD [M]  sound/usb/caiaq/snd-usb-caiaq.ko
  LD [M]  sound/usb/hiface/snd-usb-hiface.ko
  LD [M]  sound/usb/misc/snd-ua101.ko
  LD [M]  sound/usb/snd-usb-audio.ko
  LD [M]  sound/usb/snd-usbmidi-lib.ko
make[1]: Leaving directory '/home/pi/RPi_kernel_src/out'

만약 컴파일 에러가 발생하면 다음과 같은 에러 메시지와 함께 빌드가 중단됩니다.
root@raspberrypi:/home/pi/RPi_kernel_src# ./build_RPi_kernel.sh
configure build output path
make[1]: Entering directory '/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/generated/compile.h
  CC      kernel/sched/core.o
/home/pi/RPi_kernel_src/linux/kernel/sched/core.c:3302:6: error: #error "invoke compile error inside __schdedule"
     #error "invoke compile error inside __schdedule"
      ^
/home/pi/RPi_kernel_src/linux/scripts/Makefile.build:328: recipe for target 'kernel/sched/core.o' failed
make[3]: *** [kernel/sched/core.o] Error 1
/home/pi/RPi_kernel_src/linux/scripts/Makefile.build:587: recipe for target 'kernel/sched' failed
make[2]: *** [kernel/sched] Error 2
/home/pi/RPi_kernel_src/linux/Makefile:1040: recipe for target 'kernel' failed
make[1]: *** [kernel] Error 2
make[1]: *** Waiting for unfinished jobs....
make[1]: Leaving directory '/home/pi/RPi_kernel_src/out'
Makefile:146: recipe for target 'sub-make' failed
make: *** [sub-make] Error 2

사실 위 컴파일 에러는 다음 코드를 일부러 작성해 발생시킨 것입니다.
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index 4e89ed8..5c46f29 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -3299,7 +3299,7 @@ static void __sched notrace __schedule(bool preempt)
        struct rq_flags rf;
        struct rq *rq;
        int cpu;
-
+    #error "invoke compile error inside __schdedule"
        cpu = smp_processor_id();
        rq = cpu_rq(cpu);
        prev = rq->curr;

#error는 gcc 컴파일러가 제공하는 매크로인데 코드를 컴파일 할 때 무조건 컴파일 에러를 유발합니다.

이렇게 컴파일 에러를 만나면 반드시 리눅스 커널 코드를 수정한 다음 다시 빌드를 해야 합니다. 만약 컴파일 에러를 제대로 수정하지 않고 커널 이미지를 설치하면 수정한 코드가 제대로 동작하지 않습니다.

라즈비안 리눅스 커널 설치하기

커널 코드를 빌드만 해서는 수정한 코드가 라즈베리파이에서 실행하지 않습니다.
컴파일해 생성된 이미지를 라즈베리파이에 설치해야 합니다.

리눅스 커널 코드를 빌드했으니 이제 빌드한 커널 이미지를 설치해봅시다.

다음 코드는 라즈비안 이미지를 라즈베리파이에 설치하는 셸 스크립트입니다.
[install_RPi_kernel_img.sh]
01 #!/bin/sh
02
03 KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
04 OUTPUT="$KERNEL_TOP_PATH/out"
05 echo "$OUTPUT"
06
07 cd linux
08
09 make O=$OUTPUT modules_install
10 cp $OUTPUT/arch/arm/boot/dts/*.dtb /boot/
11 cp $OUTPUT/arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
12 cp $OUTPUT/arch/arm/boot/dts/overlays/README /boot/overlays/
13 cp $OUTPUT/arch/arm/boot/zImage /boot/kernel7.img

위와 같은 코드를 입력 후 다음 명령어로 install_RPi_kernel_img.sh 셸 스크립트를 실행합시다.
root@raspberrypi:/home/pi/RPi_kernel_src# ./install_RPi_kernel_img.sh

install_RPi_kernel_img.sh 스크립트를 실행하기 전에 먼저 커널 빌드 에러 메시지를 반드시 수정해야 합니다. 이 과정을 빼 먹고 커널 이미지를 설치하면 수정한 커널 이미지가 제대로 설치되지 않습니다. 

"이 포스팅이 유익하다고 생각되시면 댓글로 응원해주시면 감사하겠습니다.  
그리고 혹시 궁금점이 있으면 댓글로 질문 남겨주세요. 상세한 답글 올려드리겠습니다!"

덧글

  • 컴친구 2019/10/15 16:57 # 삭제 답글

    커널 설정이나 빌드할때 "make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig, make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs -j4" 처럼
    ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- 를 넣어줘야 하는데 셀 스크립터 작성시에는 어떻게 넣어줘야 할까요
  • AustinKim 2019/10/21 09:07 #

    아래는 라즈비안을 빌드할 때 shell script 코드입니다. 참고하세요.
    #!/bin/bash

    export PATH=$PATH:~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
    KERNEL=kernel7

    echo "configure build output path"
    TOP_PATH=$( cd "$(dirname "$0")" ; pwd )
    OUTPUT="$TOP_PATH/out"

    BUILD_LOG="$TOP_PATH/rpi_build_log.txt"

    rpi_build_start_time=`date +%s`

    OUTPUT_PATH=$( cd "$(dirname "$0")" ; pwd )
    OUTPUT="$OUTPUT_PATH/out"

    pushd linux > /dev/null

    # make config
    make ARCH=arm O=$OUTPUT CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig -j16 2>&1

    # build kernel source
    make ARCH=arm O=$OUTPUT CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs -j16 2>&1 | tee $BUILD_LOG
  • AustinKim 2019/10/21 09:07 # 답글

    아래는 라즈비안을 빌드할 때 shell script 코드입니다. 참고하세요.
    #!/bin/bash

    export PATH=$PATH:~/tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
    KERNEL=kernel7

    echo "configure build output path"
    TOP_PATH=$( cd "$(dirname "$0")" ; pwd )
    OUTPUT="$TOP_PATH/out"

    BUILD_LOG="$TOP_PATH/rpi_build_log.txt"

    rpi_build_start_time=`date +%s`

    OUTPUT_PATH=$( cd "$(dirname "$0")" ; pwd )
    OUTPUT="$OUTPUT_PATH/out"

    pushd linux > /dev/null

    # make config
    make ARCH=arm O=$OUTPUT CROSS_COMPILE=arm-linux-gnueabihf- bcm2709_defconfig -j16 2>&1

    # build kernel source
    make ARCH=arm O=$OUTPUT CROSS_COMPILE=arm-linux-gnueabihf- zImage modules dtbs -j16 2>&1 | tee $BUILD_LOG
댓글 입력 영역