본문 바로가기

Hacked Brain/embeddedland.net

임베디드 프로그래머와 부트로더-4

저 자 : 유영창
출판일 : 2003년 10월호

== 상태 표시·메시지 표출·명령 입력 및 처리 기능
보드를 시험하는 과정에서 진행된 위치나 상태를 알려주는 기능은 거의 필수적이다. 예전에 동료 한 사람이 펌웨어 프로그램에 자칭 청진기 기능이라고 하는 메모리나 기타 상태를 보여 주는 기능을 항상 넣어 놓고 프로그램을 디버그했다. 당시에는 에뮬레이터가 일반화되어 있지 않기 때문에 이런 기능을 넣어야 했다. 지금도 상황에 따라 이런 기능을 넣어야 프로그램 디버깅이 편하다. 초기에 시리얼 디바이스 초기화가 힘든 상태에서는 LED를 점멸시키거나 LED를 이용해 진행 표시를 한다.
이후 시리얼 디바이스가 초기화되면 시리얼 디바이스를 이용해 프로그램 상태나 기타 메시지를 표출한다. 또한 특정한 문자열을 입력받은 후 수신된 입력 문자열을 명령으로 해석하고 이에 대한 처리 결과를 표출하는 기능 또한 유용하다. PC에서 바이오스 셋업 프로그램을 보면 비디오 장치와 키보드를 이용해 초기 상태를 설정하는 것도 이런 류에 포함된다. 임베디드 시스템은 비디오 장치가 없으므로 대부분 시리얼을 이용해 이런 기능을 구현한다.
시리얼 통신으로 메시지를 표출할 때는 보통 C 함수의 printf문을 구현하는 것이 일반적이다. 왜냐하면 프로그램 방식이 매우 친숙하기 때문이다. 물론 printf 함수 자체가 무척 복잡한 루틴이라서 프로그램 크기가 매우 커지는 단점이 있다.
대부분의 부트로더는 자체적으로 보드를 시험할 수 있는 명령 셋을 가지고 있는데 이를 모두 외우기 힘들므로 간단한 도움말 기능을 제공한다. 이런 내용을 구현하기 위해 부트로더는 다음과 같은 기능을 갖는다.

◆ 시리얼 입출력 루틴
◆ C 표준 입출력 함수
◆ 토큰 분리 함수
◆ 명령 해석 함수
◆ 명령 수행 함수
◆ 도움말 함수
◆ 실행 이미지와 데이터 이미지 다운로드 기능
PC에서 동작하는 프로그램을 작성하다가 임베디드 시스템에서 동작하는 프로그램을 작성하면서 가장 피곤한 것이 컴파일 위치와 수행 위치가 다르다는 점이다. 그나마 지금은 많이 나아졌지만 필자가 처음 8비트 원칩 프로세서에 프로그램할 때는 거의 ‘죽음’ 수준이었다. 롬 이레이저가 있었으나 이곳에 롬을 깔아 놓고 전원을 켠 후 대략 30분 정도 있어야 롬에 프로그램이 가능했다. 롬에 프로그램을 굽고 나서 보드에 꽂고 문제가 생기면 다른 롬에 프로그램 굽는 과정을 반복하다 보니 30분 이상이 걸리는 롬 지우는 과정을 또 거쳐야 했다. 아주 간단한 파라미터를 수정하면서 시험하면 롬을 굽다가 시간을 모두 허비하는 경우가 태반이었다. 요즘에야 프로세서 에뮬레이터들이 많아지고 롬 에뮬레이터가 나와서 예전 같은 고역은 많이 줄었지만 PC에서와 같이 컴파일하고 바로 실행해 보는 환경에 비하면 너무도 열악한 개발 과정이었다.
최근에 32비트 원칩 프로세서를 이용한 보드에서 롬으로 이용하는 메모리 디바이스로는 플래시 롬을 주로 사용한다. 이 플래시 롬은 수만 번을 썼다가 지울 수 있는 특성이 있다. 이런 기능을 이용해 플래시 롬에 수행돼야 할 프로그램도 써 넣고 프로그램에서 사용하는 데이터도 써 넣는다.
최초에는 당연히 보드에 프로그램이 넣어지지 않기 때문에 멀티 ICE나 JTAG 장비 또는 직접 만든 디버거 툴로 플래시에 부트로더 프로그램을 써 넣는다. 그러나 일단 부트로더가 수행되기 시작하면 부트로더 자체에 플래시 롬에 데이터를 써 넣는 기능을 추가할 수 있다. 개발 호스트 컴퓨터에서 시리얼 통신이나 이더넷 통신 기능을 이용해 이미지를 다운로드한 이후에 플래시 롬에 써 넣는다. 이런 환경이 갖춰지면 보드의 프로그램 개발 속도는 한층 가속이 붙는다.
특히 임베디드 시스템에 리눅스를 이용할 때에는 리눅스 커널 자체 크기도 1MB에 달하고 램 디스크 이미지 크기도 2MB 이상을 넘는 것이 보통이므로 단순하게 시리얼로만 다운로드하면 전송 속도가 매우 느려서 개발이 무척 힘들어진다. 그래서 단가 문제로 인해 최종 제품에는 이더넷을 빼더라도 개발 단계의 보드에서는 꼭 집어 넣는다.
이렇게 보드에 부여된 통신 기능을 이용해 롬에 써 넣어야 할 데이터를 수신받고 이를 롬에 써 넣는 기능이 부트로더가 기본적으로 가져야 할 기능이다. 부트로더가 가져야 할 기능을 요약하면 다음과 같다.
◆ 이미지 다운로드 통신 루틴
◆ 플래시 써 넣기 및 데이터 검사 루틴

== 운영체제 구동 기능
임베디드 시스템에 리눅스 커널을 탑재하고 구동시키기 위해 단순하게 커널 시작점으로 점프하는 것으로 끝나는 것이 아니다. 초기 설정 상태를 커널에 전달하거나 커널의 진입시에 필요한 형식을 구현해 주어야 한다.
일반적인 임베디드 리눅스 커널이라면 압축된 이미지가 플래시 메모리 장치에 있고 리눅스 커널을 필요로 하는 루트 디바이스용 램 디스크 이미지가 플래시 메모리 장치에 있게 된다. 이것을 램에 복사한 후 램에 옮겨진 커널을 구동시켜야 한다. PC에서 하드디스크에 있는 내용을 램에 옮겨 놓고 부팅하는 과정과 유사하다. 이런 과정을 구현하기 위해 부트로더가 가져야 할 기능은 다음과 같다.
◆ 커널 이미지를 램 영역으로 복사 하는 기능
◆ 램 디스크 이미지를 램 영역으로 복사하는 기능
◆ 커널 수행 진입점으로 수행 위치를 옮기는 기능

== 알려진 부트로더들
지금까지 설명한 부트로더의 기능을 처음부터 혼자서 일일이 만들게 되면 보통 작업량이 아니다. 앞의 예를 보면 몇 가지 되지 않는 것 같지만 실제로 작성하다 보면 구현해야 할 기능이 생각보다 방대하다. 거의 작은 운영체제(?)를 만드는 느낌이 드는 경우도 있다. 또 알아야 할 지식도 한두 가지가 아니다(뭘 알아야 하는지는 이 연재 내용을 계속 읽어가다 보면 저절로 알 것이다).
그렇다고 포기할 것인가? 그건 개발자가 할 짓이 못 된다. 또 못한다고 위에 보고하면 그날로 바로 쫓겨날 수 있다. 요즘같이 힘든 경제 상황에서 회사에서 잘리면 골치 아프다. 처자식을 생각해야 하지 않겠는가. 그렇다면 어떻게 할 것인가? 이런 오도 가도 못하는 불쌍한 개발자를 위해 그 유명한 리차드 스톨만 같은 사람들이 인터넷 세상에는 바글바글하다(이런 사람들에게 항상 감사하게 생각하자). 수많은 프로그램 자선 사업가들은 자신이 고생고생해서 만든 루틴을 돈 한 푼 안 받고 인터넷에 기부하고 있다. 이들은 자신이 작성한 루틴이 같은 계열의 개발자들의 고생을 덜기를 바란다. 그러므로 우리는 이들의 성의를 무시하면 안 된다. 공개된 프로그램은 빨리 받아들여 사용해야 한다. 그래야 그 성은에 보답하는 길이다.
문제는 이런 공개된 부트로더 소스를 어디서 구할 수 있는가 하는 점이다. 아는 놈이 도둑질 한다고 막상 부트로더를 구하려 하면 어디서 구해야 할지도 모르고 어떻게 써야 할지도 모른다. 그래서 필자가 알고 있는 부트로더 종류와 구할 수 있는 위치 몇 군데를 알려 주겠다(<표 1>). 쓰는 방법은 필자가 알려 주기 조금 벅찬 감이 있으므로 이 부분은 독자의 몫으로 남겨 두겠다.

== 부트로더와 플램폼 의존 관계
지금까지 우리는 부트로더가 필요한 이유와 부트로더가 구현해야 할 내용에 대해 대략적으로 알아봤다. 설명한 내용을 보면서 느꼈겠지만 부트로더는 특정 프로세서와 특정 보드를 위한 프로그램이다. OS처럼 범용적인 것은 아니다. 물론 공개된 부트로더 중에는 다양한 프로세서와 다양한 플랫폼을 지원하는 경우도 있다. 하지만 이것은 결국 각각의 프로세서들을 모두 경험하고 직접 프로그램했을 경우 구현이 가능하기 때문에 혼자서 모든 것을 알 수는 없다. 때문에 이런 프로그램을 전문적으로 개발해 주는 회사나 공동 프로젝트일 경우만이 가능한 것이다.
이 글의 서두에서도 설명했듯 우리의 목적은 부트로더를 직접 제작해 가면서 부트로더의 구성과 구현 과정을 알기 위한 것이다. 다양한 프로세서와 플랫폼을 생각하고 작성하기에는 너무 벅찬 감이 있다. 그나마 다행인 것은 부트로더를 작성하기 위해 주로 C를 사용한다는 점이다. C 언어는 이식성이 매우 높기 때문에 일단 만든 루틴들은 재사용이 가능하다. 더구나 부트로더의 복잡한 루틴들은 로직적인 구현으로만 구성된다.
예를 들면 C 표준 입출력 함수라든가 UDP 통신 루틴이 한 예일 것이다. 그외 플래시 메모리의 프로그램 방식은 다른 플랫폼일 경우라도 같은 플래시 메모리를 사용한다면 구현 루틴은 동일하다. 그러므로 한번 작성된 자신만의 부트로더는 다른 플랫폼에 사용되는 부트로더를 작성하더라도 어느 정도 재사용이 가능해진다. 결국 다양한 플랫폼을 지원하는 공개된 부트로더도 이런 방식으로 시작한 것이다. 또한 공개된 부트로더의 구성 방식 역시 같은 계열의 프로세서라면 비슷한 내용을 가질 수밖에 없다. 단지 편리한 기능에 대한 루틴이 더해지거나 빠진 정도로 봐도 무방하다.
어찌됐든 부트로더는 플랫폼 의존적일 수밖에 없다. 그런 이유로 인해 필자가 설명하고 구현해 가는 부트로더 역시 플랫폼 의존적일 수밖에 없다. 또한 필자의 부족한 능력 때문에 다른 플랫폼에서 동작하는 부트로더를 설명하기에는 무리가 따른다. 이 점은 독자들에게 양해를 구하는 바이다.
이 연재를 위해 사용되는 플랫폼은 EZ-X5라는 모델명을 갖는 보드이다. 이 보드는 XScale이라고도 알려진 PXA255 프로세서를 탑재한 보드이다. XScale은 인텔에서 만든 프로세서로 하나의 프로젝트 명이다. 여러분의 PC에서 사용되고 있는 CPU의 펜티엄이라는 이름과 같은 것이다. 실제 프로세서 모델명은 PXA255와 같이 내부 구성에 따라 다른 이름을 갖는다. 보통 PXA255라는 이름만 보면 이 프로세서가 ARM의 한 계열이라고 느껴지지 않을 것이다. 하지만 구성 형식이나 동작 방식 그리고 명령을 보면 ARM 계열이라는 것을 알 수 있다. 이 프로세서는 이전에 스토롱암이라는 프로세서의 후속작으로 400MHz의 속도를 갖는 매우 막강한 프로세서이며 최근에 출시되는 많은 PDA에서 사용되고 있다. 일단 프로세서에 대해 간략하게 알아보자.