OS를 만듭시다. 어때요~ 참 쉽죠? (19)
OS. 영어로 풀어서 Operating System. 한자어로 번역해서 운영체제. 이것을 만든다고 하면 사람들은 굉장히 어렵게 생각한다. 리눅스나 윈도우같은 OS를 비교대상으로 본다면 OS를 만드는것은 정말 어렵고 힘들고, 개인이 만들기엔 어쩌면 불가능에 가까운 도전일지도 모른다. 하지만 OS의 기본 개념들은 대학교 학부과정에서 가르칠 정도로 이미 보편화 되어 있고 그 개념 자체들은 그다지 어렵지 않다. 개념 구현을 중심으로 동작하는 것 자체에 의미를 둔 OS를 만드는 것은 어쩌면 도전 해 볼만 한 가치가 있는 시도가 아닐까.
목차
1회
2회
3회
4회
5회
6회
7회
8회
9회
10회
11회
12회
13회
14회
15회
16회
17회
18회
19. 나빌눅스 계층
저번 회차 강좌를 마지막으로 나빌눅스의 구현은 끝났다. 정리해 보면 vm 을 제외한 현대 운영체제에서 필요한 거의 모든 요소를 다 다룬것 같다. 학교 수업시간에 그냥 이론으로만 배웠던 운영체제를 이루는 많은 개념들을 실제로 구현 해 보면서 이렇게 쉽게도 운영체제를 만들 수 있구나 하는 것을 느껴 줬으면 하는 것이 본 강좌를 시작하게 된 필자의 바램 이었다.
운영체제를 만든다는 것은 대단한 프로그래밍 실력 보다는 운영체제를 만들기로 정한 타겟플랫폼에 대한 이해. 나빌눅스의 경우에는 ARM 프로세스에 대한 이해가 가장 큰 부분을 차지 한다. ARM 프로세서의 특징적인 부분이나 예외처리방식 메모리 주소 접근방식등을 비롯해서 ARM 아키텍쳐의 인스트럭션별 동작 방식들에 대한 이해가 매우 중요했다. 그리고 중요한것이 MCU에 대한 이해 이다. 나빌눅스는 이지보드(ex-x5보드)를 타겟 보드로 삼았고, 이지보드에는 pxa255칩을 사용했다. pxa255칩은 ARM9 코어를 사용한 MCU 이다. OS timer 나 GPIO 등을 처리할때는 ARM 보다는 pxa255 에 대한 이해가 더 중요했다. MCU를 비롯한 어떤 칩을 사용하던지 간에 펌웨어나 디바이스드라이버 수준에서 해당 칩을 정확하게 사용하기 위해서는 칩의 데이터시트를 유심히 읽어보는 것이 필요하다. 우리가 원하는 정보는 거의다 데이터시트안에 있다. 우리는 데이터 시트를 읽고 데이터시트에서 하라는대로만 코딩해주면 하드웨어는 알아서 잘 동작한다. 놀라울 정도로.
그리고서 이제 필요한것이 운영체제의 각 부분에 대한 이해 이다. 컨텍스트스위칭의 원리나, 각종 벡터테이블에 대한 이해, 메모리주소 할당, 테스크간통신, 테스크간동기화, 디바이스드라이버 등등 알고리즘적이고 기술적인 부분에 대한 내용은 쉽게 구할 수 있는 운영체제론과 같은 개론서만 열심히 읽어도 어느정도 습득할 수 있는 지식이다. 나빌눅스는 그렇게 얻은 지식을 최대한 이해하기 쉽고 간결하게 구현해 놓은 것에 불과 하다.
필자는 운영체제를 만든 다는 것은 프로그래밍 실력보다는 운영체제 안에서 각 요소가 어떻게 동작하고 어떻게 상호작용하는지에 대한 정책을 결정하는 것이 더중요하다고 생각 한다. 그래야 그 운영체제를 사용하는 제 3의 개발자가 혼동없이 운영체제를 이용할 수 있고, 여러명의 개발자가 서로 다른 작업을 한다고 해도 운영체제가 일관된 정책을 제공한다면 서로간에 동일한 절차에 의해 운영체제에 기능을 추가 할 수 있기 때문이다. 그런것을 수립하고 만드는 능력은 필자도 아직 경지에 이르지 못했다고 생각한다. 하지만 본 강좌를 쓰면서 최대한 나빌눅스의 각 기능을 계층화 하려 노력했고 어느정도 만족할 만한 결과를 얻었다고 생각한다.
이번회차 강좌에서는 지금까지 만든 나빌눅스의 각 계층을 다시 리뷰해 보는 시간을 가져 보도록 하겠다.
나빌눅스는 운영체제 각 계층을 최대한 파일로 분류 하려고 노력 하였다. 그럼 지금까지 만든 파일들을 한번 보도록 하자. 먼저 .c 소스와 .S 소스파일들이다.
Makefile main-ld-script navilnux_lib.S navilnux_user.c vsprintf.c entry.S my_drv.c navilnux_memory.c printf.c gpio.c navilnux.c navilnux_msg.c serial.c include navilnux_clib.c navilnux_sys.c string.c lib1funcs.S navilnux_drv.c navilnux_task.c time.c
이중 Makefile 과 include 디렉토리. 그리고 이지부터에서부터 따라온 라이브러리 파일들을 제외하고 순수하게 나빌눅스를 만들면서 추가한 파일들을 따로 추려 보자.
navilnux_lib.S navilnux_user.c entry.S my_drv.c navilnux.c navilnux_msg.c navilnux_clib.c navilnux_sys.c navilnux_drv.c navilnux_task.c navilnux_memory.c
마찬가지로 include 디렉토리에서도 나빌눅스를 만들면서 추가한 파일들만 추려보자
navilnux_lib.h navilnux_task.h navilnux_memory.h navilnux_user.h navilnux.h navilnux_msg.h navilnux_drv.h navilnux_sys.h syscalltbl.h
20개도 안되는 소스파일로 운영체제를 만들어 버린것이다! 놀랍지 않은가. 운영체제라 함은 무언가 쉘이 있고 마우스가 있어야 하고 명령을 내려서 프로그램을 실행하고 마우스를 클릭해야 하고 이런 고정관념만 버린다면 저렇게 작게도 만들 수 있다는 것이다.
일단 가장 최상위레이어는 당연히 사용자테스크레이어 이다. 사용자테스크는 모두 navilnux_user.h 와 navilnux_user.c 에 정의되고 구현되어 있다. 사용자테스크에서 커널에게 무언가를 요청하는 모든 기능은 시스템콜로 구현되었으며, 나빌눅스에서 시스템콜은 랩퍼함수에 의해 navilnux_ 라는 prefix 를 달고 있다. 그럼 나빌눅스 계층의 상위는 아래 그림과 같다.
+-----------------------------------------------------------------+ | TASK | +-----------------------------------------------------------------+ +-----------------------------------------------------------------+ | NAVILNUX_LIB | +-----------------------------------------------------------------+
나빌눅스 랩퍼함수는 필요에 의해서 두개로 구분하여 구현하였다. blocking 이 필요한 시스템콜과 바로 반환해야 하는 시스템콜이다. blocking 이 필요한 시스템콜은 사용자영역에서 따로 구현하여 navilnux_clib.c 파일이 구현하였다. 하지만 navilnux_clib.c 에 구현된 함수들도 navilnux_lib.S에 있는 함수를 통해서 시스템콜 레이어에 진입한다. 따라서 위 그림은 아래와 같이 바뀌어야 한다.
+-----------------------------------------------------------------+ | TASK | +-----------------------------------------------------------------+ +------------------------------++---------------------------------+ | NAVILNUX_LIB || CLIB | | |+---------------------------------+ | +----------------------------------+ | + +-----------------------------------------------------------------+
나빌눅스에서 테스크의 동작에 관여 하는 계층이 시스템콜만 있는 것은 아니다. 가장 먼저 떠오르는 것이 entry.S에 구현되어 있는 컨택스트스위칭과 navilnux.c 에 구현되어 있는 스케줄러이다. 컨택스트스위칭과 스케줄러가 있어야 멀티테스킹이 된다. 또한 navilnux_task.h 에 정의되고 navilnux_task.c 에 구현된 테스크관리자에 소속되어 있는 테스크 자체를 생성하는 테스크생성자도 있다. 그러면 나빌눅스의 사용자 테스크 바로 아래 레이어는 아래 그림과 같이 정리 된다.
+-----------------------------------------------------------------+ | TASK | +-----------------------------------------------------------------+ +--------------++----------------------++------------++-----------+ | || CLIB || CON_SW || | | LIB |+----------------------++------------+| TASK | | +-----------------------++------------+| CREATER | | || SCHED || | +--------------------------------------++------------++-----------+
navilnux_lib.S 에 있는 랩퍼 함수들은 공통적으로 아무것도 하는 일 없이 SWI 명령으로 미리 정의된 시스템콜 번호를 커널에 전달하는 역할만 한다. 그러므로 LIB 레이어 아랫단은 SWI 로 구현되는 시스템콜 계층이어야 한다. 마찬가지로 컨택스트스위칭은 OS timer 에 의해 스케줄러가 불려지는 방식으로 구현되므로 컨택스트 스위칭과 스케줄러는 하드웨어단으로 내려가게 되면 IRQ레이어와 만나게 된다. Software Interrupt 와 IRQ 레이어를 포함해서 그림을 수정하면 아래와 같다.
+-----------------------------------------------------------------+ | TASK | +-----------------------------------------------------------------+ +--------------++----------------------++------------++-----------+ | || CLIB || CON_SW || | | LIB |+----------------------++------------+| TASK | | +-----------------------++------------+| CREATER | | || SCHED || | +--------------------------------------++------------+| | +--------------------------------------++------------+| | | SYSTEM CALL || IRQ || | +--------------------------------------++------------++-----------+
시스템콜계층을 구현한 이후 4회의 강좌에 걸쳐서 나빌눅스에 ITC(Inter Task Communication)과 뮤택스, 세마포어, 메모리동적할당, 디바이스드라이버계층을 추가 하였다. 이들은 모두 사용자테스크에서 시스템콜을 이용하여 사용 하게 하였다. 그중 특히 디바이스드라이버계층은 IRQ핸들러벡터 테이블을 이용해서 IRQ를 통해 외부 장치로부터의 입력을 처리하게 작성하였다. 즉, 디바이스드라이버계층은 시스템콜 계층을 통해서 호출되어 지고 내부에서 IRQ계층과 연결되어 있다. 또한 테스크생성자는 테스크를 생성할때 메모리관리자로 부터 정적메모리를 할당받아 TCB에 세팅하므로 그 아래 레이어에는 메모리 할당자가 들어가야 한다. 구체적인 시스템콜 모듈과 메모리 정적할당자를 포함하면 나빌눅스 계층도는 아래와 같이 바뀐다.
+-----------------------------------------------------------------+ | TASK | +-----------------------------------------------------------------+ +--------------++----------------------++------------++-----------+ | || CLIB || CON_SW || | | LIB |+----------------------++------------+| TASK | | +-----------------------++------------+| CREATER | | || SCHED || | +--------------------------------------++------------+| | +--------------------------------------++------------+| | | SYSTEM CALL || |+-----------+ +--------------------------------------+| |+-----------+ +------++------++------++------++------+| || | | ITC || SEM || MUT || DMEM || DRV || IRQ || MEMORY | +------++------++------++------++------+| || ALLOCATER | +----------------------------------++---+ || | | HW || || | +----------------------------------++----------------++-----------+
가장 아랫단의 하드웨어 계층은 시스템콜 모듈 뿐만 아니라 IRQ , 메모리관리자들도 직접 접근하는 영역이다. 그러므로 하드웨어 계층은 전체를 감싸면서 마무리 되어야 한다. 그래서 나빌눅스 계층도의 최종 완성판은 아래와 같이 된다.
+-----------------------------------------------------------------+ | TASK | +-----------------------------------------------------------------+ +--------------++----------------------++------------++-----------+ | || CLIB || CON_SW || | | LIB |+----------------------++------------+| TASK | | +-----------------------++------------+| CREATER | | || SCHED || | +--------------------------------------++------------+| | +--------------------------------------++------------+| | | SYSTEM CALL || |+-----------+ +--------------------------------------+| |+-----------+ +------++------++------++------++------+| || | | ITC || SEM || MUT || DMEM || DRV || IRQ || MEMORY | +------++------++------++------++------+| || ALLOCATER | +----------------------------------++---+ || | | || || | | |+----------------++-----------+ | HW +------------------------------+ | + +-----------------------------------------------------------------+
지금까지 강좌를 통해서 구현한 나빌눅스의 전체 모습을 한눈에 볼 수 있는 다이어그램이다. 별거 아닌듯 하면서도 의외로 많은 부분이 구현되어 있고, 뭔가 어렵게 많이 한것 같으면서도 정작 한건 별로 없다. 다만 해야 할것들은 모두 했다고 생각 한다. 저렇게 그려 놓으니 뭔가 번듯하게 제대로 해 놓은것 같아 개발자 입장에서 뿌듯한 기분이 든다.
이 글은 http://raonlife.com/navilera/blog/view/93/ 에 동시 연재 됩니다.
댓글 달기