OS를 만듭시다. 어때요~ 참 쉽죠? (10)

나빌레라의 이미지

OS. 영어로 풀어서 Operating System. 한자어로 번역해서 운영체제. 이것을 만든다고 하면 사람들은 굉장히 어렵게 생각한다. 리눅스나 윈도우같은 OS를 비교대상으로 본다면 OS를 만드는것은 정말 어렵고 힘들고, 개인이 만들기엔 어쩌면 불가능에 가까운 도전일지도 모른다. 하지만 OS의 기본 개념들은 대학교 학부과정에서 가르칠 정도로 이미 보편화 되어 있고 그 개념 자체들은 그다지 어렵지 않다. 개념 구현을 중심으로 동작하는 것 자체에 의미를 둔 OS를 만드는 것은 어쩌면 도전 해 볼만 한 가치가 있는 시도가 아닐까.

목차


1회
2회
3회
4회
5회
6회
7회
8회
9회

10. 사용자 테스크 등록

임베디드OS에서 사용자 서비스의 주체는 테스크라고 하였다. 여기서 말하는 사용자 서비스란 임베디드OS에서 실제로 하드웨어를 제어해서 어떤 작업을 하는 코드이다. 즉, 보드에 달린 LED를 점멸시킨다던가, 터미널에 메시지를 출력한다던가, 모터를 돌린다던가, 센서를 제어 한다던가 하는 일은 테스크에서 OS자원을 이용해서 하는 일이다. 우리는 지금 OS를 만들고 있지만 그 OS가 제대로 돌아가는지를 확인 할려면 테스크를 만들어서 테스크들이 제대로 동작하는지를 봐야 한다.

강좌가 10회에 오는동안 우리는 나빌눅스를 어느 정도 만들었는지 잠시 되돌아 보도록 하자. 먼저 개발환경을 세팅하고, 시험삼아 시리얼에 문자를 출력하는 펌웨어와 LED를 켜보는 펌웨어를 빌드하고 이미지를 보드에 다운로드 한다음 실행 해 보았다. 그리고 나서 부트로더를 수정해서 Exception vector table 에서 필요로 하는 Exception 의 핸들러가 커널 내부 코드에서 동작하게 하였다. 커널내부에서 Exception Handler가 도는것을 확인하고서 SWI 와 IRQ핸들러에 대한 기본코드를 넣고, OS timer 를 활성화 하였다. 어셈블리와 ARM 아키텍쳐에 대한 설명이 난무하는 힘겨운 시간이 었을거라 사료 된다. 그런 다음 숨돌릴 틈도 없이 메모리 맵을 설계하고 메모리 할당자와 테스크 관리자를 작성하였다. 메모리관리자, 테스크관리자 이런 거창한 이름에 어울리지 않게 매우 단순명료한 코드로 구현되었다. 처음 준비만 조금 복잡했지 앞으로 C언어로 작성되는 커널 본체는 대체로 이정도의 난이도를 넘기지 않을 것으로 예상된다.

여기까지 OS를 만들었다. 그럼 지금까지 만든걸로 할 수 있는 일은 무엇인가. 하나밖에 없다. 사용자테스크함수를 작성하고 이를 커널에 등록한후에 제대로 스택영역을 할당 받았는지를 확인해 보는 것 뿐이다.

하지만 아직 스케줄러나 컨택스트스위칭이 만들어 지지 않았기 때문에, 인위적으로 커널의 main()함수에서 사용자 테스크를 돌려주어야 한다. 다소 부자연 스럽지만 향후 스케줄러와 컨택스트스위칭이 완성되면 그럴 싸 해 지므로 지금은 그냥 받아들이자. 디버깅의 과정이라고 생각하면 마음이 편해 질 것이다.

자, 시작 해 보자.

커널과 함께 합쳐져 하나의 이미지로 빌드되어 보드에 다운로드되기는 하지만 그래도 커널과는 별개인 녀석들이 사용자테스크함수 인 지라 파일을 분리하는 편이 보기에 좋을 것 같다. 파일명은 navilnux_user.c 로 한다.

#include <navilnux.h>
 
extern Navil_task_mng taskmng;
 
void user_task_1(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    printf("TASK1 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
}
 
void user_task_2(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    printf("TASK2 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
}
 
void user_task_3(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    printf("TASK3 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
}
 
void navilnux_user(void)
{
    taskmng.create(user_task_1);
    taskmng.create(user_task_2);
    taskmng.create(user_task_3);
}

보면 알겠지만 navilnux_user.c 의 내용은 별것 없다. 사실상 똑같은 내용의 user_task_1 ~ 3 이 있고, 이들 세개의 사용자 테스크를 커널에 등록하는 navilnux_user() 함수가 있다. navilnux_user()는 커널메인 함수에서 호출된다.

사용자테스크함수의 내용을 보면 아무것도 없다. 그냥 세개의 내부변수를 사용한다음, 의미없는 연산을 수행한다음, 각 변수의 주소를 출력한다. 테스크스택할당자 강좌와 테스크컨트롤블록 강좌에서 설명했듯이, 각 유저 테스크는 고유의 스택영역을 가진다. 그리고 내부변수는 앞에서 부터 스택의 시작 주소에 잡힌다. 그러므로 내부변수 a, b, c의 주소를 찍으면 스택의 시작 주소부터 변수의 크기만큼씩 아래로 자라는 (감소하는) 주소값이 출력될 것이다.

사용자 스택의 시작 주소는 0xA0400000 이다. 그리고 각 스택별 사용자 영역의 크기는 1Mbyte 이다. 스택은 아래로 자란다고 하였으므로 사용자테스크의 스택시작값은 스택블럭의 끝값을 받는다. 그러므로 user_task_1 은 0xA0500000 번지의 스택을 할당 받는다. 그러므로 user_task_2 는 0xA0600000 번지의 스택을 시작주소로 할당 받고, user_task_3 은 1Mbyte를 더 올라간 0xA0700000 번지를 스택 시작 주소로 할당 받는다. 스택할당자 코드를 보면 실제로는 정확이 위에 서술한 주소를 받지 않고 4byte 씩 값을 뺀 주소를 실제로 할당한다.

위와 같이 할당하면 실제 각 사용자테스크의 출력값은 아마도 아래 와 같을 것이다.

TASK1 - a:0xa04ffffc	b:0xa04ffff8	c:0xa04ffff4
TASK2 - a:0xa05ffffc	b:0xa05ffff8	c:0xa05ffff4
TASK3 - a:0xa06ffffc	b:0xa06ffff8	c:0xa06ffff4

아마도 위와 같이 나오지 않을까? 아직은 필자도 모른다. 필자도 필자가 만들었던 OS를 다시 강좌에 맞게 단순하게 고쳐 가면서 다시 프로그래밍하며 강좌에 맞춰 작성해 가고 있기 때문에 현 시점에서 명확히 위의 값이 나올지는 알 수 없다. 하지만 앞으로 컨텍스트스위칭 강좌가 끝나고 실제 사용자테스크가 동작하기 시작하면 확인 할 수 있을 것이다.

소스파일이 수정되었으니 더불어 헤더파일도 하나 만들자. 없어도 되긴 하지만 컴파일에서 경고메시지가 나온다. 필자는 아무런 경고도, 에러도 없는 컴파일 메시지를 선호 한다. 이름은 navilnux_user.h 이다. 지금은 OS자체를 전부다 만들고 있지만, 혹시라도 만약에 나빌눅스를 OS개발이 아니라 실제 장치활용을 위해 플랫폼으로 사용한다면 개발자는 navilnux_user.c 와 navilnux_user.h 두개의 파일만 수정하면 사용자 테스크를 추가 할 수 있을 것이다.

#ifndef _NAVIL_USER
#define _NAVIL_USER
 
void user_task_1(void);
void user_task_2(void);
void user_task_3(void);
 
 
#endif

단순하다. 사용자테스크의 프로토타입을 선언해 주었을 뿐이다. 혹시라도 사용자 테스크에서 필요로 하는 define 문등이 필요하다면 이 헤더파일에 선언하면된다. 사실상 사용자테스크의 프로토타입선언문 외엔 이쪽에 쓸 내용이 별로 없을 것으로 사료 된다.

사용자테스크를 taskmng.create() 로 커널에 등록하는 navilnux_user() 함수는 커널메인에서 호출한다. 그러므로 navilnux_user() 함수의 프로토타입은 커널메인에서 참조 가능한 헤더파일에 있어야 한다. 위치는 navilnux.h 로 하자.

#ifndef _KERNEL_H_
#define _KERNEL_H_
 
#include <pxa255.h>
#include <time.h>
#include <gpio.h>
#include <stdio.h>
#include <string.h>
 
#include <navilnux_memory.h>
#include <navilnux_task.h>
 
#include <navilnux_user.h>
 
void navilnux_init(void);
void navilnux_user(void);
 
#endif

navilnux_user.h 가 include 되어 있고, 더불어 위에서 설명한 navilnux_user() 함수의 프로토타입이 선언되어 있다. 그리고 navilnux_init() 함수가 프로토타입선언 되어 있다. 이 함수 역시 커널메인함수에서 호출되는 함수인데, 앞으로 지속적으로 추가되는 나빌눅스의 추가기능들에 대한 초기화를 이 함수에서 일괄적으로 처리한다.

현재 까지 작업된 부분은 메모리관리자, 테스크관리자, OS timer 관련 내용이다. 이 작업된 부분까지의 초기화를 navilnux_init() 에서 처리 할 것이다.

이제 main() 함수를 수정하자. navilnux.c 파일을 아래와 같이 수정한다.

#include <navilnux.h>
 
extern Navil_mem_mng memmng;
extern Navil_task_mng taskmng;
 
void swiHandler(unsigned int syscallnum)
{
    printf("system call %d\n", syscallnum);
}
 
void irqHandler(void)
{
    if( (ICIP&(1<<27)) != 0 ){
        OSSR = OSSR_M1;
        OSMR1 = OSCR + 3686400;
        printf("Timer Interrupt!!!\n");
    } 
}
 
void os_timer_init(void)
{
    ICCR = 0x00;
 
    ICMR |= (1 << 27);      
    ICLR &= ~(1 << 27);    
 
    OSCR = 0;
    OSMR1 = OSCR + 3686400; 
 
    OSSR = OSSR_M1;
}
 
void os_timer_start(void)
{
    OIER |= (1<<1);
    OSSR = OSSR_M1;
}
 
void irq_enable(void)
{
    __asm__("msr    cpsr_c,#0x40|0x13");
}
 
void irq_disable(void)
{
    __asm__("msr    cpsr_c,#0xc0|0x13");
}
 
void navilnux_init(void)
{
    mem_init();
    task_init();
 
    os_timer_init();
    os_timer_start();
}
 
int main(void)
{
    navilnux_init();
    navilnux_user();
 
    irq_enable();
 
    int i;
    for(i = 0 ; i <= taskmng.max_task_id ; i++){
        printf("TCB : TASK%d - init PC(%p) \t init SP(%p)\n", i+1,
                    taskmng.free_task_pool[i].context_pc,
                    taskmng.free_task_pool[i].context_sp);
    }
 
    printf("REAL func TASK1 : %p\n", user_task_1);
    printf("REAL func TASK2 : %p\n", user_task_2);
    printf("REAL func TASK3 : %p\n", user_task_3);
 
    while(1){
        msleep(1000);
    }
 
    return 0;
}	

이전과 달라진 부분이 몇 부분 보인다. 먼저 navilnux_init() 함수를 보자, 메모리관리자와 테스크관리자를 순서대로 초기화 한다. mem_init() 함수와 task_init()함수는 앞서 강좌에서 모두 만들어 놓은 함수들이다. 그리고 종전에는 main() 함수에서 직접 호출했던 os_timer_init() 함수와 os_timer_start() 함수를 navilnux_init() 함수 안으로 옮겨 놓았다. 이런식으로 초기화 관련 함수및 코드들은 모두 navilnux_init() 함수로 일원화 한다.

커널초기화(navilnux_init)가 완료 되면 사용자테스크를 커널에 등록(navilnux_user)한다. 위에 나왔던 taskmng.create() 함수가 연달아 있는 그 함수 이다. 그런 다음 ARM프로세서의 IRQ를 활성화 한다. IRQ활성화를 navilnux_init() 에 넣지 않은 이유는 IRQ를 활성화 함과 동시에 OS timer 가 동작하기 때문에 나중에 컨택스트스위칭 코드가 추가되고 나면 아직 등록도 되지 않은 테스크를 참조하려 하기에 버그의 우려가 있어 실제 커널의 초기화가 모두 다 끝난 다음에 마지막으로 IRQ를 활성화 시킨것이다. 즉, IRQ를 활성화 시키는 것이 곧 커널을 본격적으로 무한루프에 들어가게 하는 작업이다.

irq_enable() 함수 밑으로는 제대로 테스크가 등록되었는지를 확인해 보기 위한 일종의 디버그 메시지 이다. TCB에 등록된 context_pc 의 값과 실제 사용자테스크함수의 값을 출력해서 같은지 보고, context_sp 의 값을 출력하여 제대로 등록이 되었는지를 확인 해 본다. 마지막으로 나오는 무한루프는 아무것도 하지 않고, msleep() 함수로 딜레이를 준다.

이번회에도 몇개의 소스파일이 추가 되었다. Makefile을 수정하자.

CC = arm-linux-gcc
LD = arm-linux-ld
OC = arm-linux-objcopy
 
CFLAGS    = -nostdinc -I. -I./include 
CFLAGS   += -Wall -Wstrict-prototypes -Wno-trigraphs -O0
CFLAGS   += -fno-strict-aliasing -fno-common -pipe -mapcs-32 
CFLAGS   += -mcpu=xscale -mshort-load-bytes -msoft-float -fno-builtin
 
LDFLAGS   = -static -nostdlib -nostartfiles -nodefaultlibs -p -X -T ./main-ld-script
 
OCFLAGS = -O binary -R .note -R .comment -S
 
CFILES = navilnux.c navilnux_memory.c navilnux_task.c navilnux_user.c
HFILES = include/navilnux.h include/navilnux_memory.h include/navilnux_task.h include/navilnux_user.h
 
all: $(CFILES) $(HFILES)
	$(CC) -c $(CFLAGS) -o entry.o entry.S
	$(CC) -c $(CFLAGS) -o gpio.o gpio.c
	$(CC) -c $(CFLAGS) -o time.o time.c
	$(CC) -c $(CFLAGS) -o vsprintf.o vsprintf.c
	$(CC) -c $(CFLAGS) -o printf.o printf.c
	$(CC) -c $(CFLAGS) -o string.o string.c
	$(CC) -c $(CFLAGS) -o serial.o serial.c
	$(CC) -c $(CFLAGS) -o lib1funcs.o lib1funcs.S
	$(CC) -c $(CFLAGS) -o navilnux.o navilnux.c
	$(CC) -c $(CFLAGS) -o navilnux_memory.o navilnux_memory.c
	$(CC) -c $(CFLAGS) -o navilnux_task.o navilnux_task.c
	$(CC) -c $(CFLAGS) -o navilnux_user.o navilnux_user.c
	$(LD) $(LDFLAGS) -o navilnux_elf entry.o gpio.o time.o vsprintf.o printf.o string.o serial.o lib1funcs.o navilnux.o navilnux_memory.o navilnux_task.o navilnux_user.o
	$(OC) $(OCFLAGS) navilnux_elf navilnux_img
 
clean:
	rm *.o
	rm navilnux_elf
	rm navilnux_img

매 회차 강좌마다 마치 패턴처럼 이어지는 말이 있다. 부담갖지 말고 make 를 쳐 보자. 빌드가 되고 나면 이미지파일을 보드에 다운로드 하고 가벼운 마음으로 부팅된 후에 나오는 메시지를 확인 해 보자 아래와 같은 메시지가 나오면 성공한것이다.

TCB : TASK1 - init PC(a000ba40)          init SP(a04ffffc)                      
TCB : TASK2 - init PC(a000ba94)          init SP(a05ffffc)                      
TCB : TASK3 - init PC(a000bae8)          init SP(a06ffffc)                      
REAL func TASK1 : a000ba40                                                      
REAL func TASK2 : a000ba94                                                      
REAL func TASK3 : a000bae8                                                      
Timer Interrupt!!!                                                              
Timer Interrupt!!!                                                              
Timer Interrupt!!!
         :
         :

다음 강좌에는 드디어 컨택스트스위칭을 구현 할 것이다. 실상 임베디드OS 만들기의 핵심이자 하일라이트라고 할 수 있는 내용이다. 또한 가장 어렵다고 여기는 부트로더 만들기를 이지부트를 재활용하는 것으로 얼렁뚱땅 넘어가 버려서 사실상 본 강좌 통털어 가장 어려운 내용이 될지도 모르겠다. 다음 강좌를 기대 하시라.

이 글은 http://raonlife.com/navilera/blog/view/84/에 동시 연재 됩니다.

File attachments: 
첨부파일 크기
파일 navilnux_chap10.tgz43.67 KB

댓글

paeksj98의 이미지

정말 수고 많으시네요....
계속 수고 부탁이요....^^

sixt06의 이미지

강좌 잘보고 있습니다.
프로세서를 전공하고 있는데 저희 프로세서로 포팅할 간단한 OS를 찾고 있습니다.
나빌레라님의 OS는 라이센스가 어떤지요?

나빌레라의 이미지

^^;

제가 만드는 부족한 OS에 관심을 가져 주셔서 감사합니다.

음.. 강좌에 녹여서 소스코드는 공개될것이지만 아직 공식적으로 프로젝트페이지를 만들고 공개하는 것에 대해 생각해 본적이 없어서 라이센스는 아직 정하지 않았는데...

음.. 뭐..

BPL(밥 public license) + SPL(소개팅 public license) 정도? ^^;

농담입니다.

배포를 하게 된다면 GPL 로 할것 같습니다.
----
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

sixt06의 이미지

한번 kldp.net에서 프로젝트를 시작하시는 것도 좋을 듯 합니다.

소스를 추려서 저희 프로세서에 올려 봐야 겠습니다.

다른 프로젝트하면서 하려면 시간 좀 걸리겠네요 ㅋㅋ

verimuch의 이미지

주말에 시간내서 조금씩 따라하니 재밌네요 +_ +
---------------------------------
Life is not fair, Get used to it.

---------------------------------
Life is not fair, Get used to it.

elvis의 이미지

정말 좋은 OS를 만들기 위한 테크닉을 얻게 된것 같네요~!

나빌레라의 이미지

감사합니다.

근데 아시나요?

책도 나왔어요 ^^

http://kangcom.com/sub/view.asp?topid=1&sku=200903230001

=3==33333 (도망...)

----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

----------------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

frenzy의 이미지

책 샀어용...

:-)
.
++++++++++++++++++++++++++++++++++++++++++++++
혼자놀기의 도사가 되리라... http://geeklife.co.kr

.
++++++++++++++++++++++++++++++++++++++++++++++
혼자놀기의 도사가 되리라... http://geeklife.co.kr

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.