1. 리눅스 커널 소스 코드 다운로드
1.1 git clone
리눅스 커널 소스 코드를 다운로드 받는 방법을 소개합니다.
터미널에서 다음 명령어를 입력하면 라즈비안 최신 커널 소스를 내려 받을 수 있습니다.
(물론, git은 사전에 설치되어있어야 합니다.)
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
소스 코드를 내려 받는데 약 몇분 정도 소요됩니다.
커널 소스를 다 내려받고 나서 브랜치를 확인할 수 있습니다.
root@raspberrypi:/home/pi/RPi_kernel_src# git branch
* rpi-4.19.y
git clone 명령어를 사용하여 라즈비안 리눅스 커널 소스 코드를 내려 받으면 최신 버전 브랜치를 선택합니다.
리눅스 커널은 git 유틸리티 프로그램을 써서 소스 코드를 관리합니다. 라즈비안은 리눅스 커널 버전에 따라 각기 다른 브랜치를 운영합니다.
이번에는 브랜치를 rpi-4.19.y로 지정해 라즈비안 리눅스 커널 코드를 다운로드하는 방식을 소개합니다.
git clone --depth=1000 --branch rpi-4.19.y https://github.com/raspberrypi/linux
가급적이면 4.19 버전에 맞춰서 라즈비안 리눅스 커널 소스 코드를 다운로드하길 바랍니다.
git clone 명령어로 브랜치 이름을 설정하지 않으면 최신 브랜치 소스 코드를 다운로드하게 됩니다.
1.2 커널 버전
리눅스 커널 커뮤니티에서 업데이트된 버그 패치를 꾸준히 반영하면서 관리하는 것이 LTS(Long Term Relelase) 입니다.
2. 리눅스 커널 빌드
2.1 Build Configuration
라즈비안 리눅스 커널 소스 코드를 내려 받았으니 이제 소스를 빌드하는 방법을 알아볼 차례입니다.
다음 라즈베리파이 홈페이지에 가면 커널 빌드 방법을 확인할 수 있습니다.
[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
위 방식으로 빌드 명령어를 터미널에서 입력할 수 있습니다.
다만 일일이 명령어를 입력하긴 번거로우므로, 그래서 위와 같은 커널 빌드 명령어를 모아 파일을 하나 생성하며 커널 빌드를 할 때 실행합니다.
이를 빌드 스크립트라고 부르며 대부분 임베디드 리눅스 개발에서 활용합니다.
2.2 Build Script
다음은 라즈베리파이3B에서 커널 빌드를 할 수 있는 빌드 스크립트 코드입니다.
#!/bin/sh
echo "configure build output path"
KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
OUTPUT="$KERNEL_TOP_PATH/out"
echo "$OUTPUT"
KERNEL=kernel7
BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
echo "move kernel source"
cd linux
echo "make defconfig"
make O=$OUTPUT bcm2709_defconfig
echo "kernel build"
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
2.3 Kernel Build
이 글에서 다루는 셸 스크립트는 <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 2장에 소개된 것입니다. 이 스크립트가 제대로 동작하기 위해서는 책에 설명된 환경이 갖춰져 있어야 합니다.
여기서는 커널 빌드 과정이 아닌 커널 빌드를 위한 셸 스크립트에 어떤 내용이 담겨있는지를 분석하는 글을 참고하셔서 읽어주시기를 바랍니다. 먼저 분석하고자 하는 예제 스크립트를 살펴본 다음에 셸 스크립트 문법을 딱 필요한 만큼만 알아보겠습니다. 그리고서 예제 스크립트를 한 줄씩 해석해보도록 하겠습니다.
3. Kernel Build shell script
책에 소개된 셸 스크립트는 아래와 같습니다.
#!/bin/bash
echo "configure build output path"
KERNEL_TOP_PATH="$( cd "$( dirname "$0" )" ; pwd -P )"
OUTPUT="$KERNEL_TOP_PATH/out"
echo "$OUTPUT"
KERNEL=kernel7
BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
echo "move kernel source"
cd linux
echo "make defconfig"
make O=$OUTPUT bcm2709_defconfig
echo "kernel build"
make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG
3.1 셸 스크립트 문법
셸 스크립트는 다른 프로그래밍 언어와 같이 문법이 있습니다. 그러니 제대로 이해하려면 시간을 들여서 공부해야 합니다. 이 글에서는 위에 적힌 스크립트를 이해하기에 필요한 만큼만 살펴보도록 하겠습니다.
자세한 문법은 아래 홈페이지의 문서에서 알아봅시다.
3.2 셸 스크립트 시작
셸 스크립트 맨 첫 줄에는 #!/bin/bash 라고 적습니다. 이는 지금부터 셸 스크립트가 시작된다는 것을 알립니다.
#는 hash, !은 bang이라 읽고 두 기호의 연속된 시퀀스를 hash bang, shebang 등으로 읽습니다.
#! 기호 뒤의 경로는 셸 스크립트를 해석할 인터프리터 실행 경로로, 일반적인 셸 스크립트는 #!/bin/sh 라고만 적습니다.
하지만 bash 셸에 특화된 셸 스크립트라면 #!/bin/bash 라고 적어야 합니다. zsh 셸을 사용한다면 #!/bin/zsh라고 적으면 되겠지요.
3.3 출력
echo 명령은 뒤따라오는 값을 출력합니다. echo "configure build output path" 라고 쓰면 터미널에 configure build output path 라고 써지는 것이지요. 문자열은 쌍따온표(")로 묶어줘야 합니다.
3.4 변수
변수 이름은 영문, 숫자, 밑줄 기호(언더바)로 만들 수 있습니다. 변수는 대부분의 다른 프로그래밍 언어와 마찬가지로 대, 소문자를 구별합니다. 그리고 변수 이름에 숫자를 포함할 수 있지만 숫자로 시작할 수는 없습니다.
변수에 값을 넣을 때에는 = 기호를 사용하면 됩니다. 중요한 것은 = 기호 앞뒤에 공백이 있으면 안됩니다.
NAME = "Superman" # 이렇게 하면 안됨
NAME="Superman" # = 기호 앞뒤에 공백 없이 사용하기
변수에 들어있는 값을 꺼내 쓰기위해서는 변수 앞에 $ 기호를 붙여주면 됩니다. $ 기호를 사용하지 않으면 변수명이 그대로 출력되니 주의하세요.
echo $NAME # "Superman" 출력됨
echo NAME # NAME 출력됨
3.5 셸 스크립트 실행
셸 스크립트는 텍스트 파일이지만 이를 사용하려면 실행파일로 만들어야 합니다. 아래에 적힌 명령 한 줄은 test.sh 파일에 실행 파일 속성을 더하는 일을 합니다.
chmod +x test.sh
3.6 스크립트 분석
첫 줄부터 차근히 살펴보겠습니다.
01 #!/bin/bash : 지금부터 셸 스크립트가 시작됨을 알립니다. bash 셸이 해석하도록 선언하였습니다.
03 echo "configure build output path" : 터미널에 "configure build output path" 라고 출력합니다.
05 KERNEL_TOP_PATH="$( cd "$( dirname "$0" )" ; pwd -P )" : 이 스크립트에서 가장 어려운 부분입니다.
KERNEL_TOP_PATH라는 변수에 셸 스크립트가 위치한 절대 경로를 저장하고 있습니다. 큰따옴표가 총 3쌍이 있는데 가장 바깥쪽부터 분석해보겠습니다.
"$( cd "$( dirname "$0" )" ; pwd -P )" : 아직 뭔지 모르겠지만 이 값이 KERNEL_TOP_PATH에 저장됩니다.
큰따옴표를 한 겹 벗겨보겠습니다. 그러면 만나게 되는 것이 $() 로 둘러쌓인 덩어리네요. 인터프리터는 $() 로 감싸진 것을 하나의 값으로 해석합니다. 그럼 이것을 한 겹 더 벗겨봅니다.
cd "$( dirname "$0" )" ; pwd -P : bash나 zsh 같은 셸을 사용해보신 분들이라면 이제야 익숙한 표현들이 눈에 들어오실 겁니다. 현재 경로를 변경하는 cd 명령도 보이고 현재 경로를 출력하는 pwd 명령도 보이네요.
여기서는 한 줄로 표현되어 있지만 ; 기호를 기준으로 두 줄로 볼 수 있습니다.
cd "$( dirname "$0" )"
pwd -P
훨씬 보기 편하네요. 여기서 하는 일은 cd 명령으로 어디론가 경로를 바꾸고, pwd 명령으로 이동한 경로를 출력하는 일을 하고 있습니다.
pwd 명령 뒤에 붙은 -P 는 현재 경로를 절대경로로 표시하라는 옵션입니다. 자, 그럼 cd 명령으로 이동하려는 경로는 어디일까요?
큰따옴표로 묶인 내용을 꺼내 보겠습니다.또다시 $()로 묶인 덩어리가 나타났네요. 이것은 값이라고 해석하라는 뜻이라고 말씀드렸습니다. 한 번 더 내용을 꺼내 보겠습니다.
dirname "$0" : dirname은 명령어입니다.
경로에서 상위 디렉터리 이름을 뽑아내는 역할을 합니다.
예를 들어 아래처럼 dirname 명령 뒤에 경로를 입력하면,
dirname /home/jakupsil/test.sh
이런 값이 출력됩니다.
/home/jakupsil
참고로 dirname은 주어지는 경로값에서 상위 디렉터리 이름을 뽑아내기만 하지, 실제로 그 디렉터리가 있는지 확인하지는 않습니다.
그럼 마지막 남은 궁금증 하나를 풀어보겠습니다. dirname "$0"
여기서 "$0"가 과연 무엇인가가 궁금합니다. 이것은 셸 스크립트에서 사용하는 특수한 변수 중의 하나입니다. 셸 스크립트를 실행할 때 함께 입력되는 인수가 있을 텐데 그 인수를 $번호 형식으로 불러와 쓸 수 있습니다. 0번째 인수는 셸 스크립트의 경로를 포함한 파일명입니다.
예를 들어보죠.
./test.sh hello world
위처럼 셸 스크립트를 실행시켰다면 스크립트 내부에서 $0는 ./test.sh가 되고 $1은 hello, $2는 world가 됩니다. 이해되셨나요?
그럼 우리는 복잡해 보이는 스크립트 한 줄을 다음처럼 해석할 수 있게 되었습니다.
- $0에 담긴 이 스크립트 파일명과 경로를 가져온다.
- dirname명령으로 상위 디렉터리 이름을 뽑아낸다.
- cd 명령으로 현재 디렉터리를 입력된 경로로 옮긴다.
- pwd -P 명령으로 현재 디렉터리를 절대경로로 출력한다.
- 그 값을 KERNEL_TOP_PATH에 저장한다.
자 그럼 이어서 스크립트를 해석해보겠습니다.
06 OUTPUT="$KERNEL_TOP_PATH/out"
07 echo "$OUTPUT"
OUTPUT이라는 변수를 만들어 앞서 만든 KERNEL_TOP_PATH 변수 뒤에 /out이라는 문자열을 덧붙여 저장합니다. 그리고 확인을 위해 터미널에 OUTPUT 변수값을 출력합니다.
09 KERNEL=kernel7
KERNEL이라는 변수에 kernel7 이라는 값을 넣습니다.
10 BUILD_LOG="$KERNEL_TOP_PATH/rpi_build_log.txt"
앞서 만든 KERNEL_TOP_PATH에다가 /rpi_build_log.txt라는 문자열을 붙여서 BUILD_LOG라는 변수에 저장합니다.
12 echo "move kernel source"
13 cd linux
move kernel source라는 문자열을 터미널에 출력합니다. 그리고 현재 디렉터리에 아래에 있는 linux 디렉터리로 이동합니다.
15 echo "make defconfig"
16 make O=$OUTPUT bcm2709_defconfig
make defconfig라는 문자열을 터미널에 출력합니다. 이어서 make로 커널 빌드에 필요한 설정 파일를 만듭니다. 이때 앞서 만든 OUTPUT 변수에 저장된 값을 넘겨주고 있네요.
18 echo "kernel build"
kernel build라는 문자열을 터미널에 출력합니다.
19 make O=$OUTPUT zImage modules dtbs -j4 2>&1 | tee $BUILD_LOG
마지막으로 커널 빌드를 시작합니다. 이 한 줄에는 두 가지 명령이 있습니다. 하나는 커널을 빌드하기 위한 make입니다. 다른 하나는 표준 출력으로 출력되는 내용을 모니터에 표시하고 동시에 파일로 저장하는 tee 명령입니다.
이 줄을 조금 더 자세히 들여다보겠습니다. 2>&1라는 알 수 없는 문구가 있습니다. 이것의 의미는 make가 출력하는 표준 에러(2)를 표준 출력(1)으로 넘긴다는 의미입니다. 이것을 리다이렉션이라고 합니다.
make와 tee 명령 사이에 | 기호가 있습니다. 이것은 기호를 기준으로 왼쪽에 있는 명령이 내보내는 출력을 오른쪽 명령으로 전달한다는 의미입니다. 이것을 파이프라인이라고 합니다.
마지막으로 tee 명령은 넘겨받은 출력 내용을 모니터에 표시하고 BUILD_LOG 변수에 담긴 경로에 파일을 저장합니다.
위 내용을 요약하면 다음과 같습니다.
- make 명령으로 커널을 빌드한다.
- 빌드되는 동안 출력되는 표준 에러는 표준 출력으로 재지정한다.
- 빌드 과정에서 생기는 표준 출력을 | 기호 오른편으로 넘겨준다.
- tee 명령이 넘겨받은 표준 출력값을 모니터에 표시하고 $BUILD_LOG에 저장한다.
3.7 빌드 스크립트 분석
처음에 이 스크립트와 마주쳤을 때 어떻게 읽어야 하나 감을 잡지 못해 조금 당황했었습니다. 하지만 한 줄씩 뜯어보며 공부해보니 다행히도 몹시 어렵지는 않았습니다. 지금 이 글을 읽고 있는 독자분들도 같은 생각이었으면 좋겠습니다. 글을 마무리하기 전에 셸 스크립트를 공부하기에 좋을 링크 몇 개를 소개하겠습니다. 이 글에서는 셸 스크립트 문법을 아주 일부만 소개했기 때문에 더 공부하고 싶은 분들에게 도움이 될 거로 생각합니다.
4. 리눅스 커널 소스 코드 설치 (install)
커널 코드를 빌드만 해서는 수정한 코드가 라즈베리파이에서 실행하지 않습니다.
컴파일해 생성된 이미지를 라즈베리파이에 설치해야 합니다.
리눅스 커널 코드를 빌드했으니 이제 빌드한 커널 이미지를 설치해봅시다.
다음 코드는 라즈비안 이미지를 라즈베리파이3B (32bit)에 설치하는 셸 스크립트입니다.
[install_rpi_kernel_img.sh]
#!/bin/sh
KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
OUTPUT="$KERNEL_TOP_PATH/out"
echo "$OUTPUT"
cd linux
make O=$OUTPUT modules_install
cp $OUTPUT/arch/arm/boot/dts/*.dtb /boot/
cp $OUTPUT/arch/arm/boot/dts/overlays/*.dtb* /boot/overlays/
cp $OUTPUT/arch/arm/boot/dts/overlays/README /boot/overlays/
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
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 파일을 열어 어디서 문제가 생겼는지 확인합니다.
실전 개발에서 리눅스 커널 빌드 도중 문제가 발생하면 이 방식으로 커널 빌드 로그를 다른 개발자에게 전달합니다. 커널 로그에 커널 빌드 옵션 등을 볼 수 있기 때문입니다.
64bit (Raspberry Pi 5)
#!/bin/sh
KERNEL_TOP_PATH="$( cd "$(dirname "$0")" ; pwd -P )"
OUTPUT="$KERNEL_TOP_PATH/out"
echo "$OUTPUT"
KERNEL=kernel_2712
cd linux
make 0=$OUTPUT modules_install
sudo cp /boot/firmware/$KERNEL.img /boot/firmware/$KERNEL-backup.img
sudo cp $OUTPUT/arch/arm64/boot/Image /boot/firmware/$KERNEL.img
sudo cp $OUTPUT/arch/arm64/boot/dts/broadcom/*.dtb /boot/firmware/
sudo cp $OUTPUT/arch/arm64/boot/dts/overlays/*.dtb* /boot/firmware/overlays/
sudo cp $OUTPUT/arch/arm64/boot/dts/overlays/README /boot/firmware/overlays/
빌드 스크립트 코드를 설명드렸으니 이번에는 빌드 스크립트를 실행하는 방법을 소개합니다.
먼저 라즈비안 커널 소스 코드로 이동합니다. 소스 코드에 이동한 상태는 다음과 같습니다.
root@raspberrypi:/home/pi/rpi_kernel_src# ls
build_rpi_kernel.sh linux
다음 "./build_rpi_kernel.sh" 명령어를 입력해 커널 빌드를 시작합시다.
install_rpi_kernel_img.sh 스크립트를 실행하기 전에 먼저 커널 빌드 에러 메시지를 반드시 수정해야 합니다.
이 과정을 빼 먹고 커널 이미지를 설치하면 수정한 커널 이미지가 제대로 설치되지 않습니다.
출처 : Austin Kim님 블로그
'SoC : : Architecture > : : Raspberry' 카테고리의 다른 글
(2) 디버깅과 코드 학습 능력 (0) | 2024.10.10 |
---|---|
(1) 디버깅은 문제 해결 능력의 지름길 (0) | 2024.10.10 |
Kernel Source 구조 (1) | 2024.10.10 |
리눅스 커널(Linux Kernel) 전처리 파일 생성하기 (0) | 2024.06.18 |
라즈베리파이(Raspberry pi) 기본 셋팅 (0) | 2024.04.27 |