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

나빌레라의 이미지

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

목차


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

12. 사용자 테스크 동작 확인

전편에서 컨텍스트 스위칭과 라운드로빈 스케줄러를 이용해서 테스크 멀티테스킹을 동작 시켰다. 그리고 잘 동작되는 것을 확인하였다. 이번회는 그동안 가열차게 달려왔던 코딩 부담을 덜고자, 저번회차 강좌의 출력값에 대한 고찰을 해 보자. 이번 강좌는 아마 좀 짧을 것이다.

일단 저번강좌에 나왔던 사용자테스크 관련 코드를 다시 보자.

void user_task_1(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK1 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        msleep(1000);
    }
}
 
void user_task_2(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK2 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        msleep(1000);
    }
}
 
void user_task_3(void)
{
    int a, b, c;
 
    a = 1;
    b = 2;
    c = a + b;
 
    while(1){
        printf("TASK3 - a:%p\tb:%p\tc:%p\n", &a, &b, &c);
        msleep(1000);
    }
}

임베디드 OS는 일반 PC의 OS와는 달리 사용자 테스크가 종료되지 않아야 한다. 그래서 3개의 사용자테스크가 모두 무한루프를 돈다. 그냥두면 메시지가 너무 많이 출력되기 때문에 1초 쉬고 루프를 돌도록 하였다. 세개의 테스크는 모두 무한루프를 돌면서 1초에 한번씩 a,b,c 세개의 내부변수 주소를 출력한다. 그리고 처음 TCB 를 추가 했을때 사용자 테스크의 프로토타입을 작성하고, 결과값을 예측했었다. 그때의 예측값은 아래와 같았다.

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

하지만 실제의 출력값은 아래와 같다.

TASK1 - a:a04fffe8      b:a04fffe4      c:a04fffe0                              
TASK2 - a:a05fffe8      b:a05fffe4      c:a05fffe0                              
TASK3 - a:a06fffe8      b:a06fffe4      c:a06fffe0         

예상 값보다 20byte 차이가 난다. 사실 별문제가 아니긴 하다, 20바이트 차이이긴 하지만 실제 예측했던 메모리 블록내부에 값이 무사히 잡혔으므로 컴파일러가 실제로 내부변수를 할당하기 전에 무언가 사전작업을 했나보다 생각하면 그만이다. 하지만 궁금하지 않은가. 예측하지 못한 20바이트에는 어떤 것들이 들어가는지 한번 보자. 알아볼려면 어떻게 해야 할까. 간단하다 역어셈블해서 나온 어셈블리코드를 보자. 강좌초반에 역어셈블 한번 해보았던 기억이 있을 것이다. 다시 한번 역어셈블 해 보자

$ arm-linux-objdump -D ./navilnux_elf > nvdump

저렇게 명령을 치면 nvdump 파일에 역어셈블한 내용이 들어간다. 세개의 테스크는 내용이 사실상 동일하므로 user_task_1의 내용만 보자.

a000bbe4 <user_task_1>:
a000bbe4:   e1a0c00d    mov ip, sp
a000bbe8:   e92dd800    stmdb   sp!, {fp, ip, lr, pc}
a000bbec:   e24cb004    sub fp, ip, #4  ; 0x4
a000bbf0:   e24dd00c    sub sp, sp, #12 ; 0xc
a000bbf4:   e3a03001    mov r3, #1  ; 0x1
a000bbf8:   e50b3010    str r3, [fp, -#16]
a000bbfc:   e3a03002    mov r3, #2  ; 0x2
a000bc00:   e50b3014    str r3, [fp, -#20]
a000bc04:   e51b2010    ldr r2, [fp, -#16]
a000bc08:   e51b3014    ldr r3, [fp, -#20]
a000bc0c:   e0823003    add r3, r2, r3
a000bc10:   e50b3018    str r3, [fp, -#24]
a000bc14:   e24b3010    sub r3, fp, #16 ; 0x10
a000bc18:   e24b2014    sub r2, fp, #20 ; 0x14
a000bc1c:   e24bc018    sub ip, fp, #24 ; 0x18
a000bc20:   e59f0014    ldr r0, [pc, #20]   ; a000bc3c <user_task_1+0x58>
a000bc24:   e1a01003    mov r1, r3
a000bc28:   e1a0300c    mov r3, ip
a000bc2c:   ebfff65c    bl  a00095a4 <printf>
a000bc30:   e3a00ffa    mov r0, #1000   ; 0x3e8
a000bc34:   ebfff228    bl  a00084dc <msleep>
a000bc38:   eafffff5    b   a000bc14 <user_task_1+0x30>
a000bc3c:   a000be68    andge   fp, r0, r8, ror #28

함수를 시작하자마자 fp, ip, lr, pc 네개의 레지스터를 스택에 백업 한다. 16byte 밑으로 sp 가 내려가게 된다.여기서 필자는 의문이 생겼다. 그리고 stmdb 문장의 위에 보면 sp 를 ip 에 복사한것으로 함수가 시작한다. 이후에 stmdb 문장의 아래에 보면 ip 에서 4를 뺀 다음 그 값을 fp 에 넣고, 이후 함수 내부 변수를 스택에 할당할때는 sp 를 base 로 사용하지 않고 fp 를 base 주소로 사용하여 fp 에서 16바이트를 뺀 주소에 내부 변수 a 를 할당한다.

정리하면 fp 는 처음에 ip 에서 4 를 뺀값이 들어가고 거기서 다시 16을 뺀위치에 첫번째 내부변수인 a 가 할당되므로

 ip = sp
 fp = (ip - 4) - 16
 그러므로 fp = sp - 20

위와 같은 단순한 산수에 의해서 20바이트 차이가 나게 된다. printf 를 호출하기 직전까지의 메모리 주소를 그려보면

              +-------------------+
    a04f fffc |                   |  <---- ip
              +-------------------+
    a04f fff8 |       fp          |  <---- fp
              +-------------------+
    a04f fff4 |       ip          |
              +-------------------+
    a04f fff0 |       lr          |
              +-------------------+
    a04f ffec |       pc          |
              +-------------------+
    a04f ffe8 |        a          |  <---- [fp - 16]
              +-------------------+
    a04f ffe4 |        b          |  <---- sp
              +-------------------+
    a04f ffe0 |        c          |  <---- [fp - 24]
              +-------------------+
              |                   |
              |       :           |
              |       :           |

위 그림과 같다. 컴파일러가 뭔가 의미를 가지고 사용하는 fp, ip, lr, pc 등을 스택의 상단에 백업하는데 명령이 stmdb이다. stmdb는 address descending before stm 의 준말이다. 먼저 주소값을 감소 시킨 다음에 그 감소된 주소에 데이터를 저장한다. 그러므로 첫번째로 저장되는 fp 는 인덱스레지스터인 sp 가 가지고 있는 0xA04ffffc 부터 저장되는 것이 아니라 먼저 4바이트를 내려가고서 저장되는 0xA04ffff8 부터 저장되게 된다. 그래서 처음 예상했던 주소와 20바이트 차이가 나게 되는 것이다.

다시 현재까지 작업분의 출력내용의 윗부분을 보자.

TCB : TASK1 - init PC(a000bbe4)          init SP(a04ffffc)                      
TCB : TASK2 - init PC(a000bc40)          init SP(a05ffffc)                      
TCB : TASK3 - init PC(a000bc9c)          init SP(a06ffffc)                      
REAL func TASK1 : a000bbe4                                                      
REAL func TASK2 : a000bc40                                                      
REAL func TASK3 : a000bc9c                                                      

사용자테스크 함수의 시작주소가 0xa000bbe4 번지로 출력된다. 역어셈블 해 본 결과를 보면 사용자테스크의 시작주소가 마찬가지로 a000bbe4 로 일치하게 출력된다. 이 시점에서 떠올려 볼 것이 있다. 바로 커널 이미지의 로딩 주소가 0xa0008000 번지라는 것이다. 0xa000bbe4 번지는 커널 이미지 시작 주소로 부터 15332 바이트 떨어진 위치이다. 현 시점에서의 커널이미지 크기는 16308 바이트이다. Makefile 에서 오브젝트파일 링킹 순서상 사용자테스크가 들어 있는 navilnux_user.o 는 맨 마지막에 있다. 그러므로 15332바이트 위치쯤에 오는게 합당할 듯 하다.

끝이다. 써놓고 보니 이번회 강좌는 좀 의미가 없는것 같다. 그래도 읽어주시기 바란다. 나름 열심히 썻다. 이제 OS 만들기의 아키텍쳐적인 설명이 거의 다 된것 같다. 앞으로 아키텍쳐에 대해 공부해야 할 것들은 외부 인터럽트 핸들러나 시스템콜추가 할때 외엔 그냥 간단 간단한 C 언어 테크닉이다. 그리고 필자역시 의도적으로 쉽게 코딩할것이다. 부담없이 앞으로도 계속 읽어주기 바란다.

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

댓글

powerc20의 이미지

제 생각에 정답은 stmdb에 있습니다. 나빌레라님께서 스택의 주소를 task1의 경우 init SP(a04ffffc)로 주셨습니다.
하지만 arm 의 gcc는 스택에 push를 할 때 stmdb를 사용합니다. stmdb는 address descending before stm입니다.
따라서 저장하기 저에 미리 4바이트를 줄이고 레지스터를 저장합니다. 이미 나빌레라님께서는 저장을 위해 스택의
주소를 4바이트 뺀 주소로 지정하셨기 때문에 4바이트가 사라져 보이는 것인것 같습니다.

혹시 잘못된 생각이면 죄송합니다. 제가 실습 할 수 없이 그냥 코드만 보는 입장이라 제 착각일수 있습니다.

항상 강좌 잘 보고 있습니다. 즐거운 하루 되세요.

나빌레라의 이미지

감사합니다.

저도 stmdb 를 의심하긴 했었으나,

stmdb 이던 stmda 이던, Rn! 을 사용해서 자동인덱스를 사용했을 경우에
차이는 스택에 들어가 있는 데이터의 위치일뿐 Rn 의 최종 주소값 자체는
동일한것으로 알고 있습니다.

다만 제가 다시 역어셈블한 코드를 읽으면서 메모리 주소를 되집어 보니

sp를 ip 에 복사하고 ip에서 4를 뺀 다음 fp 에 값을 넣은 후에

스택에 내부변수 할당은 fp 를 기준으로 하여,

fp, ip, lr, pc 를 백업한 스택상위와

내부변수가 할당되는 첫번째 주소 사이에 4byte 를 떨어뜨려 놓았네요.

대체 왜 그런지는 여전히 알 수 없긴 합니다..

일단 제가 나름대로 내린 결론을 가지고 본문을 다시 수정하긴 하였습니다.

관심 가져 주셔서 감사합니다..^^

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

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

powerc20의 이미지

제 생각에 stmdb를 사용하면 아래와 같은 그림이 되는 것 같습니다.

 
              +-------------------+
    a04f fffc |                   |  <---- ip
              +-------------------+
    a04f fff8 |       fp          |  <---- fp
              +-------------------+
    a04f fff4 |       ip          |
              +-------------------+
    a04f fff0 |       lr          |
              +-------------------+
    a04f ffec |       pc          |
              +-------------------+
    a04f ffe8 |        a          |  <---- [fp - 16]
              +-------------------+
    a04f ffe4 |        b          |  <---- sp
              +-------------------+
    a04f ffe0 |        c          |  <---- [fp - 24]
              +-------------------+
              |                   |
              |       :           |
              |       :           |

그래서 스택의 시작 주소를 a04f ffff로 주셨다면 4바이트를 버리는 일이 없을 것 같다는 말슴을 드린 것입니다.

즐거운 하루 되세요.

나빌레라의 이미지

정말 감사합니다.

powerc20 님의 말씀이 맞는 듯 합니다.

^^; 어줍잖은 지식으로 강좌를 쓰니 오류가 많군요.

강좌 본문은 오늘중으로 수정 하겠습니다.

매번 이렇게 댓글 달아 주시고, 유심히 읽어주셔서 저는 얼마나 고마운지 모른답니다..^^;

다시 한번 감사드립니다.
---------------
얇은 사 하이얀 고깔은 고이 접어서 나빌레라

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

powerc20의 이미지

나빌레라님의 강좌 정말 재미있게 보고 있습니다.

너무 감사하구요. 다음 강좌 기대하겠습니다.(너무 부담을 드리는 거 같아서 죄송합니다.)

inniskun의 이미지

우우우~~...
(변변치 못한자의 질투소리)

눈에 보이는 모든것은 보이지 않는 것들로 이루워져 있다.
Nobody reachs the Truth~*

눈에 보이는 모든것은 보이지 않는 것들로 이루워져 있다.
Nobody reachs the Truth~*

댓글 달기

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
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.