임베디드 시스템은 보통 작게 파일시스템을 만든다. 라즈베리파이처럼 파일시스템을 엄청 크게 만들지 않는다. 그래서 우리는 아주 미니멀한 파일시스템을 만들어보는 실습을 해 볼 것이다.
그래서 아래의 빌드 코드를 보면 "bitbake core-image-minimal"라는 옵션으로 빌드하게 된다. 우리는 아래 빌드를 통해 12MB 정도의 작은 루트 파일 시스템을 빌드할 것이고, 거기다가 ssh를 추가한다던지 하는 식으로 실습을 진행할 것이다.
// 디렉토리 생성 및 필요 패키지 다운로드
ubuntu@ubuntu14:~/pi_bsp$ mkdir rootfs ; cd rootfs
ubuntu@ubuntu14:~/pi_bsp/rootfs$ mkdir youcto ; cd yocto
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto$ sudo apt-get install gawk wget git diffstat unzip texinfo gcc-multilib build-essential chrpath socat libsdl1.2-dev xterm python zstd liblz4-tool
// 포키 빌드시스템 다운로드
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto$ git clone -b kirkstone git://git.yoctoproject.org/poky.git
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto$ cd poky/
// 라즈베리파이용 레이어(meta-raspberrypi) 다운로드
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky$ git clone -b kirkstone git://git.yoctoproject.org/meta-raspberrypi
// 반드시 한번은 실행해줘야 하는 쉘 스크립트 (환경변수를 잡아줌)
// (현재 쉘은 지정한 build 디렉토리를 관리하는 쉘이 된다.)
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky$ source oe-init-build-env
// 라즈베리파이 머신 등록 (아래 사진 참고)
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ vi conf/local.conf
// 레이어 경로 등록 (아래 사진 참고)
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ vi conf/bblayers.conf
// 본격적인 빌드 - "오래 걸림"
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ bitbake core-image-minimal
Poky는 Yocto Project의 레퍼런스 배포로서 초기 구성 및 설정을 제공한다.
파일시스템에 존재하는 수많은 소스들을 다 가져오면서 빌드한다. poky는 빌드시스템이다. 파일시스템에 포함시켜야할 layer(임베디드 이미지에 포함시켜야 할 패키지 목록)들을 가지고서 poky로 빌드한다. 그러면 poky는 해당 layer를 빌드하는데 필요한 소스들을 인터넷으로부터 다운 받으면서 빌드하기 때문에, 빌드하는데 시간이 상당히 많이 걸리게 된다.
bitbake는 실제로 레시피를 빌드하고 패키지를 생성하는 도구로 사용된다.
bitbake는 Yocto Project에서 사용되는 빌드 엔진이다.
bitbake는 여러 레시피를 관리하고 의존성을 해결하며, 레이어의 메타데이터를 사용하여 빌드 구성을 자동으로 생성한다.
zImage 파일이 라즈베리파이에 구울 수 있는 이미지 파일이다.
제대로 생성이 되어있는지 아래 경로로 들어가서 확인해보자.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/deploy/images/raspberrypi4$ ls -l
...
-rw-r--r-- 2 ubuntu ubuntu 12582912 2월 19 11:54 core-image-minimal-raspberrypi4-20240219004416.rootfs.ext3
...
lrwxrwxrwx 2 ubuntu ubuntu 58 2월 19 11:54 core-image-minimal-raspberrypi4.ext3 -> core-image-minimal-raspberrypi4-20240219004416.rootfs.ext3
...
첫번째 방법은 파일시스템 자체를 복사하는 것이다. 그리고 우리가 yocto를 이용하여 자동으로 이미지를 만들 때 core-image-minimal이라고 만들었기 때문에, 20MB라는 작은사이즈로 만들어지는 것이다. 물론 거기에 10MB정도의 여유공간을 주기는 했지만 매우 작은 사이즈라서 턱없이 부족할 수 있다. 바로 그럴 때 gparted
같은 프로그램을 사용하여 파일시스템을 확장할 수 있는 것이다.
한편 두번째 방법은 첫번째 방법과 아예 방향이 반대이다. 먼저 SD카드의 rootfs 영역을 먼저 포맷해서 32GB의 용량을 모두 쓸 수 있도록 한 다음에, 거기에 파일시스템이 그 자체로 압축되어있는 rpilinux-image-raspberrypi4.tar.bz2라는 압축파일을 그대로 압축 해제함으로써 파일시스템을 생성한 것이다.
파일시스템의 크기 만큼만 사용할 수 있다.
// SD 카드를 우분투에 연결하고 장치파일 언마운트
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/deploy/images/raspberrypi4$ sudo umount /dev/sdb?
// 우리는 지금 rootfs에 이미지를 쓰고 싶은 것이므로 dd 명령을 이용하여 sbd2에 이미지를 써준다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/deploy/images/raspberrypi4$ sudo dd if=core-image-minimal-raspberrypi4.ext3 of=/dev/sdb2 bs=1M status=progress
12+0 레코드 들어옴
12+0 레코드 나감
12582912 bytes (13 MB, 12 MiB) copied, 2.88056 s, 4.4 MB/s
이걸 그냥 이제 라즈베리파이에 다시 꽂고 부팅시키면 된다.
(현업에서는 플래시메모리에다가 이런 파일시스템을 저장한다. 라즈베리파이처럼 SD카드에다가 파일시스템을 쓰는 경우는 거의 없다. 그래서 파일시스템을 크게 가져가면 플래시메모리의 크기가 커지기 때문에 제품의 가격이 상승하는 요인이 된다.)
라즈베리파이를 부팅시키고 아래 명령어를 쳐보니, 네트워크는 자동으로 붙었는데, 디스크 파티션은 늘려져 있지 않은 모습을 볼 수 있다.
root@raspberrypi4:~# ifconfig
eth0 Link encap:Ethernet HWaddr DC:A6:32:9E:5D:9E
inet addr:10.10.15.213 Bcast:10.10.15.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:265 errors:0 dropped:0 overruns:0 frame:0
TX packets:2 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:34707 (33.8 KiB) TX bytes:684 (684.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
root@raspberrypi4:~# df
Filesystem 1K-blocks Used Available Use% Mounted on
udev 687300 0 687300 0% /dev
tmpfs 191672 100 191572 0% /run
/dev/mmcblk0p2 10393 4828 4951 49% /
tmpfs 958360 64 958296 0% /var/volatile
SD카드의 모든 저장공간을 사용할 수 있다.
압축파일을 바로 SD카드에 적겠다.
라즈베리파이용 SD카드를 우분투에 인식시키고 umount시킨다.
// 우분투에 인식된 SD카드 마운트 해제하기
ubuntu@ubuntu14:~$ sudo umount /dev/sdb?
// rootfs만 포맷하기 (ext3 저널링 파일 시스템으로 포맷해준다)
ubuntu@ubuntu14:~$ sudo mkfs.ext3 /dev/sdb2
mke2fs 1.45.5 (07-Jan-2020)
/dev/sdb2 contains a ext3 file system
last mounted on /media/ubuntu/6c215e87-1da4-4bae-a03f-d9ffd600326f on Tue Feb 20 13:55:16 2024
Proceed anyway? (y,N) y
Creating filesystem with 7659648 4k blocks and 1916928 inodes
Filesystem UUID: 6054dc61-a813-4cb2-b6c8-babce52a7834
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632, 2654208,
4096000
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
// 파일시스템의 라벨명 지정하기
ubuntu@ubuntu14:~$ sudo e2label /dev/sdb2 rootfs
// 다시 마운트 하기
// 1. 가상환경 Virtual Box의 GUI를 통해 SD카드를 재 인식시켜준다.
// 2. 그럼 정상적으로 인식이 되어있는 것을 볼 수 있다(rootfs가 29기가로 확장되어 잡힌 것을 확인할 수 있다.).
ubuntu@ubuntu14:~$ df
...
/dev/sdb1 522232 131456 390776 26% /media/ubuntu/bootfs
/dev/sdb2 29981316 156 28449232 1% /media/ubuntu/rootfs
// 3. 그리고 새로 만들어진 rootfs는 저널링 파일 시스템 ext3로 생성되었기 때문에 lost+found라는 복구 디렉토리가 존재하는 것을 확인할 수 있다.
ubuntu@ubuntu14:~$ ls -l /media/ubuntu/rootfs/ | grep lost+found
drwx------ 2 root root 16384 2월 20 13:59 lost+found
// 이제 진짜 해당 rootfs에 bz2 파일을 압축해제한다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/deploy/images/raspberrypi4$ sudo tar xvfp rpilinux-image-raspberrypi4.tar.bz2 -C /media/ubuntu/rootfs/
// df로 제대로 써졌나 확인해보기(위에서 압축해제로 쓴 이미지는 14mb(14756)의 작은 이미지가 맞다는 것을 확인할 수 있다... core-minimal)
ubuntu@ubuntu14:/media/ubuntu/rootfs$ df
...
/dev/sdb1 522232 131456 390776 26% /media/ubuntu/bootfs
/dev/sdb2 29981316 14756 28434632 1% /media/ubuntu/rootfs
yocto로 만들어진 core-image-minimal 이미지가 설치된 라즈베리파이에 ssh를 사용할 수 있도록 만들어야 한다.(그래야 putty를 쓸 수 있다.)
그러기 위해서 우리는 욕토에 사용자 정의 레이어/레시피로 ssh 관련 레이어/레시피를 추가하겠다.
// 레이어 폴더 만들기(욕토 레이어 네이밍 관습에 맞게 만들어야 함)
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky$ mkdir meta-rpilinux ; cd meta-rpilinux ; mkdir conf ; cd conf
// 레이어 파일(layer.conf)을 생성하여 아래 그림처럼 내용을 입력해준다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/meta-rpilinux/conf$ vi layer.conf
// 레시피 폴더 만들기(욕토 레시피 네이밍 관습에 맞게 만들어야 함)
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/meta-rpilinux$ mkdir recipes-rpilinux ; cd recipes-rpilinux ; mkdir images ; cd images
// 레시피 파일(rpilinux-image.bb)을 생성하여 아래 그림처럼 내용을 입력해준다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/meta-rpilinux/recipes-rpilinux/images$ vi rpilinux-image.bb
이제 위에서 만든 meta-rpilinux
라는 레이어를 bitbake 빌드 레이어 config에 등록해줘야 한다. 아래 파일을 열어준다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/conf$ vi bblayers.conf
이렇게 13번째 라인을 추가해준다. 위에서 만든 레이어인 meta-rpilinux
를 추가해준 것이다.(이로써 위에서 만든 openssh 레이어가 빌드 시스템에 등록되었다.)
그럼 이제 한번 확인하는 차원에서 다운로드 된 패키지가 제대로 있는지 확인해보자.
(-s 옵션(search): core-image-minimal를 찾아보겠다는 뜻)
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ bitbake -s core-image-minimal | grep openssh
nativesdk-openssh :8.9p1-r0
openssh :8.9p1-r0
packagegroup-core-ssh-openssh :1.0-r1
그럼 이제 다시 최종 rootfs 이미지를 빌드해주자. (이전에 한번 했어서 엄청 빨리 된다.)
(-c 옵션(clear): 이전 이미지를 삭제하고 빌드하겠다는 뜻)
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky$ source oe-init-build-env
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ bitbake -c cleanall core-image-minimal
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ bitbake rpilinux-image
위의 ## (2) bz2 압축파일로 sd카드 저장 방법
를 참고하자.
자 ssh 레이어가 추가되어 빌드된 이미지가 SD카드에 구워졌다.
현재 라즈베리파이는 ssh가 활성화 되어 있기 때문에, scp
명령을 통해서 파일을 이동시킬 수 있다. ($ scp main_arm pi@10.10.15.220:~
)
ubuntu@ubuntu14:~/linuxC$ ls
copy.c file_client.c file_server gcc.d hello_client.c hello_server.c main_arm
file_client file_from_clnt.txt file_server.c hello_client hello_server main.c main_x86
ubuntu@ubuntu14:~/linuxC$ scp main_arm pi@10.10.15.220:~
The authenticity of host '10.10.15.220 (10.10.15.220)' can't be established.
ECDSA key fingerprint is SHA256:IF85KHDxGeSZrdH+l4szHJN23B/Xkzht8jkQpBgOuMU.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.10.15.220' (ECDSA) to the list of known hosts.
pi@10.10.15.220's password:
main_arm
raspberrypi4:/home/pi# ls
main_arm
raspberrypi4:/home/pi# ./main_arm
우리가 실제로 임베디드 시스템을 개발할때는 이렇게 필요한 레이어만 딱딱딱 넣어서 만들어진 "미니멀" 이미지를 올려서 구동하게 된다.
실제로 이 미니멀 이미지는 gcc 컴파일도 안되기 때문에, 더더욱 크로스컴파일이 필수가 된다.
이게 진짜 yocto를 이용한 커스텀 포인트 of 포인트다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs$ ls
bin boot dev etc home lib media mnt proc run sbin sys tmp usr var
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs$ cd home
// 깊긴한데 여기까지 들어와봐라
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs/home$ ls
root
위를 보면 pi라는 사용자 계정의 홈 디렉토리가 없다는 것을 알 수 있다.
우리는 pi라는 폴더를 만들고 싶다.
그런데 그냥 pi라고 만들면 소유자:그룹명이 ubuntu:ubuntu로 만들어져서 안된다.
그래서 우리는 아래와 같은 방법으로 계정을 만들겠다.
우분투에서 그냥 pi라는 계정을 만들어서, 라즈베리파이 타겟머신에서 만들어져있는 pi계정과 똑같이 만들어서, 그 계정으로 로그인하는 것을 목표로 한다.
ubuntu@ubuntu14:~$ sudo adduser pi
'pi' 사용자를 추가 중...
새 그룹 'pi' (1001) 추가 ...
새 사용자 'pi' (1001) 을(를) 그룹 'pi' (으)로 추가 ...
'/home/pi' 홈 디렉터리를 생성하는 중...
'/etc/skel'에서 파일들을 복사하는 중...
새 암호:
새 암호 재입력:
passwd: 암호를 성공적으로 업데이트했습니다
pi의 사용자의 정보를 바꿉니다
새로운 값을 넣거나, 기본값을 원하시면 엔터를 치세요
이름 []:
방 번호 []:
직장 전화번호 []:
집 전화번호 []:
기타 []:
정보가 올바릅니까? [Y/n] y
// 1001번으로 만들어진 pi 계정
ubuntu@ubuntu14:~$ cat /etc/passwd
...
pi:x:1001:1001:,,,:/home/pi:/bin/bash
우분투는 기본 1000번의 UID로 만들어져있다(해당 리눅스 시스템의 첫번째 사용자유저가 ubuntu였기 때문이다). 그래서 위와같이 pi라는 새로운 계정을 만들면 1001번의 UID로 만들어진다.
우리는 저 pi의 UID를 1000으로 바꿔줘야 한다. 왜냐하면 타겟보드인 라즈베리파이에서는 첫번째 유저가 pi가 되어야 하기 때문이다.
지금부터 pi의 UID를 1000으로 변경해주겠다.
이미지를 만들기 전에 한번 꼭 clean을 해줘야 한다. 아래 경로로 가서 ls
를 쳐보면 아래와 같이 나온다. 저걸 한번 clean해줘야 한다는 것이다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0$ ls
build-wic recipe-sysroot
deploy-rpilinux-image-image-complete recipe-sysroot-native
image_initial_manifest rootfs
intercept_scripts-898246b1d0f2f649c3f2a6622aaae28f077c55eaf43333557ef6eb617be110b8 rpilinux-image-1.0
oe-rootfs-repo temp
pseudo
// 새로운 쉘을 열어서 아래의 명령어를 아래의 경로에서 입력해준다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ bitbake -c cleanall rpilinux-image
// 그리고 다시 해당 디렉토리로 가서 더 깊이 내려가서 ls를 쳐보면 정보가 다 사라져 잇는 것을 확인할 수 있다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0$ ls
temp
이제 pi:pi라는 소유자:그룹명으로 pi라는 home디렉토리를 만들기 위해서 작업을 시작하겠다.
목표는 raspberry pi4에서 이미 만들어져있는 pi계정의 내용을 그대로 ubuntu로 복사해와서 ubuntu에서 계정을 하나 새로 만들 뒤, su - pi
명령으로 pi계정으로 스위칭 한 다음에, 그 상태에서 ~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs/home
경로까지 내려가서 pi 디렉토리를 생성하는 것이다.
라즈베리파이 타겟머신에 생성되어 있는 pi계정을 우분투로 복사하기 위해서는 세개의 파일을 수정해주어야 한다.
1. /etc/passwd
2. /etc/shadow
3. /etc/group
우분투 가상환경의 /etc 경로에다가 아래 사진들(라즈베리파이 타겟 머신에서 캡쳐해온 사진) 부분을 복붙 해준다. 여기서 중요한 것은 라즈베리파이 타겟머신에서는 pi계정이 첫번재 계정이기 때문에 UID가 1000번으로 적용되지만, 우분투 가상머신에서는 ubuntu라는 첫번째 사용자 계정이 이미 존재하기 때문에, 우분투에 추가하려는 pi계정의 UID는 1001으로 수정하여 복/붙 해줘야 한다는 것이다.
아래는 최종적인 /etc/passwd
, /etc/shadow
, /etc/group
파일에 대한 스크린 샷이다.
자 이제 우분투 가상환경에서 ubuntu계정으로 ~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs/home
경로에 pi
디렉토리를 생성한다. 그리고 나서 소유자:그룹명
을 바꿔주고, 읽기쓰기권한
도 바꿔줘야 한다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs/home$ mkdir pi
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs/home$ sudo chown pi:pi pi
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build/tmp/work/raspberrypi4-poky-linux-gnueabi/rpilinux-image/1.0-r0/rootfs/home$ sudo chmod 755 pi
위와 같이 폴더를 만든 뒤에 아래 bitbake 명령을 통해 이미지를 다시 만들어주면 끝이다.
bitbake 뒤에 오는 것은 레시피 이름이다.
ubuntu@ubuntu14:~/pi_bsp/rootfs/yocto/poky/build$ bitbake rpilinux-image
이로써 pi 계정이 기본으로 포함된 rpilinux-image가 만들어졌다. 이것을 SD카드에 쓰고 싶다면 위의 "두가지 방법 중 하나"를 사용하면 된다.
uboot나 kernel은 회사에서 사오는 것을 쓰는 것이 맞다. 하지만 file system은 얼마든지 커스텀 하여, 이미 만들어져 있는 것을 그대로 가져다가 쓸 수 있다는 것이다. 그게 핵심이다.