8086계열 CPU의 구조

 8086과 호환하는 CPU는 1MB(220)의 메모리 어드레스를 가질 수 있다. 1MB의 메모리를 지정하기 위해선 20b it의 레지스터가 필요한데 86계열의 CPU는 16bit이므로 segment와 offset으로 구분하여 메모리를 지정하게 된다. CPU에 전원이 들어가는 순간 혹은 리셋이 될 때 CPU는 0xFFFF0번지로 점프하게 되어 있다. 바로 이 번지를 리셋 벡터라고 부른다. 8088/86 CPU 는 0xFFFF:0x0000으로, 80286이후의 CPU는 0xF000:0xFFF0이므로 앞자리의 segment를 한자리 빗겨서 offset과 더하면 둘다 0xFFFF 0이라는 절대 번지가 나오게 된다. 그렇다면 우리가 사용할 보드의 시작은 0xFFFF0이므로 프로그램을 이곳에 위치시켜야 한다. 하지만 0xFFFF0 ~ 0xFFFFF까지의 공 간은 16byte밖에 안되므로 보통 이곳에는 다른 번지로 점프할 명령을 넣어 놓게 된다. 그리고 특별히 0x00000 ~ 0x003FF까지는 인터럽트 벡터 영역으로 쓰도록 예약이 되어 있다.

Compile & Link

 프로그램 소스를 실행파일로 전환되는 과정은 다음과 같은 2단계를 거친다. 1. 소스를 compile이란 과정을 거치게 되면 목적 코드인 obj파일이 생성된다. 이 코드는 소스를 기계코드에 가깝게 바꾼것이지만 직접 실행할 수 없는 것이다. 2. 이 obj파일을 가지고 link란 과정을 거쳐 실행파일인 exe파일이 만들어지게 된다. link는 소스를 compile한 obj와 스타트업 코드인 C0x.OBJ, 그리고 여러 library파일과 합치는 과정이다. C0x.OBJ의 x란 메모리 모델에 따라 틀려지게 된다. 가령 small model이면 C0S.OBJ, large model이면 C0L.OBJ를 말한다. 바로 이 C0x.OBJ라는 목적 코드가 밑에서 얘기할 스타트업 코드란 것이다.

Start Up Code란?

 우리의 프로그램은 main함수를 가지고 있을 것이다. 그렇다면 이 main은 누가 호출하는 것이냐? 이것이 바로 스타트업 코드이다. 우리가 제작한 보드에서 C언어를 사용하려 한다면 컴파일러에 같이 들어있는 스타트업 코드를 수정해야한다. 스타트업 코드의 기능은 간단히 얘기하면, CPU 레지스터 초기화와 정적 변수 초기화를 한후 main을 호출하는 것이다.

 도스에서 실행될 프로그램 제작을 위해 참고로 스타트업 코드의 7가지 단계를 적어 본다.
 

    1. 도스에 의해 전달되는 기본적인 데이터는 JC전역변수에 저장되고, 데이터 세그먼트가 세트된다.
    2. 환경 테이블에서 '87='형식의 엔트리를 탐색하여 시스템 전역변수 _8087을 세트한다.
    3. 인터럽트 벡터 0과 4에서 6까지의 벡터를 저장한다.
    4. 스택과 힙을 초기화한다. 메인 메모리가 스택과 힙을 수용할 만한 충분한 공간을 가지고 있는지에 대한 테스트가 행해지고, 남은 메모리 공간을 도스에게 돌려 준다.
    5. 수치보조 프로세서와 인터럽트, 화면에 직접적으로 접근하기 위한 기본적인 데이터 처리 등에 필요한 여라가지 초기화 루틴 을 호출한다.
    6. argv와 envp 어레이가 생성되고, main함수를 부른다.
    7. exit 함수가 불려지고 수정된 인터럽트 벡터는 원래 값으로 돌려 놓는다. exit 함수는 도스 함수 0x4C를 부름으로써 프로그 램을 종결한다.

위 7단계를 우리가 제작한 보드에 적용시킨다면 다음과 같은 단계가 된다.
우리의 보드가 floating point연산을 하지않고, divide by zero, NMI(none maskable interrupt)같은 예외 처리를 하지 않으며, 하나의 프로그램만 수행한다는 조건에서는 2, 3, 5, 7단계는 필요 없게된다.
그러므로

    1. 변수의 초기화
    2. stack초기화
    3. main호출
의 3단계로 축약할 수 있다.

C언어의 변수 구조

 우리가 C언어에서 잡는 변수의 종류는 크게 정적 변수(static variable)와 동적 변수(dynamic variable)가 있다.
정적 변수는 전역 변수(global variable), 정적 지역 변수(static local variable)로 나누어 진다. 그리고 프로그램이 종료될 때 까지 존재하므로, 프로그램 코드와 마찬가지로 메모리를 항상 차지 한다. 데이터 세그먼트 내에 공간이 확보된다.
동적 변수는 지역 변수(local variable)를 말한다. 그리고 함수가 실행될 때 그 함수 내부의 변수는 스택에 영역을 확보하고, 초기화 되며 함수의 종료와 동시에 스택에서 영역을 잃게 된다.

초기값을 가지는 지역 변수는 각 함수가 자동으로 초기화 하므로, 스타트업 코드에서는 전역 변수와 정적 지역 변수 즉 정적 변 수의 초기화만 하면 된다.
정적 지역 변수는 전역 변수와 마찬가지로 생각 하면 된다. 단 변수가 미치는 Scope만 틀릴 뿐이다. 그리고 변수는 초기화 된것 과 않된 것으로 나눌 수 있다. 정적 변수는 초기값을 주지 않으면 자동으로 0으로 만들지만, 지역 변수는 말그대로 초기화 하지 않는다.


int a;                  // 초기화되지 않은 전역 변수(초기화 하지 않은 전역 변수의 값은 0

int b = 1;              // 초기화된 전역 변수



void main()

{

        static int c;   // 초기화되지 않은 정적 지역 변수

        static int d = 2;       // 초기화된 정적 지역 변수

        int e;          // 초기화되지 않은 지역 변수(이 변수의 초기값은 무엇이 될지 모른다

        int f = 3;      // 초기화된 지역 변수

}

Assembler의 Segment
 

       _TEXT Segment : CODE Class이며, 프로그램 코드가 위치할 segment _DATA Segment : DATA Class이며, 초기화된 정적 변수가 위치할 segment _BSS Segment : BSS Class이며, 초기회되지 않은 정적 변수가 위치할 segment _BSSEND Segment : STACK Class이며, 지역 변수가 잡힐 stack영역의 segment 

      ;+--------------------------------------------------------------+

      ;|                                                              |

      ;|      C_START.ASM -> C Start up code for 8088 Seriese         |

      ;|                                                              |

      ;+--------------------------------------------------------------+

                      include rules.asi

      ;       Segment and Group declarations



      _TEXT   segment byte public 'CODE'

      _TEXT   ends



      _DATA   segment word public 'DATA'

      _DATA   ends



      _BSS    segment word public 'BSS'

      _BSS    ends



      _BSSEND segment byte public 'STACK'

      _BSSEND ends



      DGROUP  GROUP _DATA, _BSS, _BSSEND



                      assume  CS:_TEXT,DS:DGROUP



      ;       External References

      ExtProc@        main,   __CDECL__



      ;+---------------------------------------------------------+

      ;|                                                         |

      ;|                      Start Up Code                      |

      ;|                                                         |

      ;+---------------------------------------------------------+



      _TEXT           segment



      startx          proc    near

                      cli

      ;+---------------------------------------------------------+

      ;|                                                         |

      ;|              Init Segment Register                      |

      ;|                                                         |

      ;+---------------------------------------------------------+

                      mov     ax,DGROUP

                      mov     ds,ax                   ;Static variables source segment in rom



      ; Set up stack segment

                      mov     ax,00h

                      mov     ss,ax

                      mov     sp,8000h

      ;stack은 감소 하기 때문에 stack은 램의 끝으로 맞춘다. 32kb의 램은 0~7FFF까지 이므로 stack

      ;segment(ss)는 0으로 stack pointer는 8000h로 해 놓으면 램의 끝에서부터 stack을 사용하게

      ;되는 것이다. 



                      mov     ax,40h                  ;start data area 

                      mov     es,ax                   ;Static variables target segment in ram



      ; move initialized data area ( ROM --> RAM )

                      mov     si,offset DGROUP:data@

                      mov     di,si

                      mov     cx,offset DGROUP:bdata@

                      sub     cx,di

                      cld

                      rep     movsb



      ; Reset uninitialized data crea ( RAM )

                      mov     ax,0

                      mov     di,offset DGROUP:bdata@

                      mov     cx,offset DGROUP:edata@

                      sub     cx,di

                      rep     stosb



                      mov     ax,40h                  ;start data area

                      mov     ds,ax



      ; Set up data segment & Call main() function

                      sti

                      call    main@

                      jmp     $



      PubSym@         DGROUP@, <dw 40h>, __PASCAL__



      startx          endp



      _TEXT           ends



      ;+--------------------------------------------------------------+

      ;|                                                              |

      ;|                      Start Up Data Area                      |

      ;|                                                              |

      ;+--------------------------------------------------------------+

      _DATA           segment

      data@           label   byte

      _DATA           ends



      _BSS            segment

      bdata@          label   byte

      _BSS            ends



      _BSSEND         segment

      edata@          label   byte

      _BSSEND         ends



                      end



      int a;

      int b = 1;



      void main()

      {

              static int c;

              static int d = 2;

              int e;

              int f = 3;

      }



      위의 스타트업 코드와 C 프로그램을 실행화일로 만들었을 때의 MAP파일이다.



       Start  Stop   Length Name               Class



       00000H 00044H 00045H _TEXT              CODE

       00046H 00049H 00004H _DATA              DATA

      이곳에 b,d변수가 위치한다. b,d는 초기화된 정적 변수이기 때문이다.

       0004AH 0004DH 00004H _BSS               BSS

      이곳에 a,c변수가 위치한다. a,c는 초기화되지 않은 정적 변수이기 때문이다.

       0004EH 0004EH 00000H _BSSEND            STACK





      Detailed map of segments



       0000:0000 003A C=CODE   S=_TEXT          G=(none)  M=C0.ASM     ACBP=28

       0000:003A 000B C=CODE   S=_TEXT          G=(none)  M=TEST.C     ACBP=28

       0004:0006 0000 C=DATA   S=_DATA          G=DGROUP  M=C0.ASM     ACBP=48

       0004:0006 0004 C=DATA   S=_DATA          G=DGROUP  M=TEST.C     ACBP=48

       0004:000A 0000 C=BSS    S=_BSS           G=DGROUP  M=C0.ASM     ACBP=48

       0004:000A 0004 C=BSS    S=_BSS           G=DGROUP  M=TEST.C     ACBP=48

       0004:000E 0000 C=STACK  S=_BSSEND        G=DGROUP  M=C0.ASM     ACBP=28



        Address         Publics by Name



       0000:0038 idle  DGROUP@

       0004:000C idle  _a

       0004:0006 idle  _b

       0000:003A       _main



        Address         Publics by Value



       0000:0038 idle  DGROUP@

       0000:003A       _main

       0004:0006 idle  _b

       0004:000C idle  _a



      Program entry point at 0000:0000
위 스타트업 코드와 다음 프로그램을 실행화일로 만들었을 때의 MAP파일이다.

 a,b만이 전역 변수이기 때문에 프로그램 전체에서 참조할 수 있는 public에 속한다.
c,d는 정적 변수이지만 e,f와 같이 지역 변수이므로 c,d를 선언한 main에서만 참조한다.
DGROUP@은 _DATA와 같이 재배치 가능한 상수들을 포함하지 않기위해 쓰여진다.
예를 들면 C의 interrupt함수는 레지스터들을 stack에 push한 뒤 DS를 DGROUP@로 초기화 한다.
다음은 interrupt함수 aaa()를 assembly어로 번역해 본 것이다.

    
       ;    
    
       ;    void interrupt aaa()
    
       ;    
    
            assume  cs:_TEXT
    
    _aaa    proc    far
    
            push    ax
    
            push    bx
    
            push    cx
    
            push    dx
    
            push    es
    
            push    ds
    
            push    si
    
            push    di
    
            push    bp
    
            mov     bp,cs:DGROUP@
    
            mov     ds,bp
    
            mov     bp,sp
    
       ;    
    
       ;    {
    
       ;    }
    
       ;    
    
            pop     bp
    
            pop     di
    
            pop     si
    
            pop     ds
    
            pop     es
    
            pop     dx
    
            pop     cx
    
            pop     bx
    
            pop     ax
    
            iret    
    
    _aaa    endp
mov ax, _data / mov ds, ax를 쓰는 대신 mov bp, cs:DGROUP@ / mov ds, bp와 같은 코드가 쓰인 것을 알 수 있다. 그러므로 DGR OUP@를 스타트업 코드에서 초기화 해주어야 한다. PubSym@ DGROUP@, <dw 40h>, __PASCAL__ 위와 같이 해줌으로 초기값을 40h로 DS와 같게 해준다. 이 변수는 코드 세그먼트 안에 있기 때문에 롬에 실릴 경우는 반드시 초 기값을 정해 주어야 하며, 램에 코드가 실릴 경우는 나중에 초기화 할 수도 있다.