Part9. 프레임워크 컴파일 및 링크 방법

원문 :  http://kkamagui.springnote.com/pages/347128

 

들어가기 전에...

0.시작하면서...

 프레임워크는 부트 로더와 커널 로더 그리고 커널의 세가지로 나누어진다고 앞서 이야기 했다. 이제 각각을 컴파일하고 링크하여 커널 이미지를 생성해보자.

 

1.컴파일(Compile) 및 링크(Link) 환경

1.1 프레임워크 1.0.3 버전 이전 사용자

 프레임워크 1.0.3 버전 이전의 사용자는 아래의 배치 파일을 프레임워크 루트 폴더에서 찾을 수 있다.

  • 부트 로더 및 부트 로더 컴파일 및 링크 : MakeBoot.bat
  • 커널 : MakeKernel.bat
  • 커널 이미지 만들기 : MakeImg.bat

 배치 파일로 되어있는데 각 파일을 열어보면 단순한 몇개의 명령으로 되어있다. 만약 위 단계에서 에러가 발생하면 에러메시지가 화면에 출력되게 되고, 해당 열을 찾아서 에러를 수정하고 다시 컴파일 하는 방법으로 작업을 계속 수행하면 된다.

 아마 커널을 자주 수정하게 될 것이므로  MakeKernel.batMakeImg.bat 배치 파일은 마르고 닳도록 입력하게 될 것이다.

 

1.2 프레임워크 1.0.3 버전 이상 사용자

 프레임워크 1.0.3 버전 이상 사용자는 이클립스 환경에서 편리하게 사용하기위해 makefile이 통합되어있다. 빌드하는 방법은 간단히 아래와 같이 입력하면 된다.

make -f makefile 또는 make

 나머지는 makefile이 알아서 처리해준다. 에러가 발생하지 않는다면 disk.img 파일까지 완전히 생성해 줄 것이다. 간혹 종속성의 문제로 인해 제대로 빌드되지 않는 상황이 발생하는데 아래와 같이 입력하여 프로젝트를 깨끗히 정리한다음 다시 빌드를 수행하도록 하자.

make clean

 

1.3 커널 이미지 생성

 커널 이미지 만들기를 수행하면 내부적으로 Boot Image Maker(BIM.exe)라는 파일을 사용하는데, 자작한 프로그램이다. 이 프로그램이 하는 역할은 부트 로더와 커널로더 그리고 커널 이미지를 섹터 크기(512Byte)로 정렬한 다음 하나의 디스크 이미지 파일로 생성하는 것이다.

 부가적인 역할은 부트 로더 영역의 일부를 할애하여 전체 이미지의 크기 및 커널 이미지의 시작 위치 등을 기록하는 것이다. 이 값은 부트 로더가 디스크로부터 메모리에 로딩해야하는 섹터의 크기이며, 커널 로더가 1Mbyte 위치에 재배치해야하는 크기이다.

 BIM 코드는 아직 정리가 안되었기 때문에 추후에 올리기로 하고, 지금은 파일 상에서 어떻게 구성되는지만 확인하고 넘어가자.

 커널이미지.PNG

<Boot Image Maker가 생성하는 이미지의 구성>

 

 위에서 보는 것과 같이 단순히 섹터크기로 맞추어 연결해 주며, 부트 로더의 뒷부분에 전체 이미지의 크기, 커널 시작 섹터, 커널 크기를 넣어주는 역할만 한다.

 

 

2.실제 컴파일 및 링크 화면

2.1 프레임워크 1.0.3 버전 이전 사용자

 아래는 MakeBoot.bat가 정상적으로 실행되었을 때 화면이다.

Makeboot.PNG

<MakeBoot.bat 실행>

 이 화면 외에 다른 메시지가 보인다면 그것은 에러 메시지이므로 에러를 처리하도록 하자.

 

 아래는 MakeKernel.bat가 정상적으로 실행되었을 때 화면이다.

MakeKernel.PNG

<MakeKernel.bat 실행>

 이 화면 외에 다른 메시지가 보인다면 그것은 에러 메시지이므로 에러를 처리하도록 하자.

 

 마지막으로  MakeImg.bat가 정상적으로 실행되었을 때 화면이다.

MakeImg.PNG

<MakeImg.bat 실행>

 이 화면 외에 다른 메시지가 보인다면 그것은 에러 메시지이므로 에러를 처리하도록 하자.

 

2.2 프레임워크 1.0.3 버전 이상 사용자

 아래는 make가 정상적으로 실행되었을 때 화면이다. 프레임워크 1.0.3 이상 버전에 대한 빌드 및 환경 설정은 20 작업환경 설치 문서를 참고하자.

 

 Framework1.PNGFramework2.PNG

<프레임워크 빌드 진행 화면(좌측) 및 빌드 완료 화면(우측)>

 

 

3.커널에 파일 추가 방법

3.1 프레임워크 1.0.3 버전 이전 사용자

 커널에 기능을 추가하다보면 파일을 추가로 더 포함해야하는 경우가 생긴다. 그럴 경우 00Kernel 폴더의 Custom 폴더에 파일을 추가한 뒤에 makefile을 에디터로 열어서 수정하면 된다.

 그럼 어디를 수정해야 할까? 만약 스케줄링 기능을 넣어서 Scheduler.c 파일을 추가해야 한다고 가정하면 아래 부분을 고치면 된다.

  1. # 응용 프로그램 파일
    FW.o : $(CUSTOMDIR)Framework.c
    1. $(GCC) -o FW.o $(CUSTOMDIR)FrameWork.c
  2. KShell.o : $(CUSTOMDIR)KShell.c
    1. $(GCC) -o KShell.o $(CUSTOMDIR)KShell.c
  3. Sch.o : $(CUSTOMDIR)Scheduler.c 
    1. $(GCC) -o Sch.o $(CUSTOMDIR)Scheduler.c 
  4. #Object 파일 이름 다 적기
    #아래의 순서대로 링크된다.
    OBJ = A.o K.o Is.o D.o Int.o Key.o Stdlib.o Task.o FW.o KShell.o Sch.o

  위 처럼 파란색 부분을 추가해서 다시 makekernel.bat를 실행하면 정상적으로 커널에 포함할 수 있다.

 

3.2 프레임워크 1.0.3 버전 이상 사용자

 프레임워크 1.0.3 버전부터는 00Kernel 폴더의 Custom 폴더 및 FW 폴더 밑의 *.c 파일을 모두 찾아서 자동으로 빌드하도록 구성되어있다. 따라서 Custom 폴더 및 FW 폴더 밑에 .c 파일을 생성하고 make를 실행하면 된다.

 

 

4.마치면서...

 프레임워크 각 버전별로 빌드하고 이미지를 생성하는 방법에 대해서 알아보았다. 다음에는 프레임워크의 부트 로더 코드에 대해서 알아보자.

 

 

5.첨부

 

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

이 글은 스프링노트에서 작성되었습니다.

Part8. 까마귀(KKAMAGUI) 프레임워크(Framework) 설치

원본 :  http://kkamagui.springnote.com/pages/345299

 

들어가기 전에...

0.시작하면서...

 프레임워크 릴리즈에 대한 내용은  21 커널 프레임워크 소스 릴리즈 내용을 참고하자. 다음부터는 프레임워크를 사용해서 커널을 만들고 실행하는 것을 주제로 진행할까 한다.

 

이클립스를 이용하면 편리하게 빌드하고 테스트할 수 있데, 이클립스 설치 및 설정에 대한 자세한 내용은 06 이클립스(Eclipse) CDT 설치, 07 이클립스(Eclipse) 단축키 및 환경설정에 대한 문서를 참고하자.

 

프레임워크 빌드 환경에 대한 설정은 20 작업환경 설치 문서를 참고하자.

 

 

 

이 글은 스프링노트에서 작성되었습니다.

이 글은 스프링노트에서 작성되었습니다.

Part7. 까마귀(KKAMAGUI) 프레임워크(Framework) 소개

원문 :  http://kkamagui.springnote.com/pages/343186

 

들어가기 전에...

0.시작하면서...

 길고 긴 시간을 거쳐서 드디어 프레임워크(Framework)를 설명할 시간이 왔다. 프레임워크에 대한 이해를 돕기 위해 앞서 여러 파트에서 이것 저것 설명했는데, 설명이 부족했던 부분은 프레임워크 설명을 진행하면서 보충하겠다.

 그럼 지금부터 프레임워크에 대해서 하나하나 알아보자.

 

1.프레임워크(Framework) 블록 다이어그램

 아래는 프레임워크를 구성하는 블록 다이어그램이다.

 프레임워크_블록다이어그램.PNG

<Framework Block Diagram>

 

 프레임워크는 위와 같이 크게 3개의 프로그램으로 구성되어있고, 각 프로그램의 역할은 아래와 같다.

  • Boot Loader : BIOS에서 제어를 넘겨받았을 때, 실행되는 부트 코드(Boot Code). 커널 로더(Kernel Loader)와 커널(Kernel)을 메모리에 로딩하는 역할. 로딩 완료 후 커널 로더로 제어 이관
  • Kernel Loader : 16bit 모드에서 32bit 모드로 전환하는 역할. 커널을 1Mbyte 위치에 재배치 한 후 커널로 제어 이관
  • Kernel : 실제 커널이 존재하는 부분. 메모리/인터럽트/태스크의 초기 설정을 수행하여 태스트들이 정상적으로 동작할 수 있도록 환경 설정 수행. 태스크 동작을 위한 Standard Library 제공

 

2.프레임워크(Framework) 실행

 프레임워크를 실행하면 어떻게 되며, 어떤 상태에서 커널 코드를 작성하게 되는 걸까? 아래는 프레임워크 기본 소스를 그대로 컴파일 했을 때 화면이다(Virtualbox를 이용해서 실행시킨 장면).

Startup.PNG

<Framework 실행 화면>

 멋지지 않는가? 골치 아픈 문제를 다 뒤로하고 컴파일만 하면 간단한 쉘까지 포함한 아주~!! 간단한 커널이 나오게 된다. 만약 여기까지 아무것도 모르는 상태에서 어셈블리어 공부하고 Intel Architecture 공부하는 순서를 밟아서 진행한다면 사람에 따라 다르겠지만 무진장 @0@)/~ 걸린다.

 초반에 작성하는 16bit에서 32bit 전환 코드를 작성하기 위해서 Intel CPU Architecture를 정확히 이해하는데 걸리는 시간만 계산한다 해도 무시 못한다. 항상 겪는 문제지만 이론과 실제는 다르기 때문에 코드를 구현할 수 있는 수준에 오르기까지 또 시간이 걸리고, 구현했다 하더라도 열악한 디버깅 환경으로 인해 각종 fault에 부딪혀 좌절하게 된다(실제로 정석대로 밟아가는 많은 사람들이 이 부분에서 어려움을 겪는다).

 

 프레임워크를 사용하게되면 그러한 초반 로드(Load)를 거의 무시할 수 있기 때문에 실질적인 커널 개발에 시간을 더 많이 투자할 수 있다.

 

 

3.메모리 레이아웃(Memory Layout) 및 커널 재배치(Kernel Relocation)

3.1 메모리 레이아웃(Memory Layout)

 자 그럼 이제 프레임워크가 대충 무슨 기능을 해주는가 알아봤으니, 프레임워크의 메모리 레이아웃(Memory Layout)을 한번 보자. (빈영역이라 표현한 부분은 실제로 아무것도 없는 영역이란 의미가 아니라 어떤 용도로 사용되는지 정확히 알 수 없는 영역이다, BIOS마다 빈 영역의 용도는 다를 수 있다).

메모리_레이아웃.PNG

<Framework Memory Layout>

 

 그림으로 보면 복잡해 보이지만 차근차근 살펴보자. 커널은 1Mbyte 위치부터 시작하고 커널 스택은 4Mbyte가 Bottom이 되서 1Mbyte 영역 방향으로 자란다. 다시 말해 커널 영역은 총 1~4Mbyte 영역이고 1Mbyte 영역부터 커널 코드가 존재하고 스택 영역의 시작은 커널 코드 영역 끝 부분 ~ 4Mbyte 까지라는 것이다.

 커널 코드의 크기가 커지면 사용가능한 스택의 양이 줄어든다는 이야기인가? 이론상으로는 커널이 커지면 스택의 공간이 줄어든다. 이 말은 스택에 공간을 많이 할당할 수록 커널의 코드를 덮어쓸 확률이 늘어난다는 말과 같다.

 하지만 걱정은 잠시 미루어 두자. 실제로 커널 코드를 작성해 보면 커널이 큰 기능을 하지 않는 한 1Mbyte가 잘 넘지 않기 때문에 크게 문제가 없다.

 만약 큰 크기의 어플리케이션을 작업해야 한다면 덮어쓸 가능성이 있지 않는가? 물론 가능성이 높지만 그러한 경우는 커널을 작성할 때 커널 코드 안에 큰 어플리케이션을 밀어넣는 것이 아니라, 어플리케이션을 동적으로 로딩해서 메모리에 배치하고 실행하는 방식으로 해결해야 한다. 큰 어플리케이션 코드가 커널에 있다는 것 자체도 낭비이고 커널은 실제로 꼭 필요한 코드만 포함하는 것이 옳다.

 

3.2 커널 재배치(Kernel relocation)

 메모리 맵을 자세히 살펴보면 커널이 1Mbyte 영역에도 존재하지만 0x10000 영역에도 존재한다. 이것은 부트 로더에서 로딩한 것으로 부트 로더는 16bit 코드이기 때문에 1Mbyte 이상 영역으로 접근하기가 어렵다. 따라서 커널을 일단 1Mbyte 영역 아래에 로딩한 다음 다시 위치를 옮겨야 한다. 아래는 이 과정을 순서대로 나열한 것이다.

  1. 부트 로더가 0x10000 영역에 커널 로더와 커널을 적재한다.
  2. 부트 로더가 커널 로더로 제어를 이관하고 커널 로더를 실행한다.
  3. 커널 로더가 32Bit 모드로 전환하고 0x10000에 로딩된 커널을 0x100000(1Mbyte) 영역으로 이동한다.
  4. 커널 로더가 커널로 제어를 이관한다.

 

 자 그럼, 4Mbyte 이후의 영역은 어떻게 할까? 그것은 쓰기 나름이다. 동적 할당을 위해 영역을 할당할 수 있고, 필요하면 커널 스택 설정을 바꿔서 스택으로 사용할 수도 있다.

 프레임워크에서는 4Mbyte ~ 8Mbyte 영역을 동적 할당 영역으로 사용할 것이므로 참고로 알아두자.

 

4.마치면서...

 프레임워크에 대해서 간단히 알아보았다. 다음에는 프레임워크 컴파일 및 실행환경 설치에 대해 알아보자.

 

5.첨부

 

 

이 글은 스프링노트에서 작성되었습니다.

이 글은 스프링노트에서 작성되었습니다.

 Part6. 부팅(Booting) 과정 소개

원문 :  http://kkamagui.springnote.com/pages/342881

 

들어가기 전에...

0.시작하면서...

 우리는 앞서 내용에서 OS를 제작할 때 알아야할 몇가지에 대해서 가볍게(??) 알아봤다. 너무 가벼운 관계로 쪼금 찔리긴 하지만, 일단 그 정도에서 접고 슬슬 프레임워크에 대해서 설명을 하겠다.

 

1.부팅(Booting)이란?

 프레임워크의 동작 과정을 알려면 첫째로 부팅과정에 대해서 알아야 한다. 그럼 도대체 부팅이란 것이 무엇일까?

 우리가 매일 쓰는 네이버의 백과사전을 검색해 보았다.

Booting(부팅)은 컴퓨터의 시동을 뜻하며, 보조기억장치(bootable device:주로 플로피디스크 또는 하드디스크)를 사용하여 컴퓨터가 동작할 수 있도록 시스템에 운영 체제를 불러 들여 작동을 준비하는 작업이다. 즉, 컴퓨터 시스템을 시동하거나 초기 설정하는 것을 뜻한다.
일반적으로 컴퓨터의 전원을 켜면 먼저 부팅 프로그램을 불러 들이고, 부팅 프로그램은 운영체제(Operating System)를 기억장치로 불러 들여 컴퓨터가 작동을 할 수 있도록 준비한다.
처음 사용자가 컴퓨터의 전원을 넣는 것으로 시작하는 부팅을 콜드(cold)부팅이라 하고 원래 전원이 들어와 있는 상태에서 하는 부팅을 웜(warm)부팅이라 한다.

 그렇다. 바로 컴퓨터를 시동하는 작업을 이야기 하는 것이다. 그럼 우리가 PC를 시동하면 어떤일이 발생하는가?

 다들 알겠지만 시커먼 바탕화면에 마치 무슨 콘솔(Console)처럼 흰 글자가 화면에 찍히고 램용량 표시/CPU/HDD 정보 표시 등등이 나타난다. 이 과정을 POST(Power On Self Test)라고 하는데 메인 보드의 BIOS(Basic Input Output System)에서 자체적인 하드웨어 점검을 해주는 것이다. 여기서 이상이 생기면 비프(Beep) 음이 울리게 되고 더이상 진행되지 않는다. 이 테스트에서 별 이상이 없으면 하드디스크를 읽어서 메모리에 적재한 뒤에 OS가 실행되게  된다.

 

2.부팅(Booting) 과정

 그럼 대충 어떤 것인지 알아봤으니 조금더 상세하게 알아보자. 아래는 실제로 부팅이 될때 그 과정을 나타낸 것이다.

 부팅과정(1).PNG

<Boot Process>

 

 

 위에서 보면 알겠지만, BIOS는 Boot Sector를 로딩해주는 것으로 일을 마친다. 다시말하면 Boot Sector부터 커널을 로딩해서 OS를 동작시키는 것은 우리의 몫이다.

 그렇다면 부트 코드(Boot Code)부터 전부 작성해야 한다는 이야기인가? 그렇다. 전부 다 작성해야 한다.

 부트코드를 작성하는 데, 설마 C로 하겠지라고 생각하는 사람은 없을 것이라 생각한다(예전에 아무것도 모를 때, 친구가 베이직으로 OS를 작성할 것이라고 이야기 했었는데... 갑자기 그 생각이 난다).

 생짜로 어셈블리어로 짠다 @0@)/~~~ ㅜ_ㅜ

 

3.부트 코드(Boot Code) 및 커널 로더(Kernel Loader)

 부트코드를 어셈블리어로 작성해야한다는 사실이 초보 레벨의 프로그래머에게 굉장한 부담이 되긴 하지만, 좋은 소식이 있다. 사실 부트 코드가 굉장히 간단하고, 기능이 다 비슷하기 때문에 어느정도 정형화된 코드가 있다.

 또한 한번 만들고 나면 거의 손볼일도 없기 때문에 여기저기 참고해서 만들어도 큰 문제가 없다(배우는 입장에서는 한번 짜보는 것이 당연히!!! 좋다). 부트 코드는 BIOS에 의해서 0x7C00에 로딩되게 되는데, 그렇다면 이 부트코드는 어디에 있는 것일까?

 전통적인 이유로 인해 16bit XT시절부터 지금까지 하드디스크 또는 플로피 디스크의 첫번째 섹터에 부트 코드가 있다. 이런 물리적인 장치의 한 섹터의 크기가 512Byte이므로 부트 코드의 크기도 512Byte로 한정되어있다. 512Byte라니... 이 크기로 무엇을 하란 말인가?

 실제로 부트코드에서 뭐 좀 제대로 일 할려고 하면 512Byte의 크기를 넘는다. 따라서 부트 코드에서는 간단히 이미지를 디스크에서 메모리로 로딩하는 역할만 한다. 그후 커널을 재배치하고 기초적인 환경 설정을 해주는 프로그램을 실행하는데 그것이 커널 로더(Kernel Loader)이다. 커널 로더와 커널은 분리되어있는 경우도 있고 같이 있는 경우도 있는데, 프레임워크에서는 분리하여 구현하였다.

 프레임워크와 같이 분리하여 구현하는 경우 커널 로더가 커널 로딩에 책임을 지게되고 커널 실행을 위한 환경 설정도 책임지게 되므로 부트 코드는 아주 간단해지는 장점이 있다.

 

 

4.커널(Kernel) 실행

 마지막으로 커널 로딩이 끝나고 나면 커널로더에서 커널의 시작 주소로 Jump하여 커널을 실행하고 이제 본격적인 OS의 동작이 시작된다.

 

 

5.마치면서...

 오늘은 간단히 부팅과정에 대해서 알아봤는데, 다음에는 프레임워크에 대해서 좀더 자세히 알아보자.

 

 

6.첨부

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

이 글은 스프링노트에서 작성되었습니다.

Part5. Intel Architecture에 대한 소개

원문 :  http://kkamagui.springnote.com/pages/339549

 

들어가기 전에...


0.시작하면서...

 앞서 인터럽트와 어셈블리어에 대해서 설명을 했다. 이제 프레임워크의 기반 환경인  Intel CPU Architecture에 대해서 알아보자.

 

1.Intel Architecture 다이어그램

 아래 그림은 System Programming 관점에서 본 i386 Architecture에 대한 그림이다. (참고로 여기 나오는 모든 그림은 다 Intel Architecture Manual에서 찾을 수 있다).
 Intel3.png

 

<Intel System Architecture>

 

 

 굉장히 복잡하게 그려져있는데, 프레임워크를 이해하기위해 필요한 부분은 General Register, Segment Register, Descriptor 정도이다.

 

2.레지스터(Register)와 16/32bit 모드(Mode)

 i386 Architecture는 16bit와 32bit 두가지 모드를 가지고 있는데, 16bit모드 일때와 32bit 모드일때 Segment Register의 역할이 다르고 사용가능한 General Register의 크기가 다르다는 차이가 있다. 16bit 모드에 대해서는 궁금한 사람은 Intel Architecture 메뉴얼을 참조 하도록하고 여기서는 32bit 모드에 중점을 두겠다.

 

2.1 레지스터(Register) 종류

 32bit 모드보호 모드(Protected Mode)라고 하는데, 권한에 따른 메모리 보호와 접근 범위 설정이 가능하기 때문이다. 32bit 모드에서 레지스터는 아래와 같이 구성된다.

Intel4.png

<32bit Mode Register>

 

 위의 각 레지스터 역할은 아래와 같다. 

  • General Register : 연산이나 비교, 저장 용으로 사용되는 레지스터
  • Segment Register : Descriptor를 지시하는 역할을 하는 레지스터
  • EFLAGS Register : 비교연산이나 산술연산의 결과의 Overflow, Underflow 같은 연산에 대한 결과 값과 인터럽터 가능/불가와 같은 전체적인 상태값을 저장하는 레지스터
  • EIP Register : 수행중인 명령의 위치를 가리키는 레지스터(Instruction Pointer)

 

 Segment Register는 디스크립터(Descriptor)를 지시하는 역할을 한다고 했는데, 디스크립터에 대해서는 다음에 상세하게 알아보도록 하자.

 

2.2 세그먼트 셀렉터(Segment Selector)

 32bit Mode에서 Segment Register의 역할은 아주 단순하다. 바로 디스크립터를 지시하고 현재 Level(Ring 0, Ring1, Ring2, Ring 3)을 표시하는 역할이다.

 아래는 Intel Architecture에서 Segment Register의 비트별 용도를 나타낸 것이다.

 Intel1.PNG

<Segment Selector>

 

 Bit 3~15까지가 실제 디스크립터의 인덱스를 의미한다. 그렇다면 아래의 3bit의 용도는 무엇일까? 3bit의 용도는 아래와 같다.

  • TI bit : 디스크립터가 전역 디스크립터 테이블(Global Descriptor Table)에 있는지 지역 디스크립터 테이블(Local Descriptor Table)에 있는지를 의미
  • RPL bit : 해당 디스크립터에 접근하기위한 권한 레벨. 만약 RPL이 디스크립터에 설정된 DPL보다 값이 크면(낮은 레벨이면) 디스크립터에 접근 불가

 디스크립터의 인덱스 설정과 동시에 권한을 설정해 주는 필드로 체워져 있음을 알 수 있다. 권한에 따른 보호는 CPL과 RPL 그리고 DPL이 서로 엮여져 있는데 이에 대한 자세한 내용은 참고. Intel i386 CPU의 보호(Protection) 방식을 참고하자.

 

2.3 디스크립터(Descriptor)

 디스크립터(Descriptor)가 무엇인고 하니, 메모리 범위를 지정하고 해당 메모리의 속성을 지정하는 기술자(Descriptor)이다. 언듯 이해가 잘 안갈텐데, 이것을 이해하려면 OS의 메모리 관리 기법중에 세그먼테이션(Segmentation)에 대한 내용을 알고 있어야 한다. 세부적인 내용은 OS책을 보도록 하고 간단히 개요만 설명하겠다.

 

 Intel5.png

<Segment Register와 Segment의 관계>

 

 

 위의 그림이 Segment Register와 디스크립터의 관계를 표시한 것이다. 각 Segment Register는 한개의 디스크립터를 지시할 수 있고,  디스크립터는 코드영역인지, 데이터 영역인지, 스택영역인지에 대한 속성과 범위(시작과 끝)를 가지는 세그먼트(Segment)의 내용을 담고 있다. 즉 OS의 각 영역은 여러개의 조각(Segment)로 구분될 수 있으며, 각각의 세그먼트에 대한 구체적인 내용(속성, 시작, 끝 등등)을 가지고 있는 것이 디스크립터이다.

 하나의 디스크립터는 여러개의 Segment Register에 의해 선택될 수 있고, 극단적인 상황에서 모든 Segment Register가 하나의 디스크립터를 지시할 수 있다.(단 해당 디스크립터의 속성은 그에 합당하는 속성(코드/데이터 속성 등등)을 모두 가지고 있어야 한다.)

 Intel2.PNG

<Descriptor 구조체>

 

 디스크립터는 위에서 보는 것과 같이 시작 및 크기, 속성, 특권 레벨(DPL)로 구성되어있다. 디스크립터에 대한 자세한 내용은 Intel Architecture Manual 및 참고. Intel i386 CPU의 보호(Protection) 방식 문서를 참고하도록 하자.

 

 그럼 Segment와 General Register와 Address 간의 관계는 어떻게 되는걸까? 암묵적으로 Segment Register가 사용되는 부분은 아래와 같다.

1. CS : 코드 수행 시 암묵적으로 사용
2. DS : 데이터 접근 시 암묵적으로 사용
3. SS : 스택 접근 시 암묵적으로 사용

 

 그렇다면 절대 주소(Absolute Address)의 계산은 어떻게 되는걸까?

1. 실제 코드가 수행되는 메모리 절대 주소 : CS가 가리키는 디스크립터의 Base Address + EIP
2. 데이터 접근이 수행되는 메모리 절대 주소 : DS가 가리키는 디스크립터의 Base Address + Memory Address
3. 스택 연산이 수행되는 메모리 절대 주소 : SS가 가리키는 디스크립터의 Base Address + ESP, EBP

 위와 같이 된다. 우리가 접근하는 위치는 디스크립터의 Base Address를 시작으로 하는 위치가 되기 때문에 이를 잘 생각해야 한다. 커널을 빌드하고 이를 실행했을 때 영향을 미치기 때문이다. 프레임워크에서는 단순하게 하기위해 Segment를 크게 2개(코드/데이터)로 구분하고 Base Address를 0, 크기를 4GByte로 설정했기 때문에 커널 코드에서 사용하는 위치는 절대 주소라고 보면 된다.

 

 

2.4 프로세스 모드(Processor Mode)

 자 그럼 여기서 잠깐만 Intel Architecture가 가지는 여러 모드(Mode)에 대해서 집고 넘어가자. 지금 16bit와 32bit 모드만 설명했는데, 2개의 모드가 전부일까?

 그렇지 않다. Intel Architecture Volume3을 참고하면 알겠지만 여러가지 모드가 존재한다.

Intel_Mode.PNG

<Intel CPU Mode 전환>

 

 위의 모드에 대해서 전부 설명하려면 책 한권으로도 부족하므로 궁금한 사람은 Intel Architecture Manual을 참고하도록 하고 간단히 설명만 하겠다.

  • Real-Address Mode : 16bit 모드
  • Protected Mode : 32bit 모드. 프레임워크가 사용하는 모드
  • Virtual-8086 Mode : 32bit 모드에서 16bit 모드를 실행하기위한 에뮬레이션 환경.
  • IA-32e Mode : 64bit 모드 또한 32bit 호환 모드

 우리가 주로 사용하는 모드는 Protected Mode 이고 처음 시스템이 부팅되었을 때 모드는 Real-Address Mode라는 것만 알아두자.

 

 

3.스택(Stack)

 마지막으로 프로그램 제작이든 커널 제작이든 꼭 필요한 스택에 대해서 알아보자.

Intel6.png

<Intel CPU의 Stack 구조>

 

 

 위의 그림에서도 나와있지만 i386의 스택은 높은 주소(0xFFFFFFFF 쪽)를 Base로 하고 낮은주소(0x0000 쪽)로 자라게 되어있다. 그렇기 때문에 앞서 본 호출 규약(Calling Convention)에서도 Parameter 접근시 ebp + 0x08과 같은 숫자를 더하는 방식으로 접근한 것이다.
 커널 프로그래밍을 하면 스택이 어느 방향으로 자라고 Top이 항상 어디에 위치하는지 확실하게 알고 있어야 한다. C와 어셈블리어 간의 함수 호출이라던지, 하드웨어적인 도움을 받지 않고 소프트웨어적으로 하는 태스트 스위칭이라던지 하는 부분은 모두 스택을 사용하기 때문에 스택을 제대로 이해하지 못하면 앞으로 나갈 수 없다.

 스택을 제대로 이해하고 있으면 불가능하게 보이는 일도 가능하게 만들 수 있다(32bit 모드에서 16bit BIOS 함수를 호출하는 것과 같은 일들...). 스택의 개념을 머리속에 항상 새겨놓도록 하자.

 

4.마치면서...

 Intel Architecture에 대해서 간단하게 알아보았다. 다음부터는 프레임워크에 대한 설명을 할 것이므로 지금까지 한 내용을 참고하면서 보면 많은 도움이 될 것이다.

 

 

 

 

이 글은 스프링노트에서 작성되었습니다.

이 글은 스프링노트에서 작성되었습니다.

Part4. 어셈블리어(Assembly Language)와 C 그리고 호출 규약(Calling Convension)

원문 :  http://kkamagui.springnote.com/pages/339546

 

들어가기 전에...

 

0.시작하면서...

OS를 개발하면서 초반에 어셈블리어로 작성한 코드를 보면, 사용한 어셈블리어 명령이 몇 종류 없는 것을 알 수 있다. 그것도 아주 기초적인 수준의 어셈블리어만 사용했는데, 역으로 말하면 몇가지 종류의 어셈블리어만 알고 있으면 부트로더(Boot Loader), 커널로더(Kernel Loader), 그리고 기타 초기화 함수를 작성할 수 있다.

 

1.어셈블리어(Assembly Language) 기초 명령

 아래는 기초 명령의 리스트이다(Intel Style의 명령이라 가정한다).

 

  • mov A, B : B에서 A로 값을 이동
  • cmp A, B : 두 값을 비교하여 결과를 Flags 레지스터에 업데이트
  • rep instruction : insturction을 CX 레지스터의 값 만큼 반복 수행
  • call X : Stack에 Return Address를 삽입하고 jump 수행
  • jmp X : 무조건 해당 주소로 jump
  • je, ja X : 조건 분기 명령. Flags 레지스터의 플레그 값에 따라서 jmp 수행(보통 cmp와 같은 명령어와 함께 사용)
  • push X: 스택에 값을 저장
  • pusha, pushad : 스택에 모든 레지스터 값을 저장. EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI 저장
  • pop X : 스택에서 값을 꺼냄
  • popa, popad : 스택에서 모든 레지스터의 값을 꺼냄. 위의 pushad 명령과 같은 순서의 레지스터 사용
  • add A, B : A에 B의 값을 더함
  • sub A, B : A에서 B의 값을 뺌
  • mul A : EAX의 값과 A의 값을 곱하여 A에 저장
  • inc A : A의 값을 1 증가시킴
  • int X : X번째의 Software Interrupt를 발생시킴
  • ret, retn : Stack에 포함된 Return Address를 꺼내서 해당 주소로 복구(보통 Call 명령과 같이 사용)
  • iret, iretd : 인터럽트 처리 시에 모든 처리를 완료하고 다시 태스크로 복구
  • or A, B : A에 B값을 OR
  • xor A, B : A에 B값을 XOR
  • not A : A의 값을 반전(0->1, 1->0)
  • lgdt : GDT를 설정(Intel Architecture 특수 명령)
  • lidt : IDT를 설정(Intel Architecture 특수 명령)
  • lldt : LDT를 설정(Intel Architecture 특수 명령)
  • ltr : Task Register에 TSS를 설정(Intel Architecture 특수 명령)
  • clts : Task Switching 플래그를 0으로 설정(Intel Architecture 특수 명령)
  • cli : 인터럽트 불가 설정
  • sti : 인터럽트 가능 설정
  • fninit : FPU 초기화 명령(x87 Floating Point Unit 관련 명령)
  • ... 기타 등등

 

 물론 전부를 나열하지는 않았지만 척 봐도 알 수 있는 기본적인 명령어들이다. 물론 성능을 고려한다면 더 많은 어셈블리어 명령어들이 리스트에 포함되겠지만, 성능적인 면을 고려하지 않는다면 위의 함수 정도면 OK다.

 위의 함수에 대한 기본적인 기능들은 Intel Architecture Manual Volume 2 Instruction Set 문서를 참고하면 된다. 위의 명령어를 사용하여 프로그램을 작성하고 싶은 사람은 New Wide Assembler(NASM)을 이용하면 테스트 가능한데, 10 참고자료를 참고하면 간단히 함수를 생성하고 빌드 할 수 있다.

 http://nasm.sourceforge.net/ 홈페이지에 가면 컴파일러를 다운받을 수 있고 예제 및 문서도 제공하므로 한번 해보는 것도 괜찮을 듯 하다.

 

 

2.호출 규약(Calling Convention)

2.1 stdcall, cdecl, fastcall

 실제로 어셈블리어를 아는 것도 중요하지만 이 함수를 C 언어에서 어떻게 호출하여 사용할 것인가 하는 문제도 중요하다. 흔히들 호출 규약(Calling Convention)이라고 표현하는 이것은 함수를 호출하는 규약인데, 몇가지 방식이 존재한다.

  • stdcall(pascal) 방식 : 스택에 파라메터를 역순으로 삽입하고 함수를 호출. 스택의 정리작업을 호출된 함수에서 수행. 파스칼 언어 및 베이직 언어에서 사용하는 방식
  • cdecl 방식 : 스택에 파라메터를 넣는 방식은 stdcall과 같음. 단 스택의 정리작업을 호출한 함수에서 수행. C언어에서 사용하는 방식
  • fastcall 방식 : 몇개의 파라메터는 레지스터를 통해 넘기고 나머지 파라메터는 스택을 사용하는 방식

 위의 세가지 중에서 보편적인 방식 두가지는 stdcall 및 cdecl 방식이다. 이 두가지 방식의 가장 큰 차이점은 스택의 정리를 누가 하는 가이다.

 stdcall 방식 같은 경우 Callee(호출 된 함수)에서 스택 정리를 하므로 Caller(호출하는 함수)와 Callee 모두 파라메터의 개수를 알고 있어야 정상적인 처리가 가능하다.

 반면 cdecl 방식 같은 경우 Caller에서 스택 정리를 하므로 Callee는 파라메터의 개수를 정확하게 몰라도 된다. 바로 이 점이 C 언어의 가변인자(Variable Argument)를 가능하게 하는 것이다(printf와 같은 함수를 생각해보자).

 가변인자에 대해서는 나중에 알아보고 우리가 사용할 cdecl에 대해서 자세히 알아보자.

 

2.2 cdecl 분석

 아래는 간단한 C 프로그램을 작성한 것이다.

  1. int DoSomething( int a, int b )
    {
        int c;
        c = a+b;
        return c;
    }
  2. int main(int argc, char* argv[])
    {
        DoSomething( 1, 2 );
  3. }

 

 간단하게 파라메터 2개를 받아서 그중 첫번째 파라메터를 리턴하는 함수이다. 이것을 cdecl로 해서 컴파일 한 결과 나온 어셈블리어 결과는 아래와 같다.

  1. int DoSomething( int a, int b )
    {
        int c;
        c = a+b;
        return c;
       /* 여기가 어셈블리어로 변경된 코드
        push ebp
        mov ebp,esp
        push ecx
        mov eax,[ebp+08h]
        add eax,[ebp+0Ch]
        mov [ebp-04h],eax
        mov eax,[ebp-04h]
        mov esp,ebp
        pop ebp
        retn
       */
    }
  2. int main(int argc, char* argv[])
    {
        DoSomething( 1, 2 );
       /* 여기가 어셈블리어로 변경된 코드
        push ebp
        mov ebp,esp
        push 00000002h
        push 00000001h
        call SUB_L00401000
        add esp,00000008h <== 스택을 정리하는 부분
        pop ebp
        retn
       */
    }


 위에서 보면 파라메터를 역순으로 Push 하는것을 알 수 있으며 main 함수에서 "add esp,08" 명령을 통해 스택 정리를 수행함을 볼 수 있다. 여기서 주의해서 봐야 할 부분은 DoSomething 함수에서 어떻게 파라메터에 접근하고 또한 어떻게 함수 내부적으로 사용하는 레지스터를 관리하고 복원하는가 이다.

 아래는 Caller(main)Callee(DoSomething)의 스택의 상태를 표시한 것이다.

 CallingConvension.PNG

<Caller와 Callee의 Stack>

 왜 ESP로 접근하지 않고 EBP를 통해 파라메터에 접근하는 것일까? 위의 그림을 보면 왜 ebp + Index로 접근을 하는 지 알 수 있다. 스택의 Top을 의미하는 ESP 레지스터의 경우 코드 중간 중간에 스택을 사용하면서 계속 변하는 값이다. 그 반면에 파라메터의 위치는 항상 고정적이므로 스택의 Top을 이용해서 파라메터에 접근하려면 문제가 발생한다. 따라서 EBP에 ESP의 값을 처음 설정해 두고 EBP를 이용해서 고정된 Offset으로 접근하는 것이다.

 이와 같이 하면 스택의 Top이 계속 바뀌더라도 EBP가 초기의 스택 Top의 위치를 가지고 있으므로 EBP + 8, EBP+ 12과 같은 값으로 접근 가능하다. 위에서 초기에 Callee의 Stack에서 Push ebp를 하고 난 뒤에 Stack의 Top은 esp1을 가르키고 있다. 이 값을 ebp에 넣게 되므로 ebp를 이용하면 Parameter에 고정된 Index( 8, 12, 16...)으로 접근을 할 수 있는 것이다.

 

2.3 stdcall

 stdcall의 경우에는 cdecl과 거의 차이가 없고 스택을 정리하는 부분만 차이가 있다.

  1. int DoSomething( int a, int b )
    {
        int c;
        c = a+b;
        return c;
       /* 여기가 어셈블리어로 변경된 코드
        push ebp
        mov ebp,esp
        push ecx
        mov eax,[ebp+08h]
        add eax,[ebp+0Ch]
        mov [ebp-04h],eax
        mov eax,[ebp-04h]
        mov esp,ebp
        pop ebp
        retn 08h <== 스택을 정리하는 부분
       */
    }
  2. int main(int argc, char* argv[])
    {
        DoSomething( 1, 2 );
       /* 여기가 어셈블리어로 변경된 코드
        push ebp
        mov ebp,esp
        push 00000002h
        push 00000001h
        call SUB_L00401000
        pop ebp
        retn
       */
    }

 

2.4 프롤로그(prologue) 및 에필로그(epilogue)

 Callee의 스택을 다시 Caller의 스택으로 복원해야 하는데 스택 top을 저장하고 복원하고 하는 작업을 프롤로그(prologue), 에필로그(epilogue)라고 한다. 위에서 본 스택을 복구하는 작업이다.

 만약 우리가 어셈블리어 함수를 만든다면? 그리고 그 함수를 C에서 호출한다면? 아니면 그 반대의 경우라면 어셈블리어 함수를 어떻게 만들어야 할까? 그렇다. 위에서 본 것과 같은 형태 즉 cdecl의 형태를 그대로 따라서 만들면 된다.

 DoSomething() 함수의 프롤로그 에필로그 형태는 아주 일반적인 형태이므로 알아두도록 하자.(꼭 저렇게 구성할 필요는 없지만 일반적이므로 알아두자.)

 

 

3.마치면서...

 자 오늘 우리는 어떻게 어셈블리어 함수를 만들어서 파라메터를 넘겨 받을 것이며, 어셈블리어 함수에서 C 함수를 어떻게 호출해야 하는지, 혹은 그 반대의 경우 어떻게해야 하는지에 대해서 알아보았다. 다음에는 좀 더 깊은 내용에 대해서 알아보자.

 

 

 

이 글은 스프링노트에서 작성되었습니다.

Part3. 인터럽트(Interrupt)

들어가기 전에...

0.시작하면서...

앞서 커널 개발시에 권장되는(??) 알아야하는 몇가지에 대해서 언급했었다.
그럼 이제 찬찬히 그것들에 대해서 알아볼텐데... 오늘은 인터럽트(Interrupt)에 대해서 한번 볼까 한다.

1.인터럽트(Interrupt)란?

1.1 인터럽트(Interrupt)의 정의

인터럽트(Interrupt)는 내부 or 외부에서 특정한 이벤트로 인해 실행중인 코드를 중단하고 해당 이벤트를 처리하는 예외적인 상황을 말한다. Intel Architecture에서는 이런 예외적인 상황을 아래와 같이 크게 2가지로 구분하고 있다.

  • 1.Interrupt : External(Hardware generated) Interrupt와 Software generated Interrupt를 포함.
  • 2.Exception : Processor-detected program-error exception과 Software-generated exception과 Machine-check exception을 포함. fault, traps, abort로 구분됨

뭔가 상당히 복잡한데, 위 2가지에 대한 자세한 내용은 여기서 설명하지 않을 것이고, 궁금한 사람은 Intel Architecture Manual의 Volume 2 : Software Developer's Manual을 살펴보기 바란다.
간단히 알아야 할 것만 설명하면, 저런 예외적인 상황은 Hardware 또는 Software 적인 방법으로 발생할 수 있고, 우리가 프로그램을 실행하면서 발생하는 Divide By Zero와 같은 에러도 Exception으로 처리가 된다는 사실이다.

편의상 위의 두가지 모두를 인터럽트(Interrupt)라고 부르기로 하자.

1.2 인터럽트(Interrupt)와 컨텍스트(Context)

내가 소시적에(??) OS를 만들면서 여러책을 보았다. 그때 한 OS 책에서 본 내용이 아직도 기억에 남는데... 그 책은 인터럽트에 대해서 이렇게 설명해 놓았다.

프로그램을 실행 도중, 인터럽트가 발생할 시 현재 작업을 중단하고 상태를 저장한 뒤 인터럽트 서비스 루틴(ISR)을 실행한다. 실행이 완료되면 중단된 시점부터 실행이 재개된다.

여기서 상태를 컨텍스트(Context)라고 이야기하는데, 컨텍스트는 실행중인 프로세스의 내용을 말한다. 위의 항목에서 내가 궁금했던 점은... "상태를 저장한 뒤" 라는 부분... 어느 부분을 어떻게 저장하는 것인지가 명확하지 않았다. 나중에 Intel 메뉴얼을 뒤져서 정확하게 알게 되었지만, 알고 난뒤의 허탈함 and 배신감이란 이로 말할 수 없었다.

과연 "상태"를 어디까지 저장해주는걸까? 아래는 Intel 메뉴얼에 Software Developer's Manual에 나오는 그림이다.

Intel Architecture의 Interrupt 발생시 Stack의 변화

벌써 눈치를 챈 사람들도 있겠지만... 그렇다. 스택(Stack)에 단순히 Flags/CS/EIP/Error Code가 저장되는 것이 전부였다. @0@)/~
그 말은 ISR에서 사용하는 레지스터들은 "알아서" 저장하고 사용한 뒤 "알아서" 복원해야 한다는 이야기다. 위에서 보면 Privilege Level이 변하지 않는 경우는 Flags/CS/EIP/Error Code를 저장하며, 변하는 경우는 SS/ESP를 추가로 더 저장한다. 추가로 저장된 SS/ESP는 인터럽트 처리가 끝났을 때 하위 레벨의 스택으로 다시 돌아가는 용도로 사용하는 것이다.

왜 추가로 더 저장하는가 하니... Intel Architecture는 총 4개의 Level( Ring0 ~ Ring3)을 가지고 Level간에 사용하는 스택을 다르게 설정할 수 있다. Level이 변경될 때 마다 CPU는 해당 Level의 Stack으로 변경해 주게되고 이것을 Stack Switching이라고 부른다(모든 레벨이 같은 스택을 사용할 수도 있다. 일단 Level이 다르면 Intel CPU는 SS/ESP를 Handler의 스택에 저장해 준다는 것만 알아두자.)

2.인터럽트의 처리

2.1 인터럽트 서비스 루틴(Interrupt Service Routine-ISR)의 컨텍스트(Context) 저장

커널을 만들려면 위에서 언급했던 인터럽트들을 처리해 주는 인터럽트 서비스 루틴(Interrupt Service Routine-ISR)을 만들어야 하는데, 보통 아래와 같은 형태를 띈다.

_kIsr :  
    **  pushad**
    **  push ds  
    push es  
    push fs  
    push gs  <== 여기까지가 컨텍스트(Context)를 저장하는 부분**

    ; 일단 커널 쪽에 진입해야 하므로 세그먼트 재 설정   
    mov ax, 0x10  
    mov es, ax  
    mov ds, ax
    mov fs, ax  
    mov gs, ax  

    **call HandlerFunction  <== Handler 함수 호출 부분 **  

    **  pop gs  
    pop fs   
    pop es  
    pop ds  
    popad  <== 여기까지가 컨텍스트(Context)를 복구하는 부분  

    iretd  <== 인터럽트 처리 완료후 복구하는 부분**

간단히 설명하면 위의 파란색 부분은 사용할 레지스터들은 저장하고 핸들러 루틴을 불러서 필요한 처리를 한다음 다시 복원하는 코드이다. 붉은 색 부분은 실제로 스택에 저장된 Flags/CS/EIPSS/ESP를 복원하고 코드로 돌아가는 코드이다.

크게 어려운 어셈블리어 명령이 아니기 때문에 설명은 다음으로 미룬다. @0@)/~ 궁금한 사람은 역시 메뉴얼을 참조하면 된다.

2.2 핸들러 루틴(Handler Routine)

지금까지 ISR의 컨텍스트 저장 코드에 대해 간단히 보았다. 이제 HandlerFunction이 무엇을 하며 어떻게 작성해야 하는지 한번 알아보자.

인터럽트가 발생하면 원인이 Interrupt인가 Exception인가에 따라서 HandlerFunction의 실제 역할이 다르고, Interrupt or Exception의 세부분류에 따라 그 역할이 다르다.

보통 Handler는 C 함수로 작성되고 우리가 흔히 알고 있는 일반 함수 코드 처럼 작성된다. 아래는 프레임워크에서 사용된 키보드 핸들러의 소스코드이다.

   //-----------------------------------------------------------------------------  
    //  
    // 키보드 핸들러 버퍼에 값을 집어 넣는다.  
    //  
    //-----------------------------------------------------------------------------  
    void kIsrKeyboard( void )  
    {  
        BYTE  bCh;
        bCh = kReadPortB( KBD\_PORT\_BUFFER );
        kPrintchxy\_low( GDT\_VAR\_VIDEOMEMDESC, 11, 0, bCh, 0x05 );
        // 키를 버퍼에 넣는다.  
        kAddKeyToBuffer( bCh );

        // Bottom Half 사용  
        g_stBottomHalfManager.vstBottomHalfUnit[ 1 ].bFlag = TRUE;  
        kSendMasterEoi();  
    }

우리가 흔히 쓰는 함수와 같은 형태를 하고 있으니 이해하는데는 문제가 없을 것이다. 코드를 보면 키보드 포트에서 값을 읽어서 버퍼에 저장하는 역할을 한다는 것을 금방 알 수 있다.

그렇다면 ISR 함수와 우리가 사용하는 일반 함수와 차이점이 무엇일까? 함수가 불리어지는 시점의 차이가 가장 큰 차이다.

ISR은 인터럽트가 발생한 시점에서 호출되는 코드이기 때문에 장시간 걸리는 작업을 실행하면 시스템 전체의 성능에 영향을 미치게 된다. 인터럽트가 발생하면 CPU에서 기본적으로 인터럽트를 불가 상태로 만든다음 ISR 함수를 호출하게 된다. 다시 말하면 ISR 처리 루틴이 완전히 끝나지 않는 한 다른 인터럽트가 발생하지 못한다는 것이다. 이것은 아주 치명적인데, 예를 들어 특정 인터럽트 루틴에서 무한루프를 돌면 키보드/마우스/타이머 등등이 먹통이된다. @0@)/~!!!!

2.3 주의해야 할 점

** ISR에서 절때 시간이 많이 걸리는 작업을 해서는 않된다.**

이것은 진리이며, 자칫 잘못하면 시스템 전체의 성능을 느리게 만든다. 예외 핸들링 시 레벨에 따라서 우선순위가 다르기 때문에 낮은 Level의 예외는 지연되게 되므로 더욱 세심한 배려가 필요하다. 최상위 Level의 핸들링에서 처리가 늦게되면 그 외에 낮은 Level의 예외는 당연히 지연될 수 밖에 없다.

인터럽트의 Level에 대한 분류는 가장 높은 것이 Hardware Reset과 Machine Checks이고 그 밑으로 이것 저것 있는데, 역시 궁금한 사람은 Intel Architecture 메뉴얼을 참조하도록 하자.

Intel Architecture의 예외 수준(Exception Level)

그리고 잊지 말아야 할 사실을 다시 한번 강조한다.

*인터럽터가 발생하면 기본적으로 ISR 핸들러 호출 시 EFLAG 레지스터의 인터럽터 가능 플래그가 0으로 되어 Disable된 상태라는 것이다. *

이 말은 인터럽터 핸들러 안에서 무한루프를 돌면, 그 외에 다른 인터럽터도 발생하지 않는다는 말과 동일하다. 따라서 각별한 주의가 필요하다.

3.인터럽트(Interrupt)의 처리흐름

인터럽트가 발생하면 수행중인 태스크는 중지되고 인터럽트 핸들러가 호출된다고 했다. 이것을 그림으로 표현하면 아래와 같다.

인터럽트 발생 시 흐름

특정 태스크가 실행중이다가 인터럽트가 발생하면 인터럽트 핸들러에서 인터럽트에 대한 처리가 되고, 그것이 완료된 다음에야 태스크로 복귀한다.

핸들러에서 작업이 늦게 끝나면 끝날수록 태스크로 복귀하는 시간이 늦어지며 이 시간 동안 거의 인터럽트가 불가능해서 전체적인 지연을 초래하는 것이다. 물론 요즘 커널들은 인터럽트 핸들러 안에서도 인터럽트가 발생할 수 있도록 하여 더욱 가용성을 높이고 있다.

인터럽트의 중복을 허용함으로써 오는 이득은 특정 인터럽트가 완료되지 않은 상태에서 다른 인터럽트의 처리가 가능하므로 많은 인터럽트를 다중으로 처리할 수 있다는 점이다. 대신 인터럽트가 다중으로 발생할 수 있으니 커널코드 자체도 재진입(reenterance) 가능하도록 해야 되니 커널 코드가 굉장히 복잡해 지는 단점을 가지고 있다.

커널 코드 내에서 인터럽트 발생이 가능하도록 한 좋은 예제로 Linux Kernel 2.6 버전이 있다. 커널이 2.6 버전으로 올라가면서 스케줄러 부분 및 메모리 관리 부분이 비약적으로 향상되었다는데, 궁금한 사람은 참고하는 것도 괜찮을 듯 하다.

5.마치면서...

인터럽트에 대한 위 내용 정도의 지식은 거의 필수라고 볼 수 있다. 이번 내용에서 깊은 내용은 다루지 않고 개론 정도만 언급했으므로 알아두도록 하자.

4.첨부

142147_part3.ppt
13.3 kB

Part2. 커널 개발시 꼭 알아야할 몇가지

원문 : http://kkamagui.springnote.com/pages/339543

 

들어가기 전에...

 

0.시작하면서...

 커널 개발 시에 알아야할 것이 프레임워크를 사용하면 많이 줄어든다고 이야기 했었다. 그렇다면 프레임워크를 사용해서 개발하면 정말 콜백(Callback)만 구현하는 방식으로 커널 개발을 완료할 수 있을까?

 글쎄... 이건 한번 생각해볼 문제인 것 같다. 초보라면 콜백과 API를 사용하는 것 만으로 충분하다. 하지만 프레임워크의 API는 시스템 또는 CPU Architecture의 특수한 기능을 수행하는 명령어를 사용하기 쉽도록 Wrapping 해놓은 것이기 때문에 시스템에 대한 어느정도 지식이 있으면 API를 '더욱 잘' 사용할 수 있다. 프레임워크(Framework)의 기능을 수정하거나 새로운 기능을 추가하려면 역시 시스템에 대한 지식이 필요하다.

 

 

1.권장 지식

 그럼 '꼭' 알아야 하는 것은 과연 무엇일까??
 내가 생각하는 꼭 알아야하는 것은 아래와 같다.

 

1. 기본적인 어셈블리어 명령 몇가지
2. C 언어의 호출 규약(Calling Convention)
3. OS의 역할 및 간단한 동작 원리
4. 인터럽트(Interrupt) 종류 및 처리 방법

 

 적어도 이 정도는 빠삭(??) 하게 알고 있는 편이 커널 제작에 도움이 된다. 위 항목은 시스템 프로그래밍에 기초가 되는 부분이기도 하니, 저 정도의 지식은 갖추고 있는 사람이 꽤 될 것이다.

 적어놓고 나니, 아까 몰라도 된다고 했던 어셈블러는 갑자기 1번으로 등극해있고... CPU Architecture도 몰라도 된다면서 Interrupt 처리에 대한 내용이 나와있는건 또... ㅜ_ㅜ

 뭐.. 어디까지나 중급자를 위한 코스~!!! 라는 점을 강조하면서 마무리해야 겠다. 초급자는 프레임워크를 돌려 실행하기도 빠듯할 터이니 그냥 읽고만 넘어가자.
 그럼 고급자는?? 고급자는 이 글을 보지 않아도 이미 커널을 만들 수 있는 실력을 갖추고 있을 터이니 더더욱 패스...(왜 이걸 보고있을까.. ㅡ_ㅡa...)

 여튼 각설하고 중급자로 올라가려면 저정도의 지직은 갖추도록 하자.

 

 

이 글은 스프링노트에서 작성되었습니다.

이 글은 스프링노트에서 작성되었습니다.

OS 프레임워크 전체글 보기

Part1. 커널 개발이 힘든 이유

들어가기 전에...

0.시작하면서...

프로그래머라면 누구나 자기가 만든 OS에서 프로그램을 실행시키는 꿈을 꿔본 적 있을 것이다.
OS... 그것은 프로그래머의 로망(??)이라고도 말 할 수 있는데....

문제는 OS 구현이 아니라 커널 개발도 힘들다는 것...
커널 개발에 시도했던 많은 초보 프로그래머들이 격는 수많은 벽....

Assembler, CPU Architecture, Device Control, C Language Skill... 등등...

난관은 아주 많다. 부트로더까지는 어셈블리어를 대충 공부해서 어찌 어찌 해본 사람들이 꽤나 많을 것인데, 보호모드로 진입하여 32bit 커널을 구동시키고 유저레벨과 커널레벨을 구분하여 응용프로그램을 생성하고 이후 기타 등등 까지 해본 사람은 크게 많지 않다고 생각한다. (왜?? 여기서 부터는 CPU Architecture를 이해해야하고 C 언어의 약간 고급 스킬이 필요하기 때문이다. 물론 요즘은 좋은 책이 많이 나와있어서 앵무새 처럼 치기만 하면 되지만, 이 또한 커널 디버깅이 필요한 상황이 닥치면 쉽게 포기하고 만다... ㅡ_ㅡ;;;; )

1. OS 제작... 그 높은 벽

나도 대학교 1학년 말에 아는 것 없이 개발을 시도했다가 어셈블리어의 장벽과 CPU Architecture에 대한 장벽에 부딪혀서 한번 좌절을 했었다. 그뒤 소스가 오픈되어있다는 리눅스를 깔아서 미친듯이 코딩을 하고 커널 코드를 뒤져보고해서 벽을 넘어보려했지만 역시 무리....
결국은 기초가 문제라는 것을 깨닫고 체계적으로 어셈블리어 공부를 시작했고 IBM 호환 PC의 구성에 대해서 공부했다. 그렇게 시간이 흐르고나니 자연스레 부트코드, CPU Architecture에 대해서 이해가 되었다.

이것이 2002년도 말쯤이었던 걸로 기억된다.

그 뒤로 커널을 만들고 유저레벨/커널레벨로 분리하고, 어플리케이션을 위한 시스템 콜(System Call)과 라이브러리(Library)를 만들면서 OS라고 부르기에는 많이 부족하지만 간단한 OS를 만들 수 있었다.
<궁금하신 분들은 여기 참조.. http://kkamagui.egloos.com/3071201 >

2. OS 포팅(Porting)의 어려움

운이 좋아서 ARM 보드(ARM7TMDI 코어 사용)를 구하여 Kernel을 제작할 수 있었는데, 기존 소스를 포팅해보려했지만 워낙 INTEL Architecture에 의존해서 구현했기 때문에 실패하였다. Kernel 코드가 너무 CPU Architecture와 긴밀하게 연결되어 분리가 거의 불가능한 지경이었다. 결국 Kernel을 처음부터 다시 구현하기 시작했는데, OS까지도 못가보고 도중 포기(사실 중단이란 표현이 더 맞을 것 같다. 삽질을 다시 하려니 죽을것 같은 귀차니즘이 몰려와서... ㅡ_ㅡ;;; )

3. OS 제작과 프레임워크(Framework)의 시작

ARM 커널을 마지막으로 3년이라는 시간이 흘렀다. OS 제작은 점점 기억에서 멀어져가던 차에 어느날 갑자기 NDS(Nintendo Dual Screen, 게임기)에 OS를 올려보면 좋겠다는 생각이 들었다. 하지만 다시 처음부터할 생각을 하니 아득하고... 그러다 문득 든 생각이 있었다.

"왜?? OS를 만들려고 시작하면 알아야 하는게 이렇게 많은 걸까?"
"좀 더 쉽게 시작하거나 간단하게 테스트할 수 있는 그런 방법은 없나?"

그래서 생각한 것이 프레임워크(Framework) 였다. CPU Architecture에 관련된 부분이나 Hardware에 관련된 부분은 wrapping하여 CallBack과 API 형태로 만들고 CallBack을 어느정도 구현하고 API를 사용하면 간단히 Kernel을 제작할 수 있는 프레임워크... 당장 시작해야겠다는 생각이 들었다.

이것이 OS 프레임워크 프로젝트의 시작이었다.

00 KKAMA OS

들어가기 전에...

옛날 kkamagui.osx86.org를 운영할 때 썼던 글을 옮겨왔습니다. ^^;;; 사실 이제는 64비트 멀티코어 OS 구조와 원리를 만들 때 개발한 MINT64로 발전해서 KKAMA OS는 더이상 볼 수 없지만... MINT64를 KKAMA OS라고 생각해주시면 좋겠습니다. ^^;;;

1.소개

KKAMA OS는 KKAMAGUI가 만든 OS란 뜻입니다.

프로젝트를 시작한건 작년 10월이나 11월쯤 되겠군요. (글고보니 이놈 정확한 생일도 모르고 있네요. 쩝쩝.. -_-a..)

처음엔 부트로더로 시작했던 녀석이 어느새 시간이 지나 32Bit 허접 Kernel로 진화를했습니다. 제가 주로 쓰는 Nic Name이 까마귀(KKAMAGUI)인지라 이녀석의 이름 또한 제 별명을 따라 지었습니다.

왜 KKAMAGUI OS가 아니라 KKAMA OS인지 궁금해 하실 분이 있으신지 모르겠으나 (-_-a.. 없으려나...) 어느날 녀석의 이름을 KKAMAGUI라고 지었을때, 어느분이

'어?? 그거 새로나온 GUI냐??' 라고 물으시던데...

이때 머리를 스치는 생각 'CUI(console user interface)를 가진 녀석은 KKAMA OS로 짓고 GUI를 갖추면 그때 KKAMAGUI OS로 만들자!!'

그래서 일단은 KKAMA OS로 지었습니다. 지금 어느정도 GUI도 있으니까 KKAMAGUI OS가 맞겠네요. ^-^

KKAMA OS의 이상은 Simplist, Light, Easy Implementation 입니다. 이중에서 Simplist와 Easy Implementation을 가장 중요하게 생각하고 있는데, 간단하고 쉬운 알고리즘이 가장 좋다고 생각하기 때문입s니다. 사실.. 생각만 그리하고, 실제로 실천 하고 있는지는 저도 알 수 없죠. 쿨럭..;;;

2.기능

2.1 동작 환경

KKAMA OS는 아래의 환경에서 동작합니다.

  • Intel 386 이상 Protected Mode 동작 가능 CPU
  • 하나 이상의 Floppy Disk ( Kernel이 Floppy를 통해 부팅되기 때문이죠 )
  • PS/2 방식의 Keyboard 및 Mouse
  • Vesa방식이 지원되는 Video Card ( 제건 3년 전 모델인데 지원하더라구요.. 앵간하면 다 될듯 GUI시 필수 CUI시 옵션 )
  • FAT32 또는 16방식으로 포멧된 HDD ( 옵션 )

2.2 지원 기능

KKAMA OS가 지원하는 기능은 아래와 같습니다.

*1. FAT32 또는 16방식으로 포멧된 HDD Read/Write
( 파일 읽기쓰기, 디렉토리 읽기쓰기 가능 )
2. Serial 제어
( Serial을 통한 파일 송/수신 )
3. Floppy Disk 제어
( 섹터 읽기쓰기 지원, FAT12는 아직 미지원 )
4. Mouse 제어
( PS/2 방식만 가능, GUI때는 필수 )
5. 간단한 kernel shell
( Kernel 테스트를 위한 Shell, 그리 큰 기능은 없음 )
6. 응용프로그램 Loader
( djgpp를 통해 컴파일 링크된 Binary또는 djgpp-coff파일 실행가능,
단 djgpp-coff는 특별히 제작된 프로그램을 한번 통해 실행해야 됨 )
7. 약간의 GUI
( 최근에 구현된 허접한 GUI, 윈도우 겹침 처리 및 마우스 처리 외에 별다른
기능 없음 )
8. RAM Disk Drive
( 2004/03/23에 개발 1차 완료, 4Mbyte의 크기 )
9. IPC 중 파이프, 공유메모리 지원
( 파이프는 2004/04/11에 개발 1차 완료 ) *

이런게 있네... ㅡ_ㅡ;;;

83a8: e3a00006 mov r0, #6 ; 0x6
83ac: eaffffff b 83b0
83b0: e91ba800 ldmdb fp, {fp, sp, pc}

내가 C 소스 코드에서 무엇을 했는고 하니.. ㅡ_ㅡ;;;

return 6;

했더랬다. 저 어셈코드를 보면, 사실 mov r0, #6를 하고 ldmdb fp, {fp, sp, pc} 하면
될것을 return 을 했다고 폴짝 바로 아랫줄로 뛰어들고 있음을
"확인"
할 수 있다. 얼래.. ㅡ_ㅡ;;; 머지.. 저런식으로 코드를 생성하면 먼가 이득이 있나??
아님 내가 멀 잘못알고 있는건가??
설마 brench 명령으로 CPU Cache를 Flush하는 그런 말도안되는.. ㅡ_ㅡ;;;;;
내가 써놓고도 좀 말이 안되는거 같네. 만약 Flush를 위함이라하면 brench가 도배되
어야 정상이지......
O1을 줘서 약간의 Optimizing을 하면.. 아래와 같은 결과가 나온다.

8310: e3a00001 mov r0, #6 ; 0x6
8314: e91ba800 ldmdb fp, {fp, sp, pc}

컥.. brench가 온데간데 없다. @0@/~
이.. 이럴수가..





??
어.. 이상하다. ㅡ_ㅡ;;; 분명 Interrupt Vector는 Address 0x00000000 에서 시작한다. 그리고
이 영역은 현재 Flash가 차지하고 있다. 음... 그렇다면 Kernel에서 Boot Loader가 mapping
한 interrupt vector를 다시 Kernel에 맞게 remapping을 할 일이 분명 있을텐데......
어떻게 하는거지?? 설마 그냥 Flash에 Write를 해버리는건가?? ㅡ_ㅡ;;;
만약 그렇다면 kernel이 이상하게 되서 Vector를 복구하지 못하고 죽어버렸을때는, 문제가 심각
해 질것인데... 으음... 아니면 Remapping용으로 무언가 있는건가??
한번 찾아봐야겠다. @0@/~~
ldmia A, B 명령어는 A 어드레스에서 B에 있는 레지스터의 개수만큼 순차적으로 읽어 B에 저장하는 역할을 하는 명령어입니다. ^^;;;

lr 뒤에 붙은 "!"는 ldmia를 수행하고 난 뒤에 lr 레지스터의 값을 변경하여, 읽은 레지스터의 개수만큼 어드레스를 증가시키라는 뜻입니다.

B의 명령어가 위처럼 { r0 - r12, pc }라면 A 어드레스부터 4byte * 14개를 읽어서 순차적으로 r0->r12, pc에 저장한다음, lr 레지스터의 값은 "lr = lr + 14 * 4" 되는 것이지요.

그리고 lr 뒤에 붙은 "^" 표시는 ldmia의 대상 레지스터로 pc가 지정되어 있는 경우, SPSR 레지스터의 값이 CPSR 레지스터로 복사가 됩니다.

코드를 보면 대충 눈치채셨겠지만... 예전에 만들었던 OS의 태스크 스위칭 코드의 일부입니다. ^^;; 지금음 오래되서 기억도 안나는데... LDMIA로 검색해서 들어오시는 분들이 꽤 되서 내용을 보강합니다. ^^

그럼 좋은 하루 되세요 ;)

ps) http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0068b/BABEFCIB.html 로 가시면 ARM 명령어에 대한 자세한 설명을 보실 수 있습니다. ^^


아래는 원래 블로그에서 옮겨온 원본 글입니다. ^^;;;

===========================================================================================================
이 명령에서 제대로 실행이 안되고 뻣는 이유는... ㅡ_ㅡ;;;
도데체 무엇이란 말인가?? ㅡ_ㅡ;;;;;
분명 IRQ Mode이고 SPSR 레지스터도 잘 설정해 줬건만... 뻗어버리는 이유는.. ㅡ_ㅡ;;;;
으으.. ^를 빼면 그래도 스위칭하는데 별 문제가 없어보이는데....
것참 이상하네......
먼가 다른 이유가 있을것도 같은데, 쿨럭...;;;
어려워 어려워.. @0@/~~
오오~~ @0@/~~ 드뎌 좀 되기 시작했다.
이때까지 ldmia ...^ 해서 뻣던건, 실제로 뻗은게 아니라 OS Timer를 초기화 해주는걸 또
까먹어서 열심히 Interrupt 루틴이 불리고 있던 것이었다. @0@/~~
크아~~ 이런 낭패가.. ㅡ_ㅡ;;; 역시 머리가 나쁘면 손발이 고생하는거 같다.
머 그외에 ARM을 잘 몰라서 내멋대로 작성한 코드가 지멋대로 동작해서(당연한가?? ㅡ_ㅡ;;)
문제가 좀 됬었는데, 참고 문헌을 좀 뒤지고 해서 해결을 봤다.

지금은

내멋대로 작성한 코드가 내가 의도한대로 동작하고 있어서 다행이다. ㅋㅋㅋ
여튼 참... 하나라도 어디 쉽게 넘어가는게 없구만. 쩝쩝...
이제 C 코드로 만든 커널을 컴팔해서 뒤에 붙여넣은 다음 적절한 메모리 위치에 로드해서
사용하는 것만 고민하면 될것 같기도 하다.

음... 그나저나 역시 코드는 지저분하군... ㅡ_ㅡ;;;
잘 모르는 상태에서 마구 코딩한거니 어쩔 수 없는건가... 쿠.. 쿨럭..;;;;
Vector Table을 재구성한 뒤... 재실행한 결과....
Interrupt가 계속 튄다.. @0@/~
머지?? IRQ Interrupt Vector에 Led On/Off를 넣어놓으니 그것만 계속 켜졌다 꺼졌다 한다.
쿨럭..;;;
확실히 먼가 이상하다. 분명 Enable된 Vector는 OS Timer께 맞고 이놈의 Interrupt는 특정조건
을 만족해야 계속 튀게 되어있는데, 특정 조건이란게 순식간에 맞을 정도로 만만한게 아닌
것을....... ㅡ_ㅡ;;;;
혹시 내가 Interrupt Mask를 OS Timer가 아니라 다른걸 Enable한걸까??
당췌 잘 몰것네.. 쩝쩝...
OS Timer Interrupt가 죽어라고 발생하여, 해결방법을 찾던 중... 얼핏 ezboot 소스가 생각났
다. 음... 이놈도 필시 OS Timer를 이용하리라고 생각되어 소스를 뒤지던중...
내가 생각지도 못한 코드를 발견했는데... 그 코드는 이러하였다.

OSSR = OSMR0

이것이 무엇인고 하니, Status Register를 0으로 초기화 하는게 아니라 1로 초기화 하는 것이
었다.

SA-1110 Manual의 표에는 다음과 같이 나와있다.


bit 0, M0, 0 - OS Timer match Register 0 has not matched the OS Timer counter sind the last clear.
1 - OS Timer match Register 0 has matched the OS Timer counter.



이런 문장을 보면 누구라도 딱 일케 생각하지 않나??

"음.. 그래 그람 초기화는 0으로 해야지.. ㅡ_ㅡ;;;"

쿨럭..;;; 나만 그런가.. ㅡ_ㅡ;;;; 머 그래가지고 0으로 초기화 하니 줄창 Interrupt가 끝도없이
뜨더랬다.
1로 초기화 하는 코드를 보고 다시 SA-1110 Manual을 보니 다음과 같은 문장이 눈에 들어온다.

"and cleared by writing a one to the proper bit position. Writing zeros to this register has no effect."

컥.. ㅡㅁㅡ... 이말인 즉슨 걍 니가 멀하든간에 해당 비트를 무조건 1로 셋팅해라는 말이었던 것인가??
실제로 IRQ Interrupt가 발생했을 때, OSSR를 보면 해당 비트에 1이 설정되어있다. 그리고 다시
내가 1로 한번더 설정해 줘야 정상적인 Interval로 Interrupt가 발생된다.
쿠.. 쿨럭...;;;
나보고 어쩌라고.. ㅡㅁㅡ;;;;;
죽어도 모를뻔 했다. 쩝쩝.... 여튼 황당.... ㅡ_ㅡ;;;;
컥.. 어제 저녁에 삽질을 좀해서 OS Timer를 Enable 할 수가 있었다.
그런데 이상한것은 이놈이 interrupt가 발생하면 FIQ Handler가 불리는 것이었다.
분명 Interrupt Register의 Level Register는 모두 IRQ Interrupt로 설정된 상태였는데.... ㅡ_ㅡ;;
IRQ Handler를 부르게 할려고 혹시나 값을 바꾸면 될까해서 여러가지로 노력해 봤으나...
나온 결론은 두가지...

OS Timer는 IRQ Interrupt로 설정된게 확실하다.
지금 튄 Interrupt는 분명 IRQ Interrupt다. (비록 FIQ Handler가 불리긴 하지만.. ㅡ_ㅡ;;; )

오늘 혹시나 해서 Vector Table에 대한 문서를 보니... 컥.. 아래와 같았다.

0x00000000 Reset Handler
0x00000004 Undefined Instructions Handler
0x00000008 Software Interrupt Handler (SWI)
0x0000000C Prefetch Abort Handler
0x00000010 Data Abort Handler
0x00000018 IRQ Interrupt Handler
0x0000001C FIQ Interrupt Handler

음.. 머가 이상한지 처음엔 몰랐지만 Address를 보면 0x00000014 부분이 사용되지 않고 있었
던것이다. @0@/~~
이런.. 난 걍 문서에 나온 Vector Handler의 순서만보고 그냥 연번호로 할당했으니

0x00000014 IRQ Interrupt Handler
0x00000018 FIQ Interrupt Handler 가 될 수 밖에~~ @0@/~~

머여... 강조 같은걸 써서라도 좀 눈에 띄게 해놓던가.... ㅡ_ㅡ;;;;
삽질만 했네 그려.. 쿨럭..;;;;
쿨럭..;;; 어제 실컷 해보고 알았는데, SWI 명령으로 발생하는 interrupt는 Interrupt핀을
통해 발생하는 Interrupt와는 달랐다.
Vector Table에 위치하는 곳도 다르고, 무엇보다 중요한것은 SVC Mode라는 것이다.
음냥.. Reset을 했을때 Mode가 이미 SVC 이기땜시롱 SWI를 통해 Interrupt를 발생
시키면 그냥 brench 한것과 마찬가지 결과가 된다.

또 IRQ Interrupt와 SWI의 다른점은
SWI Interrupt는 lr에 들어가있는 Address가 SWI 바로 뒤의 명령을 가리키고
IRQ Interrupt는 lr에 들어가있는 Address가 다음에 수행할 명령의 다음 명령(??)

Thumb Mode가 아닌 경우 Next Instruction + 4
Thumb Mode인 경우 Next Instruction + 2
를 가지므로 lr에 4 or 2를 sub해 줘야 정상적으로 동작된다는 결론~~

으으.. ㅡ_ㅡ;;;
x86은 int 명령하고 IRQ Interrupt가 별차이가 없는데... 야들은 왜 일케 틀린지 모르겠
다.
여튼 SWI로 테스트를 해보겠다는 나의 생각은 무~ 로 돌아가고... ㅡ0ㅜ/~~
그냥 OS Timer를 이용해서 해야겠다.

쉽지 않구만... ㅡ_ㅡ;;;;;;;;
쿨럭..;;; 오늘 Memory Setting을 좀 해볼려고 문서를 딱 펴 들었는데...






다.

@0@/~~

머 연결방법하고 라인수에 따라서 Memory Register에 설정해주는 값이 틀리던데...
아아~~ 어찌 연결된줄 알고 한단 말이냐아아아아아~~ ㅡ0ㅠ...
그래서 걍 한참을 고민하다가...
기존의 부트로더 Memory Setting 코드를 그대로 Dump해서 테스트했다.
역시나 잘되는군.. ㅡ_ㅡ;;;
함 잡아볼려고 맘 먹었는데... 굉장히 복잡하네...
DSP의 MCBSP 설정만큼 황당한거 같다. 쿨럭..;;;
크윽.. ㅡ0ㅠ... 난 너무 허접한거 같은....
아아아아아아~~ 모르겠다아아아아~~~
Interrupt Vector Table에 Reset을 제외한 나머지에 모두 같은 Function으로 brench
하게 설정한다음 SWI 0x01 일케 했다.
음~ LED에 불이 들어오는걸 확인했다. 으음... Software Interrupt를 발생하게하는건
x86이랑 거의 차이가 없네..
명령만 틀린거 같고... 흐음...

아웅.. 잠와...
아침이 왜이리 빡신지.. 모르겠다.
난주 생각해야지.. ㅡ_ㅡzZZ
음... Interrupt를 이용한 Context Switching을 고민하고 있는데...
User Level에서 Switching하는거 보다 약간 더 까다로운 문제가 있군.

첫째, Banked Register에 대한 처리 ( SP, LR )
둘째, Reenterance에 대한 처리

이 두가지가 문제가 되는거 같다.
머 사실 Baked Register는 CPSR을 조작하면 되는 문제인데, Reenterance는 쬐금
생각을 더 해봐야겠다.
지금까지의 생각은 Task끼리 Interrupt없이 Sleep() 같은걸 하기위해 Switching을
할때는 직접적으로 Switching 함수를 불러서 수행할 생각이었는데, 이것이 만약에
직접 Call한 Switching함수에서 Interrupt가 불려서 다시 스위칭이 될 경우...
현재 루틴에서는 문제가 발생할 소지가 있다. @0@/~~

간단히 하려고 생각하고 있는데, 자꾸 문제가 나오면서 점점 더 복잡한 로직으로
가는 듯도 하고.. ㅡ_ㅡ;;;
머 빵빵한 x86 같으면 Task 마다 IRQ, SVC Stack을 각각 할당하고
마구 Stack 전환을 난무(??) 하면서 할텐데... 이것도 사실 오바인거 같고.. Orz

흠.. 걍 Task끼리 Switching 할때도 SWI를 통한 Interrupt로 하도록 해서 단순화를
하는것도 생각해 볼만한거 같다.
일단 그람 그래해 볼까나??
훗.. @0@/~~
으으.. 어제 만든 Context Switching 코드를 테스트 한답시고 요것조것 바꿔가면서 하고있었는데, 아무리 해도 시나리오 대로 진행이 되지 않았다.
쿨럭..;;;

첫번째 문제는 Link를 한 목적파일의 코드가 Start Address 0 으로 정렬되지 않았다는 것이고, 두번째 문제는 DRAM쪽 메모리를 초기화 하지 않아서 DRAM쪽에 메모리를 아무리써 도 읽을때 내가 쓴 값이 아니라는 것이었다.

Start Address야 머, 역어셈하면서 왼쪽에 Address 나오는거 보고도 지나쳤고, DRAM쪽은....

초기화 해야하는지 몰랐다. @0@/~

이때까지 코드에 자동적으로 라인을 초기화하는 코드가 숨어있었기 땜시롱... ㅡ0ㅠ...
으으.. 몬산다 몬살아...
자.. 그람 또 MEMORY쪽을 살펴볼까...
크윽..;;; Orz
링크
앗쌀 좋구나 @0@/~~

이것도 한번 써봐야 쓰것다~
오늘 한 2시간 삽질한 결과 허접하게나마 Task Switching을 시험해 볼 수 있었다.
음.. 새로구한 ARM Emulator로 비교적 순조롭게 디버깅을 하면서 진행했는데, 상당
히 괜찮은 프로그램을 구한 것 같다는 생각이 든다.
@0@/~~ 어셈코드 마구 보여주고 메모리도 덤프해주고..
음냥 넘 좋은거 아니라..ㅋㅋㅋ

근데.. 역시 사람이 어떤걸 자주 접하게 되면 거기에 물드는거 같다.
처음에 Context를 저장하는걸 Stack에 안하고 따로 내가 만든 영역에 하려고 생각했
었는데, 워낙 예제 코드들이 자기 Stack에다가 저장하는걸 많이 보여줘서...

결국 내가 짠 코드는...
내가만든 영역에.. Stack Order로 저장하는.. 쿠.. 쿨럭..;;;;
디버깅하다가 한참을 헤맸다. 분명 되야하는데, 이놈이 거꾸로 들어간다 하면서...
당연히 거꾸로 들어갈 수 밖에.. Stack Order 명령을 줬으니.. ㅡ_ㅡ;;;;;
쩝.. 여튼 나중에 알고 깜짝 놀랐다. @0@/~~

이제 쫌만 더하면 안되겠나.. ㅋㅋㅋ
으음... Context Switching을 한번 해볼려고 어셈코드를 잡고 씨름중인데.. ㅡ_ㅡ;;;
어셈을 잘 모르니 참 답답하다. 일단 c 코드를 만든 다음 역어셈해서 나오는 어셈코드
를 중심으로 살펴보고는 있으나, 역쉬... 머리속에 그림이 잘 그려지지 않는다.
우째야 쓰까나...
코드는 대충 짜 놨는데, 잘 돌아가는지 테스트 할려니 넘 야심하네...
기분도 우울한데... 쩝쩝... 낼 해야 쓰겄다. @0@/~~
ARM Assembly
흠... 나름대로 괜찮은거 같은데, 일단 난주 둘러보기로 하자.
자야징... ( ^0^)r~~
여전히 부트코드를 돌아다니며 훓어보고 있는데 아래와 같은 루틴이 보였다.

FlashingWait:
mov r0, #FLASHING_DELAY
FlashingWait1:
subs r0, r0, #1
bne FlashingWait1 <<== 요기.. 주목...

sub 함수 밑에 바로 brench 명령이 딱 붙어있는 것이었다. 그것도 ne를 붙이고..
음.. sub도 s를 따라 붙이긴 했는데 멀까 하면서 ARM 문서를 신나게 뒤진 결과...
s가 붙으면 sub를 한 결과에 따라 cmp 명령처럼 Condition Field에 Update를 하는
것 같다.
음냥.. 그람 조걸 풀어쓰면..

sub r0, r0, #1
cmp r0, #0
bne FlashingWait1

으음.. 것참 신기하구만.. @0@/~~
약 30분 정도를 삽질한 끝에 시리얼로 데이터를 송신할 수 있게 되었다. 시리얼 설정하
는 레지스터가 PC의 메인보드에 달려있는 시리얼 설정하는 레지스터랑 거의 비슷해서
별 무리없이 된거 같다.
혹시 똑같은건가?? ㅡ_ㅡ;;; 워낙 오래되서 PC꺼는 생각이 잘 안나는데.. 쩝쩝...
메뉴얼을 보고 요것 조것 넣어가면서 어설프게 9600으로 송신하는걸 만들었으니,
이제 쫌더 손봐서 송수신 하도록 하고 Baud Rate도 쫌더 높여야겠다.
쩝... 이제야 먼가 되가는거 같군.
컥.. 이런.. 다른 PC에서 Cygwin을 깔고 툴을 컴파일하니까..




가 난다. ㅡ_ㅡ;;; 몬산다 몬살아... 이상하네 분명히 같은 버전인데 왜 여기서는
안될까나??
으으.. 머리에 스팀이 꽉차서 죽겠다. ㅡㅠㅡ;;;;
음냥.. 프로젝트 전체에 넘치는 Global 들로인해 드뎌 어디서 머가 되고, 어떤 파일에
이 Global 들이 있는지 파악하기 어려운 단계에 이르렀다.
쿨럭..;; 여튼 정리를 하긴 해야 할 것 같은데...
Class를 만들어서 처리를 할려고 하니 쓰는 곳이 한두군데가 아니어서 상당히 빡시게
구르고 있다.
완전 노가다다.. ㅡ_ㅡ;;;
우쒸... 이러다가 관절염 걸리겠네 그려...

+ Recent posts