Linux Kernel(4.19) Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

791223
1016
122607


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

이번에는 라즈비안 리눅스 커널 소스코드를 내려받고 빌드 및 설치하는 과정을 다룹니다. 이 내용은 아래 라즈베리 파이 커뮤니티에서 소개한 내용을 참고했습니다.

https://www.raspberrypi.org/documentation/linux/kernel/building.md


리눅스 유틸리티 프로그램을 설치하기

라즈비안 리눅스 커널 소스코드를 내려받기에 앞서 리눅스 유틸리티 프로그램을 설치해야 합니다. 이를 위해 다음 명령어를 입력합니다.

$ apt-get install git bc bison flex libssl-dev

이 책에서 사용한 라즈베리 파이에서 이 명령어를 입력했을 때 나오는 화면은 다음과 같습니다. 

 
그림 2.39 리눅스 필수 유틸리티 프로그램 설치

유틸리티 프로그램을 설치하는 데 약 1분 정도 시간이 걸립니다.

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

이어서 리눅스 커널 소스코드를 다운로드하는 방법을 소개합니다. 다음 명령어를 입력하면 라즈비안 최신 커널 소스를 내려받을 수 있습니다.

$ git clone --depth=1 https://github.com/raspberrypi/linux

이 명령어를 라즈베리 파이 터미널에서 입력한 후 출력되는 화면은 다음과 같습니다.

root@raspberrypi:/home/pi/rpi_kernel_src# git clone --depth=1 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 브랜치로 설정돼 있습니다. 

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을 통해 소스코드를 관리합니다. 브랜치(branch)는 git에서 자주 쓰는 개념으로, 프로젝트 소스코드의 큰 줄기라고 볼 수 있습니다. 라즈비안은 리눅스 커널 버전에 따라 각기 다른 브랜치를 운영합니다.


이 책에서는 리눅스 커널 4.19.60 버전을 기준으로 빌드 및 테스트를 마쳤습니다. 이 책에서 다루는 리눅스 커널 버전에 맞춰 커널 코드를 내려받기를 권장합니다. 이를 위해 rpi-4.19.y 브랜치를 선택해야 합니다.

다음은 브랜치를 rpi-4.19.y로 지정해 라즈비안 리눅스 커널 코드를 다운로드하는 명령어입니다.

$ git clone --depth=1 --branch rpi-4.19.y https://github.com/raspberrypi/linux

root@raspberrypi:/home/pi/rpi_kernel_src# git clone --depth=1 --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년이 되면 라즈비안 기본 브랜치가 아마 새로운 버전으로 설정될 수 있습니다. 즉, git clone 명령어로 브랜치 이름을 지정하지 않으면 최신 브랜치 소스코드를 다운로드하게 됩니다.

라즈비안 리눅스 커널 빌드

라즈비안의 커널 소스를 내려받았으니 이제 소스를 빌드하는 방법을 알아볼 차례입니다. 참고로 아래의 라즈베리 파이 홈페이지에 가면 커널을 빌드하는 방법을 확인할 수 있습니다.

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

이어서 그 아래의 ‘Building’ 부분에 나온 아래 명령어를 통해 본격적인 커널 빌드를 진행합니다.

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/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_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 명령어를 입력해 파일에 실행 권한을 부여합니다.
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이라는 셸 스크립트 변수에 저장합니다.

05~06 번째 줄 코드를 실행하면 OUTPUT 변수는 다음과 같이 '/home/pi/rpi_kernel_src/out'으로 변경됩니다.

OUTPUT=/home/pi/rpi_kernel_src/out

여기서 설정한 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번째 줄은 커널 컨피그 파일을 생성하는 코드입니다.

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

사실 이 에러는 다음과 같이 컴파일 에러를 일으키는 코드(#error)를 일부러 작성해서 발생시킨 것입니다.

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/bash
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
댓글 입력 영역