출처 : imaso.co.kr (월간, 마이크로소프트)
이글은 월간 임베의 글을 제가 보기 좋게 편집한 글입니다. 인터넷에 돌아다니는 해당글들이너무 조잡하게 편집되어있어서 보기좋게 편집했습니다. 처음 임베디드 시스템을 접하시는 분들에게 좋은 글입니다.
문제가 된다면 자삭하겠습니다.
임베디드 프로그래머와 부트로더-1
- 저 자 : 유영창
- 출판일 : 2003년 10월호
- 필자는 이 세상에 PC라는 시스템이 나오기 전에는 임베디드 시스템이라는 구별이 따로 없었던 것으로 기억한다. 또 한 가지 ‘펌웨어’라는 단어도 별로 사용하지 않았다. 그러다가 PC가 나온 후 어느 정도 시간이 지나면서 임베디드 시스템이라는 말이 사용되기 시작했고 또한 펌웨어라는 단어를 접하게 되었다.
임베디드(embedded)를 인터넷의 야후 사전에서 찾아보면 이렇게 나온다.
embed[imbd, em-] vt. (-ded; -ding) [1] 깊숙이 박다, 파묻다 [2] (마음 속 등에) 깊이 간직하다((in)) [3]【언어수학】 끼워 넣다
임베디드 시스템을 통상적으로는 내장 시스템이라고 말한다. 하지만 필자는 PC 이외의 시스템을 모두 임베디드 시스템으로 정의하겠다. 물론 경우에 따라서는 PC 자체도 임베디드 시스템으로 사용될 경우도 있다. 아마도 대부분의 독자들은 임베디드 시스템과 밀접하게 연결되어 살고 있을 것이다. 얼마나 많은 것들이 임베디드 시스템인가 필자의 오전 일과를 통해 알아보자.
아침에 일어나면 필자는 냉장고부터 찾는데 시원한 물을 먹기 위함이다. 이 최신(?) 냉장고는 전면에 LCD 창을 달고 있는데 예전의 기계식 제어 방식이 아닌 컨트롤러가 내장된 시스템이다. 식사 중에 잠깐 케이블 TV를 켜서 케이블 방송을 보는데 이 케이블 방송 단말기도 컨트롤러가 내장된 시스템이다. 버스를 타기 위해 버스 카드를 운전자 앞에 있는 버스 카드 리더기에 갖다 대는데 이것도 컨트롤러가 내장된 시스템이다. 버스를 타고 가는 도중 곤혹스럽게 휴대폰이 울려대는데 이 휴대폰도 역시 컨트롤러가 내장된 시스템이다. 회사에 출근해 사원증으로 문을 여는데 사원증 인식 장치 역시 컨트롤러가 내장된 시스템이다.
필자가 회사까지 출근하는 과정에 만나는 상황을 적?것인데 내장된 시스템이라는 단어가 들어간 것들이 모두 임베디드 시스템이다. 예를 보면서 느꼈겠지만 이 땅에 사는 사람들이라면 거의 임베디드 시스템을 보지 않고는 살 수 없을 것이다. 그만큼 임베디드 시스템은 주변에 널려 있는 것이다. 필자가 PC 이외에 모든 것을 임베디드 시스템이라고 우기는 이유가 바로 여기에 있다.
이런 임베디드 시스템에서 동작되는 프로그램을 일반적으로 펌웨어라고 한다. 이런 내장형 시스템에 들어가는 프로그램을 작성하는 것은 PC 프로그램을 작성하는 과정과는 매우 큰 차이가 있다. 이 차이점은 부트로더를 만들어 가면서 온 몸으로 느낄 것이다.
임베디드 시스템에 사용되던 프로세서들
필자가 느끼는 시간을 기준으로 볼 때 이전에(사실 그리 오래 전도 아니다) 임베디드 시스템에 사용되는 프로세서들은 크게 두 분류로 나눠진다. 무척 싸고 작은 8비트 원칩 계열과 고기능(?)으로 무장한 32비트 프로세서들이다. 물론 중간에 해당하는 16비트 프로세서들도 있었지만 이 프로세서들은 중간 정도의 역할을 했다기보다는 사용처가 애매모호해서 크게 활성화되지 못한 것으로 보인다.
우선 8비트 원칩 계열을 보면 8051 계열, PIC 계열의 칩들이 대표적이다. 32비트 CPU라면 MC68XXX 계열과 V40으로 대표되는 i386 계열이다. 물론 그 외에도 많았겠지만 필자의 발이 그리 넓지 못했기 때문에 주로 접한 프로세서들은 이 정도이다.
일반적으로 8비트 원칩 계열을 적용하는 임베디드 시스템은 싸고 대량 생산이 필요할 경우에 주로 이용됐다. 그 외 제어용으로도 많이 사용됐다. 임베디드 시스템에 사용된 주된 이유는 프로세서의 가격이 싸고 필요로 하는 기능이 대부분 하나의 칩에 구현되어 있기 때문이다. 또한 초기에 이 프로세서를 사용하는 개발자가 많았던 이유도 있다. 이 프로세서의 대표적인 특징은 하나의 칩 제어에 필요한 요소인 램, 롬, 시리얼 포트, I/O 포트, ADC(Analog to Digital Conversion)와 같은 것들이 모두 들어 있다. 즉, 칩 하나면 충분한 구조로 설계되어 있는 것이다. 또한 프로세서 한 종류에 여러 가지 타입이 존재해 필요에 따라 칩을 선택할 수 있는 장점도 있다.
32비트 프로세서 계열은 고기능(?)을 필요로 하는 장비에 많이 사용되었는데 이 프로세서들은 단독으로 사용할 수는 없고 주변에 프로세서가 동작하기 위한 부가 칩들이 필요하다. 특징은 주로 고속이라는 것이다. 물론 고속이라고 해도 현재 PC에 사용하는 프로세서와 비교하면 형편없는 속도다. 빨라도 40MHz 이하의 속도가 대부분이었기 때문이다. 최근에는 8비트도 이 정도 속도는 기본이고 100MHz의 속도를 갖는 제품도 속속 등장하고 있다. 이렇게 두 분류로 나눠지다 보니 내장되는 소프트웨어의 모습도 두 분류로 나눌 수 있다. 우선 32비트 소프트웨어의 모습을 살펴보자.
32비트 프로세서에 내장되는 소프트웨어
32비트 프로세서를 사용하는 임베디드 장비는 복잡한 작업을 수행하는 경우가 일반적이다. 네트워크 통신 제어라든가, 영상 검출이라든가, 아니면 반도체 장비 제어와 같은 제어 흐름이 복잡해 프로그램 용량이 커야 하고 제어 속도도 매우 중요하고 기능도 많아야 하는 것이 대부분이다.
이러한 기능을 모두 충족시켜야 하고 리얼타임 요소도 갖추다 보니 돈 많은 회사거나 신뢰성을 요구하는 장비에는 리얼타임 OS를 채택하는 경우가 많았다. 대표적인 것이 바로 VxWorks이다. 이 운영체제는 유닉스 스타일의 프로그램을 요구하는데 최고의 특징은 실시간이라는 것이다. 단점으로는 높은 라이선스 비용과 개발 툴 비용, 그리고 개발자 수가 매우 적다는 점이다(개발 툴이 비싸서 돈 없는 중소기업에서는 구경하기 힘들고 중견급 이상의 연구소나 개발실에서나 접할 수 있다). 돈이 없거나 굳이 OS란 개념이 불필요한 엔지니어들은 32비트 프로세서라 해도 그냥 OS 없이 프로그램을 작성하는 경우도 태반이다. i386 계열의 임베디드용 프로세서를 사용하는 경우라면 악명 높은 DOS를 올리고 DOS용 컴파일러를 사용하는 경우가 많았다.
OS가 있는 시스템에 프로그램을 하는 펌웨어 프로그래머들은 행복한 편에 속한다. 물론 임베디드 프로그램의 특수성이 있기 때문에 나름대로 어려운 부분은 있겠지만 상당 부분을 OS에서 처리하기 때문에 실제 응용 프로그램에 관련된 부분만 집중적으로 프로그램하면 되기 때문이다. 이에 반해 OS 없이 프로그래밍하는 펌웨어 프로그래머들은 아비규환의 세계에 빠져들 수밖에 없다. 대부분을 직접 구현해야 하기 때문이다. 각 장치의 디바이스 드라이버부터 필요하다면 멀티태스크 기능까지 말이다. 한번 상상해 보라. 프로그램의 엄청난 양의 증가를 ...
8비트 원칩 프로세서에 내장되는 소프트웨어
8비트 원칩을 사용하는 임베디드 시스템은 무척 간단하다. 그렇다고 기능이 간단한 것은 아니다. 필자의 프로그램 경험에 의하면 작다고 해서 있을 게 없는 경우는 없다. 눈, 코, 귀 다 달아 주어야 한다. 문제는 8비트 원칩에 넣을 수 있는 프로그램 크기가 제한되어 있고 속도도 느리다는 것이다. 이 때문에 주로 사용되는 언어가 어셈블러이다. 최근에는 C를 사용하기는 하지만 여러 가지 이유에 의해 주로 어셈블러를 사용한다. 어셈블러는 C 같은 언어와 다르게 프로세서마다 문법이 다르다. 때문에 하나의 프로세서를 안다고 해서 다른 프로세서도 동일하게 다룰 수 있는 것은 아니다.
이렇게 작은 용량의 기억장소를 갖는 프로세서에 OS를 갖춘다는 것은 말도 안 되는 것이다. 그래도 프로그래머에 따라서는 나름대로의 멀티태스크 기능을 간단하게 짜서 수행하기도 한다. 오랜 경력의 프로그래머들은 8비트 원칩 프로세서를 위한 어셈블러 매크로 라이브러리나 소스 라이브러리를 갖춘다. 그래서 초짜 프로그래머들이 한달 걸릴 일을 경우에 따라서는 한두 시간 만에 해치우기도 한다. 한마디로 슈퍼 프로그래머들이 있는 것이다.
8비트 프로세서를 다루는 프로그래머들의 구호는 ‘좀더 빠르게 좀더 작게’라는 말로 대변된다. 워낙 프로세서 자체가 느리게 동작하기 때문에 속도가 필요한 경우 온갖 꼼수(좋은 말로 ‘테크니컬’이라는 말들을 사용한다)를 부리기 마련이다. 즉, 명령 하나를 쓸 때마다 시간 계산을 하면서 한다는 것이다. 여기에 요구되는 기능이라도 많아지면 동일한 기능을 하면서 작은 코드를 발생하게 해야 한다. 필자가 본 프로그래머 중에서 이런 부분을 가장 고민하는 임베디드 프로그래머로는 보드 게임 프로그래머가 최고였던 것 같다.
이런 상황이다 보니 코드 가독성에 대한 고려나 유지 보수에 대한 최소한의 배려마저 포기할 때가 많다. 어떤 관리자가 감히 이런 것을 요구할까. 그랬다가는 돌 맞는다. 또 이런 임베디드 프로그램을 작성하는 경우에는 보드 개발부터 프로그램까지 모두 혼자 하는 경우가 비일비재 하다. 국내에서 제어기기를 제작하는 업체 대부분이 소규모 회사이기도 하고 여러 명이 공동으로 작업하는 것이 도리어 능률을 떨어뜨리는 경우가 많기 때문이다. 더구나 필자는 하드웨어 팀과 소프트웨어 팀이 나눠져서 개발하는 경우 두 개발자간의 사이가 좋은 경우를 보지 못했다.
32비트 원칩 프로세서의 등장
8비트 원칩 프로세서는 저가의 대량 생산용 임베디드 시스템에 32비트 프로세서는 고가의 고기능 임베디드 시스템에 사용된다는 규칙은 시대가 변하면서 변하기 시작했다. 왜냐하면 임베디드 시스템 시장의 요구가 달라졌기 때문이다. 사용자들은 PC라는 편리한 시스템에 길들여지기 시작했고 다른 시스템에도 이러한 편리성을 요구하기 시작했다. 한마디로 가격은 싸면서 기능은 PC급을 요구하기 시작한 것이다. 더구나 8비트 원칩 프로세서로는 구현이 힘든 사용자 화면을 요구하기 시작한 것이다. 그밖에 구현해야 하는 기능 역시 매우 복잡해지기 시작한 것이다. 간단하게 냉장고만 예로 들더라도 예전에는 문을 열었을 때 램프를 켜고 온도에 따라 송풍기나 에어컨 구동 모터를 동작시키면 되던 것이 LCD를 달아서 내부 상태를 보여주더니만 급기야 웹까지 구현하기를 요구하게 된 것이다. 공장 자동화 기기들은 어떤가. 요즈음은 네트워크 통신은 거의 기본이 되었다(이런 요구의 틈새 시장을 노린 것이 시리얼과 네트워크 변환 모듈들이다).
이런 고급화된 기능을 기존 32비트 프로세서를 사용해 구현해도 되지만 주변에 추가해야 할 디바이스가 나날이 늘어나서 저가를 형성하는 임베디드 시스템에 적용하기에는 너무도 무리가 따르기 시작했다. 설상가상으로 환경 문제와 에너지 문제가 대두되면서 초절전 시스템을 요구하는 것이다. 개발자들은 이런 시대적 상황 변화에 기민하게 대처해야 하는데 기존 프로세서로는 해결이 거의 불가능한 것이다. 그래서 나온 꼼수가 PC 시스템을 내장하는 것이었다. 문제는 PC 시스템을 제대로 사용하려면 OS를 사용해야 하는데 당시 윈도우 98 계열밖에 없던 터라 개발자는 시스템 다운에 하루하루를 걱정하며 살아야 하는 악몽같은 세월을 거치기도 하였다. 더구나 PC 시스템을 탑재할 수 있는 경우는 덩치 큰 기기들이지 소형 시스템에는 꿈도 못 꾸는 것이다. 이런 상황에 나타난 것이 32비트 원칩 프로세서이다.
기존 32비트 프로세서의 속도에 많은 주변 디바이스를 아예 칩 자체에 내장하고 나타난 것이다. 기존 32비트 프로세서를 사용하기 위해서는 주변에 필요한 프로세서를 보드에 다닥다닥 붙여야 했던 것과는 달리 칩 안에 모두 내장해 버리니 소형 시스템에도 사용할 수 있는 것이다. 더구나 회로 배선과 패키지 기술 발달에 힘입어 8비트 프로세서보다 작아지는 기현상도 발생했다. 이런 32비트 프로세서 역시 대량 생산이 되지 않았다면 가격이 매우 높게 책정될 수밖에 없기 때문에 지금과 같은 32비트 원칩 프로세서가 대중적으로 사용되기는 힘들었을 것이다. 그러나 PDA와 셋톱박스 그리고 네트워크 장비에 사용되면서 대중화되기 시작한 것이다. 또 한 가지 대중화가 가능한 요인으로는 ‘프로세서 코어’라는 개념이 등장하면서 프로세서 생산의 독점 시대가 종말을 고하기 시작한 것을 들 수 있다. 예전에는 ASIC을 생산할 수 있는 공장이 있더라도 프로세서를 만드는 것은 요원했다. 왜냐하면 프로세서를 구현하는 CPU 로직을 만들 소프트웨어 기술이 없었기 때문이다. 삼성전자마저도 D램 시장에서는 선두를 다툴지 몰라도 생산되는 CPU라고 해 봤자 4비트나 8비트 수준이 한계였기 때문이다. 또 기껏 만든다고 해도 기존 시장의 아성을 무너뜨리기에는 한계가 있었다. 누가 특별한 매력이 없는 상황에서 시장에서 검증(?)되지 않은 프로세서를 사겠는가?
그런데 PowerPC라든가 ARM이라든가 하는 프로세서 소프트웨어를 라이선스해 판매하면서 이러한 프로세서 생산 독점 시대도 막을 내리게 된다. 필자가 조금 자신있게 알고 있는 ARM이라는 프로세서는 실제로 제품으로 존재하지 않는다. 단지 소프트웨어 프로세서일 뿐이다. 이 프로세서 코어를 사서 삼성에서 만든 것이 S3C2410과 같은 모델명을 갖는 프로세서이다. 요즘 많이 알려져 있는 인텔의 스트롱암(StrongARM)이나 XScale 역시 원초적인 코어는 ARM에서 제작된 것이다(XScale의 족보는 이것보다 조금 복잡한데 이 이야기는 여기서 굳이 하지 않으려 한다). 즉, ASIC 생산이 가능한 회사라면 누구나 실제 프로세서를 구현하는 프로세서 로직을 라이선스해 프로세서를 생산할 수 있게 된 것이다.
이런 저런 이유로 인해 32비트 원칩 프로세서들은 대중화되기 시작했고 이제는 임베디드 시스템 개발자라면 언젠가는 해야 할 프로세서로 등장하게 된 것이다. 문제는 개발자들이다. 이들의 본격적인 고민이 시작된다.
임베디드 프로그래머와 부트로더-2
- 저 자 : 유영창
- 출판일 : 2003년 10월호
임베디드 시스템 개발자의 고민
필자가 다니는 회사의 한 개발자는 처음 배운 프로세서가 32비트 원칩 프로세서이다. 특정한 장비 개발 건으로 8비트 원칩을 다룰 일이 생겼는데 프로세서 매뉴얼을 보더니만 씨익 웃고서 “정말 간단한 구조를 가졌네요” 하는 것이다. 필자는 조금 놀랐다. 왜냐하면 예전에 8비트 원칩을 보고 매우 복잡하다는 생각을 많이 했기 때문이었다. 물론 이전에는 Z-80이나 6502 같은 좀더 단순한 프로세서만 보다가 8비트 원칩을 보았기 때문이기도 하다.
문제는 필자와 같이 기존 8비트 원칩 프로세서를 다루던 개발자들이 32비트 원칩 프로세서를 보면 매우 복잡해 보인다는 것이다. 8비트 원칩 프로세서가 다루던 4MHz와는 차원이 다르게 클럭 속도가 50~400MHz 프로세서를 보면 기가 질릴 정도다. 더구나 이 글을 쓰기 바로 전 달에 삼성에서는 500MHz의 CPU를 개발했다는 소식을 전하고 있다. 사용되는 주변 메모리도 내장되어 있던 것과 달리 SDRAM처럼 조금은 복잡한 디바이스들이 붙는 것이다.
하드웨어 개발자가 이러할진대 해당 칩에 펌웨어를 넣어야 하는 프로그램 개발자들은 어떠하겠는가. 그냥 한숨만 나올 뿐일 것이다. 단순하게 기존 8비트 원칩 프로세서에서 만들던 정도의 기능만을 구현하면 그나마 덜하겠지만 TCP/IP 네트워크 기능이라도 붙인다거나 GUI 화면을 구성하거나 PCMCIA와 같은 디바이스를 붙인다면 막막함 그 자체일 것이다. 임베디드 시스템 프로그래머들이 단순하게 펌웨어 자체만 열심히 짠다고 해결되는 것이 아닌 것이다. 그나마 인터넷의 오픈 소스 운동에 더불어 소스포지 같은 곳에서 남들이 열심히 작성한 소스를 가지고 와서 이리 맞추고 저리 맞추고 하기는 하지만, 개발 기간이 짧기로는 세계 2위로 치부해도 서운한 우리나라에서 프로그램 개발자들의 고충은 이만저만한 것이 아니다.
그렇다고 회사가 부유하거나 개발하려는 제품의 단가가 높아서 상용 OS를 사용한다면 그나마 천국에서 일하는 것이겠지만 열악한 환경에서 오늘도 밤 새워가며 고군분투하는 우리나라 프로그래머들에게 상용 OS를 사용한다는 것은 정말 꿈같은 일인 것이다. 대부분의 경영자는 그렇지 않겠지만 일부 몰지각한 경영자들은 좋은 프로세서가 있는데 왜 개발이 늦어지고 품질이 안 나오느냐고 윽박지르기만 하고 있으니 개발자들은 하루하루 비탄의 나날을 살 수밖에 없다. 이런 프로그래머들에게 한줄기 서광이 비추기 시작한 것이 바로 리눅스의 등장이다.
리눅스와 임베디드 시스템
요즘에 웬만한 사람이라면 알고 있는 리눅스는 초기에 PC에서 사용되는 프로세서인 인텔 i386 프로세서 계열만 지원했다. 그러다가 다른 CPU에 포팅을 하면서 아키텍처 의존적인 코드들이 정리되기 시작했다. 이런 정리가 이뤄진 이후에 리눅스 해커들은 자신이 사용하는 시스템의 프로세서에 하나 둘씩 이식해 가는 숫자를 늘려 갔고 덕분에 리눅스 커널은 다른 아키텍처에 이식하기 쉬운 구조가 되었다.
그러나 초기에는 고기능의 프로세서에 커널 포팅을 수행했기 때문에 주로 알파 프로세서 같은 고성능 프로세서에만 포팅이 가능했다. 더군다나 아무리 이식성이 우수하다고 해서 8비트 원칩 프로세서와 같은 계열에 포팅한다는 것은 말도 안된다. 16비트 프로세서인 인텔 286에 포팅하는 프로젝트도 존재하기는 하지만 지금은 진행이 미미한 상태이다. 즉 어느 정도 프로세서가 받쳐 주어야 한다는 것이다. 리눅스 커널이 필요로 하는 최소 사양이 32비트 처리 능력과 최소한 메모리 4MB 이상은 되어야 한다. 이것도 최소 사양일 뿐 실제 사용 한다는 것은 무리이다. 하지만 32비트 원칩 프로세서가 등장하면서 상황은 달라졌다. 리눅스 커널이 동작할 수 있는 성능이 보장됐기 때문이다.
필자가 보기에 오픈소스란 개념은 단순하게 소프트웨어만 적용되는 개념이 아닌 듯하다. 예전에 프로세서가 새로 나오면 해당 프로세서의 자료를 구하기는 ‘하늘의 별 따기’였다. 대량으로 소비할 수 있는 회사에 근무하는 개발자들은 이런 소리가 뭔 소린가 하는 생각이 들겠지만 기껏 해봐야 서너 개 사서 개발해야 하는 개발자들은 프로세서 판매자에게 아부에 가까운 아양을 떨어야 매뉴얼이라도 구할 수 있었다. 더구나 참고할 수 있는 레퍼런스 회로도를 구하는 것은 정말 꿈같은 일이기도 했다. 하지만 최근에 판매되는 프로세서들은 해당 프로세서 생산 회사의 홈페이지에 가면 레퍼런스 회로도나 초기 구동용 펌웨어 소스를 쉽게 구할 수 있다. 물론 프로세서에 따라 다르겠지만 기존에 폐쇄적으로 나오던 회사마저 공개로 방향을 바꾸고 있다는 소식이다. 즉, 하드웨어 관련 정보도 오픈소스처럼 공개하는 것이 하나의 추세가 되어가는 것이다. 결국 개발자들이 쉽게 접할 수 있는 프로세서가 시장에 살아 남는다는 것을 프로세서 생산자들도 깨닫게 된 것이다.
이런 공개된 정보 중에 프로세서를 지원하는 리눅스 커널 소스를 공개하는 것도 하나의 흐름이 되었다. 그렇다면 프로세서 생산자들이 왜 리눅스 커널 소스를 공개하고 있는 것일까? 이유는 단 한가지라고 본다. 그만큼 임베디드 시스템 개발자들이 리눅스 커널을 선호하기 때문이다. 굳이 사용하지 않는다면 공개할 이유가 없으니까. 그렇다면 왜 임베디드 개발자들이 리눅스 커널을 사용하려는 것일까?
다른 곳에서 많이 소개되기도 했지만 한번 되짚어 보자. 공짜라는 개념은 집어 치우자. 실제로 커널 소스에 대한 가격은 지불하지 않지만 실제로 개발하다 보면 분명히 그만큼의 돈이 든다(시간도 돈이다). 지금부터 기술하는 내용은 필자가 실제로 개발하면서 느낀 점이다.
소스가 공개되어 있다
프로그래머들은 의외로 남의 코드를 믿지 않는다. 자신은 절대 아니라고 하지만 어떤 문제가 생기고 자신이 이해하기 힘든 증상이 나타나면 거의 대부분의 프로그래머들은 자신의 코드를 의심하기보다는 주변을 의심한다. 필자는 프로그래머가 고수인지 하수인지를 구별할 때 기준을 자신의 프로그램 버그 가능성과 다른 사람 프로그램 버그 가능성에 대해 의심하는 비율로 나눈다. 다른 사람의 버그 가능성에 대한 의심 비율이 70% 이상이면 그 사람은 분명히 하수다. 반대로 자신의 버그 가능성에 대한 의심 비율이 70% 이상이면 그 사람은 고수다. 그래서 윈도우 98의 에러 메시지를 만든 사람은 필자가 보기에 하수일 가능성이 높다. 시스템에 문제가 생기면 이런 메시지가 뜨기 때문이다.
“응용 프로그램의 잘못된 연산으로 인하여 프로그램이 종료되었습니다.”
이런 의심 많은 프로그래머들에게 소스가 공개되어 있다는 것은 곧 신뢰성과 연관될 수 있다. 또 문제가 생기면 수정할 수도 있다는 것이다. 즉 문제에 대한 제어권이 개발자 자신에게 있는 것이다. 이것은 개발자 입장에서 보면 너무도 중요한 점이다. 문제가 생겨도 해결할 가능성이 아주 조금이라도 있기 때문이다.
많은 디바이스 드라이버 소스가 포함되어 있다
임베디드 시스템을 개발하는 프로그래머의 최대 고민은 주변 디바이스의 제어 프로그램을 작성하는 것이다. 매뉴얼이 아무리 자세하게 나오더라도 디바이스 제어 루틴을 짜면서 고생하지 않은 프로그래머들은 거의 없을 것이다. 더구나 대부분의 매뉴얼이 영어라서 부정문에서 한번 헛갈리면 별 것 아닌 문제로 며칠을 낭비하는 경우가 종종 있기 때문이다. 이런 디바이스의 제어에 필요한 웬만한 소스가 커널에 존재하고 있기 때문에 리눅스 프로그래머는 처음부터 다시 작성해야 하는 경우가 매우 적어진다. 필자가 임베디드 시스템에 리눅스를 적용하면서 처음부터 고생하면서 다시 작성해야 했던 디바이스는 몇 개 안된다. 이것 하나만으로도 리눅스 커널을 임베디드에 사용하는 필자는 본전을 뽑았다.
응용 프로그램이 하드웨어 구조에 영향을 적게 받는다
OS 없이 개발하던 펌웨어의 경우에는 응용 프로그램과 OS의 개념 구분이 없기 때문에 항상 새로운 하드웨어를 도입하게 되면 처음부터 다시 작성해야 한다는 마음을 먹어야 한다. 물론 오랜 경험 끝에 이렇게 다시 작성해야 하는 경우를 최소한으로 하려고 최선을 다하지만 프로세서나 하드웨어 구조가 바뀌면 처음부터 새로 구축하고 싶은 유혹을 떨쳐 버리기 힘들다.
하지만 리눅스 커널을 사용한다면 새로 작성해야 하는 부분이 상당히 준다. 즉 응용 프로그램 소스는 거의 손대지 않아도 된다는 것이다. 왜냐하면 하드웨어에 관련된 부분은 거의 커널 수준에서 맞춰주면 된다. 또 실제로 하드웨어가 바뀌더라도 가급적이면 응용 프로그램의 수정이 없도록 디바이스 드라이버 단에서 맞춘다. 임베디드 시스템 개발 기간의 대부분이 응용 프로그램 작성으로 소모된다는 점만 보면 이것은 개발 생산성이라는 측면에서 엄청난 이득이다.
실제 하드웨어가 없어도 응용 프로그램을 구현할 수 있다
임베디드 시스템의 개발 스케쥴을 보면 대부분 하드웨어가 준비된 이후에 소프트웨어 개발을 진행하는 방식을 택한다. 그만큼 하드웨어 의존적인 코드가 많기 때문에 미리 하기가 힘들고 시험 자체도 검증되지 않기 때문이다. 그나마 가상 코드라고 해서 미리 컴파일 에러만 잡고 로직을 만들어 가는데 이것도 결국 하드웨어가 나온 후 동작시키면 엄청나게 수정되는 경우가 비일비재하다. 그래서 하드웨어가 나오기 전에 개발은 별 의미없는 경우가 많다.
하지만 리눅스 커널이 동작하는 환경이라면 PC나 임베디드 시스템이나 응용 프로그램 구현 방식이 같다. 필요한 하드웨어 제어는 디바이스 드라이버의 접근에 대한 처리를 에뮬레이션 형태로 해주면 대부분의 로직이 검증되기 때문이다. 필자 역시 이 방식을 즐겨 사용한다. 결국 개발 시간을 잡아먹는 것은 응용 프로그램 제작이기 때문이다. 그리고 하드웨어 검증까지 지켜보면 결국 시간에 쫓겨서 고생하는 것은 프로그래머 몫이다.
참조할 만한 응용 프로그램들의 소스는 대부분 리눅스 애플리케이션 소스다
간단한 응용 프로그램이야 별 문제 없지만 동영상 구현같이 복잡한 애플리케이션을 처음부터 자신이 만들어 간다면 엄청난 시간이 걸릴 것이다. 그런데 이런 기능이 구현된 소스는 인터넷에서 쉽게 구할 수 있다(물론 필요한 기능에 따라 다르겠지만). 문제는 이런 응용 프로그램 대부분이 리눅스에서 동작하는 경우가 많다는 것이다. 그래서 필히 리눅스 프로그램 방식에 익숙하지 않으면 고생하기 십상이다. 결국 리눅스 커널을 도입한다는 것은 리눅스 프로그램 방식에 익숙해지는 것이기 때문에 아무래도 일반 펌웨어 프로그래머보다는 유리하다.
앞에서 열거한 니용 외에도 많은 장점이 있다. 이 때문에 전 세계의 많은 개발자들이 리눅스 커널을 임베디드 시스템에 적용하는 것이다.
임베디드 프로그래머와 부트로더-3
- 저 자 : 유영창
- 출판일 : 2003년 10월호
리눅스 커널의 구동 환경
앞서 잠깐 언급했지만 임베디드 시스템에서 리눅스 커널이 원활하게 동작하기 위한 최소 조건이 있다고 했다. 물론 절대적인 것은 아니지만 필자의 현장 경험상 다음과 같은 조건은 충족돼야 한다.
- 32비트 프로세서
- 8MB 램(일반적으로 SDRAM을 사용한다)
- 8MB 롬(일반적으로 플래시를 사용한다)
- 시리얼 포트 1개
이 정도 사양만 있어도 리눅스 커널이 동작한다. 더 이하 사양의 시스템에 리눅스 커널을 올려 동작시킬 수도 있지만 응용 프로그램이 조금만 커져도 문제가 발생하고 프로그램 작성시 상당한 주의를 요한다.
여기에 한 가지 더 조건을 붙인다면 MMU(Memory Management Unit) 즉 메모리 관리 장치의 지원이다. 필자 개인적인 입장으로 본다면 개발의 편리성이나 기존 리눅스 커널의 응용 프로그램을 원활하게 동작시키려 한다면 거의 필수 개념이 아닐가 싶다. 이 MMU 없이 리눅스 커널이 동작하는 프로젝트로 uCLinux가 있는데 실제로 이것을 사용해 본 경험에 의하면 다시는 사용하고 싶지 않았다. 그만큼 제약 사항이 많아서 개발시에 무척 고생하게 된다. 제품의 단가 측면이 절대적이지 않다면 몇 푼 더 들어가더라도 개발하기 편한 것이 결국은 이익으로 연결될 수 있다. 주위에서 몇 푼 아끼려다 개발 진행이 늦어져서 개발에 실패한 경우도 종종 보았다.
앞서 열거한 리눅스 커널이 동작하기 위한 하드웨어적인 조건 외에 소프트웨어적인 조건도 있다. 부트로더라는 프로그램의 존재가 바로 그것이다.
리눅스와 부트로더
대부분의 임베디드 시스템의 프로그램들은 롬에서 동작한다. 이 롬은 보통 펌웨어 전용 프로그램들이 탑재되는데 대부분 한번 넣으면 거의 고정이다. 즉, 펌웨어 프로그램이 바뀌지 않는 이상 동작되는 프로그램이 롬의 내용을 변경하지 않는다. 롬(ROM)이라는 말 자체가 Read Only Memory라는 것도 바로 이 때문이다. 하지만 최근에 사용되는 롬들은 플래시 타입이 주종을 이룬다. 플래시의 가장 큰 특징은 읽고 쓰기가 가능하다는 것이다. 플래시가 읽고 쓰는 기능과 롬처럼 동작하는 기능을 지원하는 플래시를 플래시 롬(NOR FLASH)이라고 한다. 이와 반대로 우리가 알고 있는 USB 보조 기억 장치나 디지털 카메라에 사용되는 플래시들은 읽기 쓰기는 되지만 롬으로 사용할 수 없는 NAND 플래시들이다.
롬에 프로그램을 집어 넣어서 처리하는 것이 이전 임베디드 시스템에서는 문제가 되지 않았다. 하지만 32비트 고속 프로세서에 사용되면서 이 롬의 처리 속도가 너무 느린 것이 문제가 되었다. 때문에 요즈음의 고속 프로세서들은 프로그램이 롬에서 수행되지 않는다. 대부분 롬에 있는 프로그램의 내용을 램에 복사해 넣은 후 실제 프로그램들은 램에서 수행된다. SDRAM 같은 메모리 소자들은 그 동작 주파수가 100MHz로 매우 빠르기 때문에 접근 속도 역시 매우 빠르다. 이 때문에 롬에 있는 내용을 램에 써 넣은 후 램에서 프로그램을 동작시킨다. 리눅스 커널 역시 매우 빠르게 동작해야 하기 때문에 대부분 램에서 수행한다. 때문에 커널을 램으로 이동시켜 주는 프로그램이 필요하다.
또 한 가지 기존 리눅스 커널이 동작하는 방식을 살펴봐야 리눅스 커널이 동작하는 소프트웨어 조건에 대해 이해할 수 있다. 커널은 원칙적으로 PC 기반에서 만들어진 것이다. 이 PC 기반의 리눅스가 동작하는 과정을 살펴보면 다음과 같다.
[1] PC 부팅 시작 [2] 바이오스 루틴 수행 [3] 하드디스크 부팅 [4] 압축된 리눅스 커널 이미지를 하드디스크에서 램으로 복사 [5] 압축된 리눅스 커널 이미지 풀기 [6] 리눅스 커널 동작 [7] 하드디스크 루트 디렉토리에 마운트 [8] 실제 운영체제를 위한 응용 프로그램 수행
수행되는 과정을 살펴보면 처음부터 리눅스 커널이 수행되는 것이 아니다. 즉 다른 소프트웨어가 수행된 이후에 최종적으로 리눅스 커널이 수행되는 것이다. 임베디드 시스템에서 리눅스 커널이 동작하는 것도 유사하다.
[1] 보드 부팅 시작 [2] 부트로더 수행 [3] 압축된 리눅스 커널 이미지를 롬에서 램으로 복사 [4] 압축된 램 디스크 이미지 를 롬에서 램으로 복사 [5] 압축된 리눅스 커널 이미지 풀기 [6] 리눅스 커널 동작 [7] 압축된 램 디스크 이미지 를 풀고 루트 디렉토리에 마운트 [8] 실제 운영체제를 위한 응용 프로그램 수행
이 과정에서 ?, ?, ? 과정을 수행하는 것이 부트로더가 수행해야 할 핵심 요소이다.
부트로더의 실체
부트로더(BOOTLOADER)는 BOOT+LOADER라는 단어의 합성어이다. 각 단어를 원래대로 해석한다면 조금 이상한 뜻이다. ‘장화+짐을 싣는 사람’이란 뜻이 되어 버린다. 영 말이 안 된다. 당연히 두 단어는 프로그래머들에게 통하는 속어(?)다. 부트는 시스템 프로그램의 초기 동작을 의미하고 로더는 실제 운영체제를 시스템에 올린다는 뜻이다. 즉 시스템의 초기 동작을 수행한 후에 실제 운영체제를 동작시켜 주는 프로그램을 부트로더라고 한다. 간단한 임베디드 시스템에서는 굳이 올려야 할 운영체제가 필요 없기 때문에 부트로더니 커널이니 응용 프로그램이니 하는 구별이 없다. 하지만 리눅스 커널을 사용한다면 다음과 같은 구성을 갖는다.
[1] 부트로더 프로그램 [2] 리눅스 커널 프로그램 [3] 루트 디바이스 [4] 응용 프로그램 [5] 셀 및 기타 유틸리티 프로그램
이 각각의 구성을 맞추어 주어야 동작한다. 우리가 이번 호부터 관심을 갖고 만들려고 하는 프로그램이 바로 부트로더이다. 왜 부트로더라는 프로그램을 제작해야 할까? 이제 앞서 말했던 내용과 그 외의 이유에 대해 차근차근 정리해 보면서 살펴보자. 다음은 부트로더가 가져야 할 기능들이다.
하드웨어 디버깅과 시스템 초기화 기능
필자가 느끼기에 소프트웨어 팀이 프로그램의 문제점을 찾는 것보다 하드웨어 팀에서 보드의 문제점을 찾아내는 것이 더 어려운 것 같다. 그 이유로는 상대적으로 열악한 디버깅 툴과 논리상으로는 예상할 수 없는 물리적인 문제점이 발생하기 때문인 것 같다. 소프트웨어보다는 하드웨어가 좀더 경험에 의한 검증에 의존하는 수밖에 없는 이유가 이 때문이다. 물론 하드웨어는 한번 동작하면 특별한 경우가 아니면 동일한 동작을 수행하기 때문에 장기적으로 보면 하드웨어 팀이 더 편하기는 하다. 소프트웨어는 납품하거나 시장에 판매되는 시점부터 문제점이 보고되는 경우가 비일비재하기 때문이다.
그런데 필자가 보기에 한 가지 특이한 것은 하드웨어 설계자 중에는 의외로 프로그램을 작성하는 능력이 없는 사람이 많다는 것이다. 회로 설계는 기막히게 잘하고 하드웨어적인 문제도 이상할 만큼 잘 잡아내지만 보드 테스트용 프로그램 하나를 제대로 작성하지 못해 고민하는 하드웨어 개발자를 종종 본다. 이렇게 하드웨어 팀과 소프트웨어 팀이 나눠지면 같은 프로젝트를 진행하는 데 거의 앙숙 관계가 되어 버린다. 서로를 이해하지 못하기 때문에 생기는 문제다. 그래서 하드웨어와 소프트웨어를 모두 잘 다루는 개발자는 현장에서 환영받는다. 문제점이 생길 때마다 대처가 빠르기 때문이다.
하드웨어 제작 과정에서 설계가 끝나고 가장 처음 시작하는 것이 프로세서를 동작시키는 것이다. 프로세서를 동작시키면서 그 나머지 회로의 이상 유무도 검증해야 하고 아트워크에 문제점이 있는지, 전기적인 특성은 제대로 나오는지를 검사해야 한다. 설계대로 착착 동작하면 별 문제가 없는데 실제 현장에서 한방에 모든 것이 끝나는 것을 필자는 본 적이 없다. 이때 프로세서를 정상적으로 동작시키려면 테스트용 프로그램을 만들어야 한다. 이 테스트용 프로그램에는 기본적인 프로세서의 환경 설정 루틴이 들어간다. 프로세서 자체의 클럭 속도를 설정하고, 주변 메모리 소자를 위한 디바이스 설정을 하며, 주변에 연결된 디바이스 역시 초기화해야 한다.
초기화 과정에서 정상적으로 동작하지 않을 때 개발비가 부족한 대부분의 개발자들은 장비보다는(실제 검증 장비들이 그리 큰 도움이 되지도 않는다) 프로그램을 이용해 시스템 동작의 이상에 따른 하드웨어 검증 루틴을 만들어야 한다. 이상하게 동작하는 디바이스에 반복적으로 접근해 일정한 검증 패턴을 만들거나 디바이스의 제어 레지스터에 대한 테스트 데이터를 넣게 하는 기능들이다. 이런 과정에서 부트로더는 자연스럽게 다음과 같은 기능을 갖는다.
◆ 프로세서 초기화 루틴 ◆ 프로세서 동작 환경 설정 루틴 ◆ 프로세서 동작 환경 검사 루틴 ◆ 주변 디바이스 초기화 루틴 ◆ 주변 디바이스 이상 유무 검사 루틴
임베디드 프로그래머와 부트로더 -4
- 저 자 : 유영창
- 출판일 : 2003년 10월호
상태 표시·메시지 표출·명령 입력 및 처리 기능
보드를 시험하는 과정에서 진행된 위치나 상태를 알려주는 기능은 거의 필수적이다. 예전에 동료 한 사람이 펌웨어 프로그램에 자칭 청진기 기능이라고 하는 메모리나 기타 상태를 보여 주는 기능을 항상 넣어 놓고 프로그램을 디버그했다. 당시에는 에뮬레이터가 일반화되어 있지 않기 때문에 이런 기능을 넣어야 했다. 지금도 상황에 따라 이런 기능을 넣어야 프로그램 디버깅이 편하다. 초기에 시리얼 디바이스 초기화가 힘든 상태에서는 LED를 점멸시키거나 LED를 이용해 진행 표시를 한다.
이후 시리얼 디바이스가 초기화되면 시리얼 디바이스를 이용해 프로그램 상태나 기타 메시지를 표출한다. 또한 특정한 문자열을 입력받은 후 수신된 입력 문자열을 명령으로 해석하고 이에 대한 처리 결과를 표출하는 기능 또한 유용하다. PC에서 바이오스 셋업 프로그램을 보면 비디오 장치와 키보드를 이용해 초기 상태를 설정하는 것도 이런 류에 포함된다. 임베디드 시스템은 비디오 장치가 없으므로 대부분 시리얼을 이용해 이런 기능을 구현한다.
시리얼 통신으로 메시지를 표출할 때는 보통 C 함수의 printf문을 구현하는 것이 일반적이다. 왜냐하면 프로그램 방식이 매우 친숙하기 때문이다. 물론 printf 함수 자체가 무척 복잡한 루틴이라서 프로그램 크기가 매우 커지는 단점이 있다.
대부분의 부트로더는 자체적으로 보드를 시험할 수 있는 명령 셋을 가지고 있는데 이를 모두 외우기 힘들므로 간단한 도움말 기능을 제공한다. 이런 내용을 구현하기 위해 부트로더는 다음과 같은 기능을 갖는다.
- 시리얼 입출력 루틴
- C 표준 입출력 함수
- 토큰 분리 함수
- 명령 해석 함수
- 명령 수행 함수
- 도움말 함수
- 실행 이미지와 데이터 이미지 다운로드 기능
- PC에서 동작하는 프로그램을 작성하다가 임베디드 시스템에서 동작하는 프로그램을 작성하면서 가장 피곤한 것이 컴파일 위치와 수행 위치가 다르다는 점이다. 그나마 지금은 많이 나아졌지만 필자가 처음 8비트 원칩 프로세서에 프로그램할 때는 거의 ‘죽음’ 수준이었다. 롬 이레이저가 있었으나 이곳에 롬을 깔아 놓고 전원을 켠 후 대략 30분 정도 있어야 롬에 프로그램이 가능했다. 롬에 프로그램을 굽고 나서 보드에 꽂고 문제가 생기면 다른 롬에 프로그램 굽는 과정을 반복하다 보니 30분 이상이 걸리는 롬 지우는 과정을 또 거쳐야 했다. 아주 간단한 파라미터를 수정하면서 시험하면 롬을 굽다가 시간을 모두 허비하는 경우가 태반이었다. 요즘에야 프로세서 에뮬레이터들이 많아지고 롬 에뮬레이터가 나와서 예전 같은 고역은 많이 줄었지만 PC에서와 같이 컴파일하고 바로 실행해 보는 환경에 비하면 너무도 열악한 개발 과정이었다. 최근에 32비트 원칩 프로세서를 이용한 보드에서 롬으로 이용하는 메모리 디바이스로는 플래시 롬을 주로 사용한다. 이 플래시 롬은 수만 번을 썼다가 지울 수 있는 특성이 있다. 이런 기능을 이용해 플래시 롬에 수행돼야 할 프로그램도 써 넣고 프로그램에서 사용하는 데이터도 써 넣는다. 최초에는 당연히 보드에 프로그램이 넣어지지 않기 때문에 멀티 ICE나 JTAG 장비 또는 직접 만든 디버거 툴로 플래시에 부트로더 프로그램을 써 넣는다. 그러나 일단 부트로더가 수행되기 시작하면 부트로더 자체에 플래시 롬에 데이터를 써 넣는 기능을 추가할 수 있다. 개발 호스트 컴퓨터에서 시리얼 통신이나 이더넷 통신 기능을 이용해 이미지를 다운로드한 이후에 플래시 롬에 써 넣는다. 이런 환경이 갖춰지면 보드의 프로그램 개발 속도는 한층 가속이 붙는다. 특히 임베디드 시스템에 리눅스를 이용할 때에는 리눅스 커널 자체 크기도 1MB에 달하고 램 디스크 이미지 크기도 2MB 이상을 넘는 것이 보통이므로 단순하게 시리얼로만 다운로드하면 전송 속도가 매우 느려서 개발이 무척 힘들어진다. 그래서 단가 문제로 인해 최종 제품에는 이더넷을 빼더라도 개발 단계의 보드에서는 꼭 집어 넣는다. 이렇게 보드에 부여된 통신 기능을 이용해 롬에 써 넣어야 할 데이터를 수신받고 이를 롬에 써 넣는 기능이 부트로더가 기본적으로 가져야 할 기능이다. 부트로더가 가져야 할 기능을 요약하면 다음과 같다.
- 이미지 다운로드 통신 루틴
- 플래시 써 넣기 및 데이터 검사 루틴
- PC에서 동작하는 프로그램을 작성하다가 임베디드 시스템에서 동작하는 프로그램을 작성하면서 가장 피곤한 것이 컴파일 위치와 수행 위치가 다르다는 점이다. 그나마 지금은 많이 나아졌지만 필자가 처음 8비트 원칩 프로세서에 프로그램할 때는 거의 ‘죽음’ 수준이었다. 롬 이레이저가 있었으나 이곳에 롬을 깔아 놓고 전원을 켠 후 대략 30분 정도 있어야 롬에 프로그램이 가능했다. 롬에 프로그램을 굽고 나서 보드에 꽂고 문제가 생기면 다른 롬에 프로그램 굽는 과정을 반복하다 보니 30분 이상이 걸리는 롬 지우는 과정을 또 거쳐야 했다. 아주 간단한 파라미터를 수정하면서 시험하면 롬을 굽다가 시간을 모두 허비하는 경우가 태반이었다. 요즘에야 프로세서 에뮬레이터들이 많아지고 롬 에뮬레이터가 나와서 예전 같은 고역은 많이 줄었지만 PC에서와 같이 컴파일하고 바로 실행해 보는 환경에 비하면 너무도 열악한 개발 과정이었다. 최근에 32비트 원칩 프로세서를 이용한 보드에서 롬으로 이용하는 메모리 디바이스로는 플래시 롬을 주로 사용한다. 이 플래시 롬은 수만 번을 썼다가 지울 수 있는 특성이 있다. 이런 기능을 이용해 플래시 롬에 수행돼야 할 프로그램도 써 넣고 프로그램에서 사용하는 데이터도 써 넣는다. 최초에는 당연히 보드에 프로그램이 넣어지지 않기 때문에 멀티 ICE나 JTAG 장비 또는 직접 만든 디버거 툴로 플래시에 부트로더 프로그램을 써 넣는다. 그러나 일단 부트로더가 수행되기 시작하면 부트로더 자체에 플래시 롬에 데이터를 써 넣는 기능을 추가할 수 있다. 개발 호스트 컴퓨터에서 시리얼 통신이나 이더넷 통신 기능을 이용해 이미지를 다운로드한 이후에 플래시 롬에 써 넣는다. 이런 환경이 갖춰지면 보드의 프로그램 개발 속도는 한층 가속이 붙는다. 특히 임베디드 시스템에 리눅스를 이용할 때에는 리눅스 커널 자체 크기도 1MB에 달하고 램 디스크 이미지 크기도 2MB 이상을 넘는 것이 보통이므로 단순하게 시리얼로만 다운로드하면 전송 속도가 매우 느려서 개발이 무척 힘들어진다. 그래서 단가 문제로 인해 최종 제품에는 이더넷을 빼더라도 개발 단계의 보드에서는 꼭 집어 넣는다. 이렇게 보드에 부여된 통신 기능을 이용해 롬에 써 넣어야 할 데이터를 수신받고 이를 롬에 써 넣는 기능이 부트로더가 기본적으로 가져야 할 기능이다. 부트로더가 가져야 할 기능을 요약하면 다음과 같다.
운영체제 구동 기능
임베디드 시스템에 리눅스 커널을 탑재하고 구동시키기 위해 단순하게 커널 시작점으로 점프하는 것으로 끝나는 것이 아니다. 초기 설정 상태를 커널에 전달하거나 커널의 진입시에 필요한 형식을 구현해 주어야 한다.
일반적인 임베디드 리눅스 커널이라면 압축된 이미지가 플래시 메모리 장치에 있고 리눅스 커널을 필요로 하는 루트 디바이스용 램 디스크 이미지가 플래시 메모리 장치에 있게 된다. 이것을 램에 복사한 후 램에 옮겨진 커널을 구동시켜야 한다. PC에서 하드디스크에 있는 내용을 램에 옮겨 놓고 부팅하는 과정과 유사하다. 이런 과정을 구현하기 위해 부트로더가 가져야 할 기능은 다음과 같다.
- 커널 이미지 를 램 영역으로 복사 하는 기능
- 램 디스크 이미지를 램 영역으로 복사하는 기능
- 커널 수행 진입점으로 수행 위치를 옮기는 기능
알려진 부트로더들
지금까지 설명한 부트로더의 기능을 처음부터 혼자서 일일이 만들게 되면 보통 작업량이 아니다. 앞의 예를 보면 몇 가지 되지 않는 것 같지만 실제로 작성하다 보면 구현해야 할 기능이 생각보다 방대하다. 거의 작은 운영체제(?)를 만드는 느낌이 드는 경우도 있다. 또 알아야 할 지식도 한두 가지가 아니다(뭘 알아야 하는지는 이 연재 내용을 계속 읽어가다 보면 저절로 알 것이다).
그렇다고 포기할 것인가? 그건 개발자가 할 짓이 못 된다. 또 못한다고 위에 보고하면 그날로 바로 쫓겨날 수 있다. 요즘같이 힘든 경제 상황에서 회사에서 잘리면 골치 아프다. 처자식을 생각해야 하지 않겠는가. 그렇다면 어떻게 할 것인가? 이런 오도 가도 못하는 불쌍한 개발자를 위해 그 유명한 리차드 스톨만 같은 사람들이 인터넷 세상에는 바글바글하다(이러 사람들에게 항상 감사하게 생각하자). 수많은 프로그램 자선 사업가들은 자신이 고생고생해서 만든 루틴을 돈 한 푼 안 받고 인터넷에 기부하고 있다. 이들은 자신이 작성한 루틴이 같은 계열의 개발자들의 고생을 덜기를 바란다. 그러므로 우리는 이들의 성의를 무시하면 안 된다. 공개된 프로그램은 빨리 받아들여 사용해야 한다. 그래야 그 성은에 보답하는 길이다.
문제는 이런 공개된 부트로더 소스를 어디서 구할 수 있는가 하는 점이다. 아는 놈이 도둑질 한다고 막상 부트로더를 구하려 하면 어디서 구해야 할지도 모르고 어떻게 써야 할지도 모른다. 그래서 필자가 알고 있는 부트로더 종류와 구할 수 있는 위치 몇 군데를 알려 주겠다(). 쓰는 방법은 필자가 알려 주기 조금 벅찬 감이 있으므로 이 부분은 독자의 몫으로 남겨 두겠다.
부트로더와 플램폼 의존 관계
지금까지 우리는 부트로더가 필요한 이유와 부트로더가 구현해야 할 내용에 대해 대략적으로 알아봤다. 설명한 내용을 보면서 느꼈겠지만 부트로더는 특정 프로세서와 특정 보드를 위한 프로그램이다. OS처럼 범용적인 것은 아니다. 물론 공개된 부트로더 중에는 다양한 프로세서와 다양한 플랫폼을 지원하는 경우도 있다. 하지만 이것은 결국 각각의 프로세서들을 모두 경험하고 직접 프로그램했을 경우 구현이 가능하기 때문에 혼자서 모든 것을 알 수는 없다. 때문에 이런 프로그램을 전문적으로 개발해 주는 회사나 공동 프로젝트일 경우만이 가능한 것이다.
이 글의 서두에서도 설명했듯 우리의 목적은 부트로더를 직접 제작해 가면서 부트로더의 구성과 구현 과정을 알기 위한 것이다. 다양한 프로세서와 플랫폼을 생각하고 작성하기에는 너무 벅찬 감이 있다. 그나마 다행인 것은 부트로더를 작성하기 위해 주로 C를 사용한다는 점이다. C 언어는 이식성이 매우 높기 때문에 일단 만든 루틴들은 재사용이 가능하다. 더구나 부트로더의 복잡한 루틴들은 로직적인 구현으로만 구성된다.
예를 들면 C 표준 입출력 함수라든가 UDP 통신 루틴이 한 예일 것이다. 그외 플래시 메모리의 프로그램 방식은 다른 플랫폼일 경우라도 같은 플래시 메모리를 사용한다면 구현 루틴은 동일하다. 그러므로 한번 작성된 자신만의 부트로더는 다른 플랫폼에 사용되는 부트로더를 작성하더라도 어느 정도 재사용이 가능해진다. 결국 다양한 플랫폼을 지원하는 공개된 부트로더도 이런 방식으로 시작한 것이다. 또한 공개된 부트로더의 구성 방식 역시 같은 계열의 프로세서라면 비슷한 내용을 가질 수밖에 없다. 단지 편리한 기능에 대한 루틴이 더해지거나 빠진 정도로 봐도 무방하다.
어찌됐든 부트로더는 플랫폼 의존적일 수밖에 없다. 그런 이유로 인해 필자가 설명하고 구현해 가는 부트로더 역시 플랫폼 의존적일 수밖에 없다. 또한 필자의 부족한 능력 때문에 다른 플랫폼에서 동작하는 부트로더를 설명하기에는 무리가 따른다. 이 점은 독자들에게 양해를 구하는 바이다.
이 연재를 위해 사용되는 플랫폼은 EZ-X5라는 모델명을 갖는 보드이다. 이 보드는 XScale이라고도 알려진 PXA255 프로세서를 탑재한 보드이다. XScale은 인텔에서 만든 프로세서로 하나의 프로젝트 명이다. 여러분의 PC에서 사용되고 있는 CPU의 펜티엄이라는 이름과 같은 것이다. 실제 프로세서 모델명은 PXA255와 같이 내부 구성에 따라 다른 이름을 갖는다. 보통 PXA255라는 이름만 보면 이 프로세서가 ARM의 한 계열이라고 느껴지지 않을 것이다. 하지만 구성 형식이나 동작 방식 그리고 명령을 보면 ARM 계열이라는 것을 알 수 있다. 이 프로세서는 이전에 스토롱암이라는 프로세서의 후속작으로 400MHz의 속도를 갖는 매우 막강한 프로세서이며 최근에 출시되는 많은 PDA에서 사용되고 있다. 일단 프로세서에 대해 간략하게 알아보자.
임베디드 프로그래머와 부트로더 -5
- 저 자 : 유영창
- 출판일 : 2003년 10월호
ARM 프로세서
PXA255는 앞서 언급했듯 ARM 계열이다. 삼성이나 다른 회사에서 만든 프로세서가 ARM 코어를 라이선스한 것과는 다르게 아예 설계 자체를 사서 인텔 자사의 기술을 더한 프로세서이다. 그러나 결국은 이것도 개발자 입장에서 보면 ARM의 한 계열로 봐도 무방하다. 이 프로세서를 이해하면 ARM 코어를 사용하는 다른 프로세서들도 어느 정도 이해가 가능하다
그렇다면 ARM이란 것은 어떤 것일까? 아무래도 이 연재가 ARM용 부트로더를 작성하기 위한 것이므로 ARM 프로세서에 대해 모르고 갈 수는 없다. 그래서 ARM에 대해 잠깐 살펴보자.
우리가 알고 있는 펜티엄은 인텔에서 제작하고 있는 프로세서이다. 하지만 ARM에서 만든 ARM 프로세서는 실제로는 존재하지 않는다. 즉 ARM(회사 이름과 파는 프로세서 이름이 같다)에서는 ARM 프로세서를 직접 생산하지는 않고, 아키텍처 코어 및 시스템 코어를 라이선스해주는 IP 회사인 것이다. 코어는 일종의 설계도와 같은 것으로 설계도를 구입해간 회사에서는 이 코어를 이용해 주 프로세서를 구현하고 그 외에 판매하고자 하는 형태의 주변 디바이스를 추가한다. 이렇게 완성된 제품을 각 판매 회사의 고유 모델명을 붙인 후 판매하는 것이다. 우리가 주변에서 실제로 구매 가능한 것은 이렇게 나온 제품이다. 삼성의 S3C2410이나 S3C2800 같은 것이 이에 해당한다.
이런 ARM에도 종류가 매우 많은데 그 구현 방식에 따라 다른 이름을 갖기는 하지만 일종의 버전 번호와 같은 형식의 이름을 부른다. 예를 들면 ARM7 Thumb, ARM8 Thumb, ARM9 Thumb, ARM9E Thumb, ARM10 Thumb 등이다. 그 외 ARM720T, ARM920T 같은 이름이 붙기도 한다. 이런 프로세서 구분은 명령어 집합과 속도에 따라 구별한다. 이것은 부트로더 컴파일시 컴파일러 옵션에 영향을 미치는데 이런 자세한 내용은 추후 살펴 볼 것이다. 그리고 ARM 자체의 특성은 상당히 방대한 부분이기 때문에 이번 호에서 살펴보지는 않겠다. 부트로더의 실제 제작에서 자세하게 살펴볼 것이다.
XScale과 PXA255
인텔에서는 프로세서 이름을 기존 프로세서들과 다르게 두 가지 형태의 이름을 붙이는 습성이 있는 것 같다. 예전에는 80386이니 하는 이름을 붙이다가 어느 순간 펜티엄이니 아이테니엄이니 하는 이름으로 붙여 간다. XScale도 이런 명명법의 하나이다. 아마도 ARM이라는 꼬리표를 달기 싫었던 모양이다. XScale 프로세서를 설명하려면 먼저 스트롱암 프로세서를 알아봐야 한다.
스트롱암은 원래 인텔에서 만든 것은 아니다. DEC라는 회사에서 만든 제품이다. 이 회사는 ARM으로부터 아키텍처를 라이선스해 독자적인 ARM 코어 칩을 만들었는데 스트롱암이라는 모델명을 갖는다. 이 프로세서는 실제 SA-110, SA-1110이라는 이름을 갖는다. 이 DEC는 1993년에 스트롱암 관련 사업을 인텔에 매각하게 되고 인텔은 인텔 버전의 스트롱암을 생산하게 됐다. 그러다가 인텔 내부에서 기능 향상에 한계를 느껴서 새로운 내부 설계를 하기로 결정했다고 한다. 이후에 새로 설계되어 나온 것이 XScale 시리즈로 그 중 대표적인 것이 PXA250이라는 프로세서이다. 이 프로세서는 기존 스트롱암과 동일한 구성을 갖고 있으며 멀티미디어에 필요한 명령 셋이 추가됐다. 그 외에 주변 디바이스의 기능적인 확장을 했다. 또한 지원 클럭도 400MHz, 300MHz, 200MHz와 같이 다양한 종류를 선보였다. 하지만 초기 설계의 버그로 인한 버전업을 계속하다가 최종 버전을 PXA255란 모델명으로 바꾸게 된다. 결국 PXA255는 기존 스트롱암의 버전업된 프로세서로 봐도 무방할 정도로 그 구성은 유사하다. 실제 내부 버스도 매우 효율적으로 설계되어 있어서 스트롱암보다는 월등한 성능을 나타낸다.
PXA255는 워낙 유명한 회사에서 만든 프로세서이고 기존 스트롱암과 유사하게 만들어졌기 때문에 기존 스트롱암을 이용해 제품을 개발했던 회사들은 새로운 제품에 이 프로세서를 채택하는 추세이다. 이런 이유에 의해 PXA255 프로세서를 시험하거나 실제 제품에 이용할 수 있는 보드는 시중에 매우 다양하게 출시되어 있다. 이런 보드 중에 필자가 다니는 회사에서 판매하고 있는 것이 EZ-X5란 보드이고 이 보드에 구현된 것이 이지부트라는 부트로더이다. 물론 이렇게 특정 회사의 제품을 대상으로 해서 부트로더 제작에 관련된 연재를 진행하는 것은 공공적인 성격을 가지고 있는 잡지의 특성상 그리 탐탁치 않을 수 있다. 처음 이 연재를 계획했을 때 담당 기자의 걱정 또한 이런 부분이었다. 하지만 부트로더를 설명하기 위해서는 어쩔 수 없이 특정 타겟 보드가 있어야 하고 이왕이면 필자가 다니는 회사 제품을 가지고 설명한다면 좀더 정확하게 실제 개발과 관련된 노하우나 구현 과정에 필요한 사항을 모두 알려 줄 수 있다는 장점이 있다고 판단된다. 그래서 필자의 회사에서 판매하는 보드를 가지고 연재를 진행하는 것에 대해 독자들에게 양해를 구하는 바이다.
이지부트
이지부트는 필자와 필자의 회사 동료인 푸지라는 엔지니어가 직접 작성한 부트로더이다. 이 부트로더는 스트롱암 보드를 구동하기 위해 만들기 시작했던 것인데 굳이 공개된 부트로더를 이용하지 않고 다시 만들었던 것에는 나름대로 아픔이 있었기 때문이다.
초기에 스트롱암 보드가 만들어지고 이 보드에 포팅을 하기 시작한 시점에서 공개된 부트로더로 유명했던 것은 ARM 계열에 blob였다. 처음에는 blob를 분석해서 이를 조금 변형한 상태로 개발을 진행하려 했지만 기능적인 제약이 있었다. blob은 당시에 SA110 프로세서를 위한 구성을 가지고 있었는데 ARM용 부트로더로는 쓸만했지만 커널 이미지를 올리거나 램 디스크를 올리기 위해서는 시리얼 전송 방식 밖에 구현되지 않았다. 더구나 시리얼 통신을 이용한 것도 범용적인 ZMODEM이나 YMODEM 같은 방식이 아닌 UUDECODE와 UUENCODE 방식을 사용해야 했다. 필자와 같이 리눅스에서 개발하는 것보다 윈도우가 편한 사람에게 이 방식을 권하면 처음에는 개념도 어려웠고 사용상의 불편함이 이만저만한 것이 아니다. 그 외 가장 치명적인 것은 이더넷에 관련된 것을 지원하지 않는 것이었다. 실제 개발을 하다 보면 수차례의 이미지 다운로드를 수행하게 되는데 이때 전송 속도가 느려지면 필자와 같이 성격 급한 사람은 견디기 힘들어진다.
이런 저런 이유로 인해 blob의 부트로더를 변형해서 사용하는 것을 포기하고 새로운 부트로더를 만들기로 결심했다. 그런데 막상 실제 부트로더를 만들어 가면서 많은 기술적인 지식이 필요함을 느꼈다. 그래서 다른 부트로더를 살펴보고 필자의 마음에 드는 부분들만 골라서 재구성하기로 결정한 것이다. 물론 그대로 가져온 부분도 상당수 되지만 이것저것 수정하다 보니 아주 달라지는 부분도 많아졌다. 이후에 PXA255의 부트로더를 만들었을 때는 스트롱암용 이지부트의 부족한 점을 정리하고 추가했을 뿐이다. PXA255의 이지부트는 다음과 같은 기능을 갖는다.
- PXA255의 초기 설정 기능
- 부트 블럭 플래시 쓰기 기능
- NAND 플래시 쓰기 기능
- ZMODEM 시리얼 프로토콜
- TFTP 프로토콜
- 커널 부팅 기능
- 환경 설정 기능
- 리셋 기능
기능 이외에 이지부트의 특징은 다음과 같다.
- 자주 쓰는 명령은 3글자로 끝난다
필자는 매우 게으르다. 그래서 키보드 타이핑을 치는 것을 그리 좋아하지 않는다. 특히 계속 반복적인 내용을 쓰는 것을 무척 싫어한다. 다른 부트로더들은 명령어만 보면 무슨 일을 하는지 알 수 있지만 이지부트에서 사용되는 명령 자체만 보았을 때는 무슨 명령인지 모른다. 왜냐하면 3글자의 약어로 끝나기 때문이다. tfk라는 명령이 그 한 예인데 이것은 tftp 프로토콜을 사용해 플래시 메모리에 커널을 넣으라는 명령이다. 이 명령은 보드 개발 단계에서 수십 번에서 수백 번을 써 먹을 명령이다. 이 명령은 다른 부트로더에서는 다음과 같은 긴 명령이 되어 버린다. ‘download 0x3f000 tftp write flash’. 이것을 수백 번 치다 보면 신경이 곤두서게 된다. 그래서 이지부트에서는 자주 쓰는 명령들은 몽땅 3글자로 만들어 버렸다.
- 주석이 한글이다
요피 부트로더를 보면서 정말 이제는 세계화로 가는 것이 기본이구나 하는 생각을 했다. 소스에 모든 것이 영어이다. 하지만 영어에 영 자신 없는 필자 같은 사람은 주석을 보고서는 무엇을 하는 것인지 무척 헛갈리는 것이 솔직한 심정이다. 끼리끼리 모인다고, 필자 주위의 개발자들은 대부분 영어에 무척 약하다. 그래서 필자는 주석을 모두 한글로 쓰기로 했다. 또 한번 해석하기 싫은 국내 개발자들을 위해서다. 문제는 필자의 게으름의 기법상 복사문을 자주 사용해 함수가 실제로 하는 기능과 주석이 다른 것이 종종 발견되는 경우와 한번의 오타는 영원한 오타가 된다는 것이다. 원래 만들고 난 후에 공개할 생각이었고 소스를 참조할 사람들이 국내 사람일 것이기 때문에 한글로 주석을 달아 버렸다. 필자는 언젠가는 gcc가 해석하는 변수명과 함수명을 한글로 사용할 수 있게 만들 것이다. 언제가 될지 모르지만...
앞으로의 연재 방향
대부분의 연재들이 그러하듯 이 글 역시 소개 글로 채워져 버렸다. 하지만 소개 글이라기보다는 부트로더라는 프로그램의 기초 분석서라고 이해하는 아량을 베풀어 주기 바란다. 다음 호부터 본격적인 부트로더 제작 과정을 알아 볼 것이고 이 과정에 필요한 기술적인 사항 역시 기술할 것이다. 앞에서 보듯이 기능 자체는 몇 가지 안 되지만 다음과 같은 내용을 설명할 것이다.
◆ gcc 크로스 컴파일 구축 방법 ◆ 펌웨어를 위한 gcc의 컴파일 방법 ◆ PXA255의 내부 구조 및 ARM 코어의 동작 특징 ◆ PXA255의 디바이스 중에서 부트로더에서 지원해야 할 주변 디바이스에 대한 설명과 드라이버 구현 방법 ◆ PXA255의 메모리 설정과 구현 방법 ◆ ARM용 gcc의 어셈블러와 C 함수의 링크 관계 ◆ 최초 보드 시험 프로그램 로직 ◆ 부트 블럭 플래시의 구조 및 구현 방법 ◆ NAND 플래시의 구조 및 구현 방법 ◆ C의 표준 입출력 함수의 구현 방법 ◆ ARP, RARP 프로토콜의 구현 방법 ◆ UDP 프로토콜의 구현 방법 ◆ TFTP 프로토콜의 구현 방법 ◆ PING 프로토콜의 구현 방법 ◆ CS8900 이더넷 칩의 구조 및 구현 방법 ◆ 커널과 부트로더의 관계 ◆ 부트로더 탑재를 위한 JTAG의 이해
부트로더를 모든 임베디드 리눅스 개발자가 알아야 하는 것은 아니다. 하지만 펌웨어 프로그래머라면 32비트 ARM 프로세서에 직접 코딩을 해야 할 경우가 있을 때 이 연재가 도움이 될 수 있으리라 생각한다. 또는 간단한 TCP/IP 스택을 구현하는 프로그래머에게 도움이 될 수도 있을 것이다. 이지부트라는 프로그램을 처음 만들 때 필자 개인적인 용도로 사용하기 위해서만 만들지는 않았다. 서로 공유하는 문화의 혜택을 많이 받았던 필자도 공개 소프트웨어에 조금이라도 도움이 될 수 있지 않을까 해서 작성했기 때문이다. 부디 프로그램의 내용이 부실하고 사용된 프로그램의 기법이 별로 일지라도 너그러운 마음으로 보아주기 바란다. 그리고 개발자간에 서로 도와 가는 오픈소스 운동에 여러분도 참여해 주기를 바란다.