[EETB] 7# 임베디드 리눅스

문연수·2022년 10월 6일
0

EETB

목록 보기
7/9
post-thumbnail

1. 임베디드 리눅스

- 1. 리눅스 운영체제를 사용하는 이유

 소프트웨어에 요구되는 다양한 기능을 처음부터 개발한다면 기간과 비용 모두 막대하게 소비된다. 이 문제를 해결하기 위해서는 임베디드 시스템의 개발 프로세스를 개선해 소프트웨어를 전부 다 만드는 것이 아니라 사용 가능한 소프트웨어 부품을 사용해서 개발 기간 및 비용을 절감하는 것이 바람직하다.

리눅스 운영체제는 무료로 이용할 수 있고 풍부한 미들웨어 및 장치 드라이버가 있기 때문에 임베디드 제품 개발에 이용되고 있아.

- 2. 리눅스 운영체제가 동작하는 하드웨어 구성

 임베디드 시스템은 최소한의 CPU, ROM, RAM 이 있으면 소프트웨어를 동작시킬 수 있다. 리눅스의 경우 추가적으로 메모리를 가상화하는 MMU (Memory Management Unit) 가 있는 것이 바람직하다.

* MMU (Memory Management Unit)

  • MMU 에는 가상으로 취급하는 주소 공간과 물리적으로 취급하는 주소 공간 이 있다. 가상주소 공간은 CPU가 다룰 수 있는 32비트 공간이다. 물리주소 공간은 실제로 하드웨어가 장착한 메모리 용량을 취급하는 공간이다.
  • MMU 에는 가상주소 공간으로부터 물리주소 공간으로 변환하는 기능이 있다. 이 혜택을 받기 위해 리눅스에서는 물리적 메모리를 페이지라는 최소의 단위로 관리해서 소프트웨어 프로세스마다 페이지 단위로 메모리를 할당하고 있다.

더 자세한 사항은 필자가 작성한 다음의 글을 참고하길 바람.

* MMU 가 없는 CPU 에서 리눅스를 동작시키는 것은 가능한가?

  • 결론부터 말하면 Yes. uCLinux 라고 불리는 리눅스의 기능을 사용하면 MMU 가 없는 CPU 에서도 동작시킬 수 있다.
  • 구체적으로 리눅스 커널의 구성에서 CONFIG_MMU 부분을 N 으로 설정하여 MMU 가 없는 CPU 에 대응한 동작을 하게 된다.

* 32 비트 이상의 CPU

  • 리눅스 운영체제가 등장한 시대에는 CPU가 32비트였다. 리눅스 운영체제는 원래 PC 용으로 개발된 운영체제다.
  • 최근에는 라즈베리 파이로 대표되는 저가형 오픈 하드웨어로 32비트 이상의 CPU 가 탑재되어 있다. 간단한 업무에 이용하고 싶을 때에는 가격도 저렴하고 정보도 풍부한 라즈베리 파이를 이용해 쉽게 리눅스 운영체제를 사용하여 개발 및 테스트를 할 수 있다.

* 탑재 메모리

 리눅스 운영체제는 소프트웨어에서 최대로 사용할 수 있는 메모리 공간인 4GB 중 3GB 를 애플리케이션이 동작하는 공간으로 할당하고, 1GB 는 전체를 관리하는 소프트웨어인 커널 공간으로 할당한다.

- 3. 리눅스 운영체제가 동작하는 소프트웨어 구성

  • 리눅스 운영체제는 커널과 기타 소프트웨어 군으로 나눌 수 있다. 커널은 하드웨어를 제어하기 위한 드라이버, 네트워크 프로토콜, 파일 시스템 등 하드웨어를 추상적으로 이용하기 위한 스택과 시스템 전체의 관리를 위해 스케줄러, 메모리 관리 등으로 구성된다.
  • 커널은 하드웨어를 이용하기 쉽도록 추상적인 /dev 인터페이스를 제공한다. 그리고 이 인터페이스들은 POSIX 호환 API 로 취급하도록 되어 있다.
  • 기타 소프트웨어 군(/sys)은 커널이 제공하는 추상 인터페이스를 더욱 편리하게 이용하기 위한 라이브러리, 프로세스간 통신을 위한 라이브러리 (IPC), 기동 순서를 제어하는 데몬(daemon) 이라고 부르는 상주 소프트웨어, GUI 를 제어하는 윈도우 관리자 등으로 구성된다.

자세한 사항은 필자가 작성한 다음의 글을 참고하길 바란다.

2. 임베디드 리눅스 소프트웨어의 개요

- 1. 프로세스

 리눅스 운영체제는 실행 프로그램을 프로세스 (Process)라는 단위로 관리한다. (더 정확하게는 태스크) 하나의 프로세서는 가상 공간에 하나의 메모리 공간을 가지고 있으며, 서로 다른 프로세스 간의 변수에는 접근할 수 없다. 프로세스는 PID (Process ID)라는 번호로 관리된다. (커널 내부에서는 tgid(Thread Group ID) 로 관리됨.)

더 자세한 사항은 필자가 작성한 다음의 글을 참고하길 바란다.

- 2. 쓰레드

 프로세스 안의 처리 단위로 잘게 나눈 것이 쓰레드(Thread)다. 개념적으로 프로세스와 동일하지만, 쓰레드를 사용함으로써 프로그램 처리의 병렬성을 향상시킬 수 있다. 프로세스와의 큰 차이는 부모-자식 관계가 없이 생성한 프로세스 내에서 실행 된다는 점이다.

- 3. IPC

 사용자 공간의 각 프로세스는 서로 다른 가상 공간의 메모리에서 동작하며, 서로의 메모리 공간에 액세스 할 수 없다. 프로세스끼리 메모리 간의 액세스와 처리를 의뢰할 때에는 IPC (Inter Process Communication)라는 프로세스 간 통신을 사용한다.

 IPC 와 관련된 더 자세한 사항은 Advanced Programming in the UNIX Environment 3/e 이라는 책을 읽기 바란다.

- 4. 커널

  • 커널에는 시스템 전체 스케줄을 관리하는 부분과 하드웨어를 제어하는 장치 드라이버 부분이 있다. 장치 드라이버는 하드웨어를 추상화하고 사용자 공간에서의 처리 의뢰나 하드웨어 통지를 실시한다. 두 기능 모두 추상화된 시스템 콜 인터페이스 를 사용자 공간에 제공한다.
  • 커널에는 시스템 콜 인터페이스와 커널 코드, 아키텍처 의존 코드가 포함되어 있다.

시스템 콜 인터페이스: 커널이 지닌 기능을 사용자 공간의 프로세스에서 사용할 수 있도록 제공하는 인터페이스다.

  • 커널 코드: 하드웨어 아키텍처에 의존하지 않으며, 리눅스 운영체제가 지원하는 CPU 아키텍처 모두에 공통되는 부분. e.g.) 프로세스 관리 및 스케줄링.
  • 아키텍처 의존 코드: 특정 아키텍처에 의존한 고유의 코드 부분. e.g.) ARM 이나 Intel 같은 CPU, SoC 의 아키텍처에 의존하는 코드 등.

* 장치 드라이버

 리눅스의 장치 드라이버는 커널의 최하층, 즉 하드웨어에 가장 가까운 부분에 위치한다. 장치 드라이버에는 캐릭터형 장치블록형 장치, 네트워크형 장치가 있다.

장치 드라이버 명내용특징
캐릭터형 장치바이트 스트림을 취급해 순차적으로 읽고 쓰는 장치. 대표적인 장치는 디스플레이나 프린터, USB 시리얼 등이 있다.순차적인 액세스이기 때문에 하드디스크 등의 제어에는 적합하지 않다.
블록형 장치고정된 크기의 블록을 랜덤으로 읽고 쓰는 장치. 대표적인 장치로는 하드디스크나 CD 드라이브, SD 카드 등이 있다.데이터 크기는 고정 길이 버퍼를 사용하기 때문에 CPU와 속도 차이가 있는 장치의 경우 장점이 있다.
네트워크형 장치소켓 인터페이스를 통해 통신하는 장치. 대표적 장치는 이더넷 및 와이파이 등이 있다.프로토콜 스택의 제어에 의존한다. 데이터 수신 시, 인터럽트가 발생한다.

- 5. 시스템 콜 인터페이스

 시스템 콜 인터페이스는 사용자 공간에서 실행되는 프로세스나 라이브러리가 커널의 기능을 사용할 경우의 인터페이스다.

* 장치 파일 (/dev)

  • 장치 파일은 시스템 콜을 사용하여 장치 드라이버에 액세스하는데 사용한다. 파일 형식으로 되어 있으며, 커널이 장치별로 종류와 번호를 관리한다.
  • 장치 파일은 /dev 의 아리에 위치하며, 장치 드라이버를 초기화 했을 때 파일이 생성된다. 원칙적으로는 하나의 장치에 하나의 파일이 생성된다.

* 시스템 파일 (/sys)

  • 시스템 파일은 사용자 공간의 프로세스에서 장치의 상태나 설정 변경 등이 가능한 장치 파일이다. 장치의 상태 확인, 설정 등에 이용하는 각종 파일이 실제 하드웨어처럼 사용할 수 있는 형태로 제공된다.
  • 일반적으로 디버깅을 할 때나 간편하게 하드웨어의 동작을 확인하고 싶을 때 사용한다. 시스템 파일 /sys 아래에 위치하며, 장치 드라이버를 초기화했을 때 파일을 생성한다.

* 시스템 상태의 확인 (/proc)

/proc은 사용자 공간의 프로세스 상태 및 커널의 설정 상태, 하드웨어 상태 등 다양한 상태를 확인할 수 있는 디렉토리이다.

3. 임베디드 리눅스의 빌드와 기동

 책에서는 Yocto Linux 를 통해 생성한 이미지 파일을 Raspberry Pi 3B+ 모델에서 실행시키는 과정을 보여주는데, 생략된 부분이 많고 과정 자체가 구식이기에 필자는 입맛대로 최신 문서를 읽고 메뉴얼대로 진행했다.

https://velog.io/@mythos/Yocto-Linux-Quick-Build-for-Raspberry-Pi-3B-Fedora-35

 위 링크는 Fedora 35 환경에서 Yocto Linuxmeta-raspberrypi 를 통해 Raspberry Pi 3B+ 모델에서 실행시킬 이미지 파일을 생성하는 과정을 풀어서 작성한 글이다.

 책에서는 USB to Serial 을 통해 보드에 접속하는데, 필자는 그냥 HDMI 케이블을 연결해서 접속했다.

4. 임베디드 리눅스의 동작 확인

- 1. Yocto 재빌드에 의한 자체 개발환경의 도입

local.conf 을 아래와 같이 수정하여 커널 드라이버를 자체 환경에서 빌드할 수 있게 만든다:

EXTRA_IMAGE_FEATURES ?= "debug-tweaks tools-sdk dev-pkgs tools-debug"

IMAGE_INSTALL:append = "kernel-devsrc git cmake"

책에서는 IMAGE_INSTALL_append 로 나오지만 최신 버전에는 다음과 같이 문법이 변경되었다.

bitbake core-image-base
cd tmp/deploy/images/raspberrypi3-64
sudo bmaptool copy <image name>.rootfs.wic.bz2 --bmap <image name>.rootfs.wic.bmap <device name>

 위 명령어를 순차적으로 입력하여 이미지파일을 microSD 에 기록한다. 빌드가 완료되었다면 파이에 접속하여 (유저명 root 입력하여 접속) date 명령을 입력하여 날짜와 시간을 변경한다:

date -s "2022-10-01 23:55:00"

 이후 다음의 명령을 입력하여 커널을 빌드한다. 책에서는 archheaders 로 되어있으나, make archheaders 를 입력해도 해당 target 에 대한 build 가 제대로 이뤄지지 않는다. 필자는 대신 archheadersdependancies 로 포함하고 있는 headers 를 빌드하였다.

cd /lib/moudules/$(uname -r)/build/
make prepare
make headers
make scripts

- 2. Hello World 드라이버 작성하기

vi 를 통해 다음과 같은 코드를 작성한다. 위 코드는 커널에 모듈로 삽입되어질 때 hello, world! 를, 제거되어질 때 goodbye~ 를 출력하는 모듈이다.

 다음과 같이 Makefile 을 작성한 뒤 make 명령을 이용하여 빌드를 수행한다. 책에서는 CC=/usr/bin/aarch64-poky-linux-gcc 의 구문이 따로 없으나 다음 구문이 없으면 gccaarch64-poky-linux-gcc 가 동일한 파일(심볼릭 링크로 연결되어 있음)임에도 불구하고 서로 다른 컴파일러로 취급하여 빌드가 수행되지 않는다. 따라서 필자는 위와 같은 방식으로 CC 를 직접 명시해주었다.

 빌드가 성공적으로 수행되었다면 hello.ko 라는 이름의 파일이 생성되는 것을 확인할 수 있다.

insmod 명령과 rmmod 명령을 입력하여 커널에 모듈을 삽입하고 또 제거할 수 있다.

- 3. 오픈소스 소프트웨어의 사용

 외부에서 파일을 다운로드 받기 위해 다음과 같이 LAN 케이블을 보드에 연결한 뒤 보드에 전원을 인가했다.

 정상적으로 부팅이 되었다면 ifconfig 명령어를 입력하여 제대로 연결이 되었는지 확인한다. 책에서는 네트워크 설정 파일을 만져서 직접 인터페이스를 생성하는데 필자는 처음부터 자동으로 연결이 되어 있어서 따로 만지지 않고 진행했다.

wget 명령을 입력하여 공식 github 에서 wiringPi 라이브러리 파일을 다운로드 받는다. 책에서는 git://git:drogon.net/wiringPi 를 이용하는데 이는 정말 deprecated 된, 또 더 이상 사용이 불가능한 주소이므로... 업데이트가 이뤄지는 비공식 github 주소를 이용하길 바란다:

wget https://github.com/WiringPi/WiringPi/archive/refs/tags/2.61-1.zip
unzip 2.61-1.zip

 다운로드가 끝났다면 압축을 풀고 build 파일을 vi 로 열어 다음과 같이 수정한다.

build 파일 중간에 sudo=${WIRINGPI_SUDO-sudo} 라는 구문이 나오는데 이를 주석처리 한다. (Custom Yocto Linux 에는 sudo 라는 이름의 명령어가 없어서 에러가 발생한다.) 이제 다음의 명령어를 입력하여 빌드에 필요한 디렉토리를 생성하고 빌드를 진행한다:

mkdir -p /usr/local/bin
mkdir -p /usr/local/include
mkdir -p /usr/local/lib

./build

 빌드가 정상적으로 수행되었다면 All Done. 이라는 메세지가 프롬프트되면서 스크립트가 종료된다.

 이제 다음과 같은 코드를 작성하고 컴파일하여 실행한 뒤 정상적으로 GPIO 에 대한 제어가 이뤄지는지 확인한다:

gcc led.c -I/usr/local/include -L/usr/local/lib -lwiringPi


 책에는 GPIO 직접 조작 방법 장치 드라이버 작성하는 법 등이 기술되어 있으나 필자는 귀찮아서 생략했다.

5. 임베디드 리눅스 개발에서 주의해야 할 포인트

  1. 리눅스 운영체제를 사용한 임베디드 시스템의 제품 개발은 SoC 업체의 레퍼런스 보드 및 제품 보드에서 하드웨어가 잘못 지정될 때 드라이버 소프트웨어가 리눅스 운영체제에서 지원되지 않아 어쩔 수 없이 드라이버 소프트웨어를 자사에서 직접 개발해야 하는 경우가 있다.
  2. 자사의 소프트웨어를 만들기 위해 과거 자산이라고 하는 소프트웨어 부품을 이용하는 경우 빌드는 쉽게 진행할 수 있으나 실시간 운영체제와 윈도우 등에서 개발한 소프트웨어는 리눅스 운영체제의 아키텍쳐와 맞지 않은 부분이 있기 때문에 통합(integration) 시험 등 후반 공정으로 갈수록 성능 문제와 버그가 많이 발생하여 개발 비용이 늘어나는 경우가 있다.
  3. 오픈소스 소프트웨어는 일반에 공개되어 있기 때문에 제품 사용 시 기능 안전 및 보안에 대해 주의해야 한다. 오픈소스 소프트웨어는 소스 코드 자체가 공개되어 있기 때문에 악의를 가진 사람이 소스코드의 버그를 악용해 제품에 침입한 후 소프트웨어의 조작이나 바이러스, 악성 코드 등을 감염시킬 우려가 있다.

출처

[책] 임베디드 엔지니어 교과서 (와타나베 노보루, 마키노 신지 지음, 정인식 옮김)

profile
2000.11.30

0개의 댓글