ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • BIOS
    프로젝트/운영체제 만들기 2023. 8. 7. 02:11

    글의 참고

    - https://wiki.osdev.org/MBR_(x86) 

    - https://en.wikipedia.org/wiki/BIOS_interrupt_call

    - https://en.wikipedia.org/wiki/INT_13H

    - https://wiki.osdev.org/ATA_in_x86_RealMode_(BIOS) 


    글의 전제

    - 내가 글을 쓰다가 궁금한 점은 파란색 볼드체로 표현했다. 나도 모르기 때문에 나중에 알아봐야 할 내용이라는 뜻이다.

    - 밑줄로 작성된 글은 좀 더 긴 설명이 필요해서 친 것이다. 그러므로, 밑 줄 처친 글이 이해가 안간다면 링크를 따라서 관련 내용을 공부하자.

    - `글의 참조`에서 빨간색 볼드체로 체크된 링크는 이 글을 작성하면 가장 많이 참조한 링크다.

    - `운영체제 만들기` 파트에서 퍼온 모든 참조 글들과 그림은 반드시 `이 글과 그림을 소스 코드로 어떻게 구현을 해야할까` 라는 생각으로 정말 심도있게 잠시 멈춰서 생각해봐야 실력이 발전한다.


    글의 내용

    - BIOS 기능

    : BIOS의 기능을 사용할 때, 흔히 `BIOS 서비스`를 제공받는다고 한다. BIOS 서비스는 2가지로 이루어져 있다.

    1" BIOS 인터럽트
    2" BIOS 기능

    : BIOS `인터럽트`와 BIOS `기능`의 차이는 인터럽트가 좀 더 상위 개념이고, 기능은 인터럽트의 하위 개념이다. 예를 들어, BIOS 인터럽트 0x10은 `비디오 디스플레이 기능`들이 모여있다. 그리고 BIOS 인터럽트 0x16 에는 키보드관련 기능들이 모여있다.

     

    : BIOS 기능을 사용하기 위해서는 `INT` 명령어와 `AX` 레지스터를 조합한다. 예를 들면, 아래와 같다

    1" INT 0x10, AH = 1 : 커서 설정 
    2" INT 0x10, AH = 3 : 커서 위치 설정
    3" INT 0x10, AX = 0x4F00 : 비디오 메모리 사이즈 반환

    : 코드로는 아래와 같이 AX 레지스터를 먼저 설정하고, INT 명령어로 인터럽트를 호출하면 된다. 아래의 코드는 비디 디스플레이의 커서 위치를 바꾸려고 BIOS 기능을 사용하는 코드다.

     

    {
       ...
       ...
       mov ax, 0x03
       int 0x10
       ...
       ...
    
    }

     

     

    - BIOS 메모리

    : 나만의 부트 로더를 작성할 때 주의할 점은 그 시점의 메모리 구성이다. 내가 작성한 부트 로더는 BIOS에 의해서 실행된 프로그램이다. 즉, 내 프로그램은 최초의 프로그램이 아니다. BIOS에 의해서 실행되었고, BIOS의 기능을 사용해서 주변 하드웨어 장치들과 통신할 것이기 때문에 BIOS가 사용하고 있는 메모리 구성에 대해 알고 있어야 한다. 당연히 BIOS가 사용하고 있는 부분은 절대 침범해서는 안된다. 

     

    : 아래 그림은 리얼 모드에서 BIOS가 우리가 직접 작성한 부트 로더를 메모리에 로드시킬 때의 메모리 구성이다. BIOS가 실행되는 시점은 CPU가 리얼 모드일 때 이고, 리얼 모드는 1MB 이하에서 동작하는 모드이다. 그래서 아래의 메모리 구성은 1MB 이하에서의 구성이다.

     

    : 제일 먼저 보이는 부분은 커스텀 부트 로더가 로딩되는 주소이다. 0x00007C00 ~ 0x00007DFF에 커스텀 부트로더가 로드된다. 이 부분은 커널 및 세컨더리 부트 로더가 실행된다는 필요가 없어지는 부분이므로, 앞에 프로그램이 실행되기 전까지는 저 주소에 반드시 있어야 한다. 앞에 언급된 프로그램들이 실행되면 저 주소는 오버라이트되도 된다.

     

    : 그런데 사용가능한 메모리가 생각보다 적다. 0x500 ~ 0x7FFFF 까지가 사용 가능한 메모리이다. 한 512KB 정도를 커널에 할당할 수 있다. 리얼 모드에서 스택을 설정할 때, 0x7BFF 에 할당하면 좋을 듯 싶다. 물론, 최대한 빠르게 32비트 모드로 넘어간다는 전제로 말이다. 왜냐면, 저 위치에 스택을 설정하면, 잘못하면 BDA 영역을 침범할 수도 있기 때문이다.  

    출처 - https://wiki.osdev.org/Memory_Map_(x86)

     

     

     

    - BIOS INT 10h

    : BIOS 인터럽트 17번은 `Video Service` 이다. BIOS INT 10h 관련해서 텍스트 모드에 대한 내용이 생각보다 많지가 않다.

    - 예제1

    - 예제2

    - 예제3

     

    - BIOS INT 13h

    : BIOS INT 13h 기능을 이용하면 대부분 입력 파라미터로 DRIVE INDEX[NUMBER]가 필요하다. 

    : BIOS가 시스템을 초기화하는 과정에서 부팅 디스크 드라이브 넘버를 DL 레지스터에 넣는다. 그리고 MBR 부트 섹터의 부트 로더를 0x7C00:0000에 로딩한 뒤, 제어권을 완전히 부트 로더에게 넘긴다.

     

     

    : F2h - Read Sectors

    " BIOS의 20(십진수)번째 인터럽트를 의미한다. 그리고 이건 x86기반이다. INT 13h에 사용할 수 있는 기능들은 아래와 같다. 우리는 00h, 02h, 08h를 사용할 것이다.

    출처 - https://en.wikipedia.org/wiki/BIOS_interrupt_call

     

    " BIOS를 통해 디스크를 읽기(BIOS INT 13h의 Functon 02h) 기능을 사용하기 위해 지켜야 하는 포맷은 아래와 같다.

    출처 - https://en.wikipedia.org/wiki/INT_13H#List_of_INT_13h_services

     

    " AH는 BIOS INT 13h 에서 사용할 기능 번호(리셋, 섹터 읽기, 섹터 쓰기 등) 를 명시한다. 그리고 AL은 읽을 섹터 개수를 지정한다. 이 AL은 당연히 0이 되면 안된다. AL은 1바이트 이니 이 값의 범위는 127 까지 가능하다. 즉, 한 번에 읽을 수 있는 섹터 개수가 레지스터 크기로 제한되어 있다는 말이다. 이게 BIOS INT13h F2의 한계라고 지적되는 부분이기도 하다.  추후에 나올 호환성을 위해서 저렇게 놓은 것일 것이다. 그런데, 문제는 각 버전 별 BIOS가 지원하는 디스크 드라이버들의 한 트랙 당 지원하는 섹터의 개수가 모두 달라서 문제다. 

     

    " CHS에서 섹터를 올바르게 읽는 것은 하나의 트랙(헤드)당 몇 개의 섹터로 구성이 되어있는지 부터 알아야 한다. 왜냐면, CHS는 섹터의 개수에 따라 언제 헤드를 카운트하고, 언제 실린더를 카운트할지가 달라지기 때문이다. 트랙 당 섹터의 개수 32라면 32개의 섹터를 읽으면 헤드를 1 올려야 하고, 트랙 당 섹터의 개수 64라면 64개의 섹터를 읽고 헤드를 하나 올려야 한다. 먼저 자신의 시스템의 디스크 드라이브의 정보를 읽어와야 한다. 그게 조금 있다 알아볼 `BIOS INT13h F8h`다.

     

    " 읽을 섹터의 개수를 정했다면, 이제 실린더, 헤드, 섹터의 번호를 지정해야 한다. 이 3개의 번호를 통해서 어디서부터 섹터를 읽어야 할지를 정한다. 아마 대게는 첫 번째 섹터인 MBR 바로 뒤 부터 읽고 싶을 것이다. 혹은 파티션 테이블이 있다면, 해당 파티션부터 읽고 싶을 것이다. LBA가 지원되는 디스크 드라이브면 모르겠지만, 대게 옛날 디스크 드라이브(e.g 플로피)들은 CHS 만을 지원하는 경우가 많다.

    ...
    mov al, 36                      ; how many count to read sectors
    mov ah, BIOS_READ_SECS          ; BIOS INT 13h F2h:Read Sectors from drive
    mov ch, byte [start_cylin]      ; start to cylinder number started from number 0                                         
    mov cl, byte [start_sec]        ; start to sector number started from number 1, not 0 
    mov dh, byte [start_head]       ; start to head numbber started from number 0
    mov dl, 0                       ; set disk drive to 0
    
    int 0x13
    ...

    " 위의 6개는 반드시 필수로 작성해야 하는 부분이다. 제대로 작성하지 않을 경우, BIOS INT가 실패하게 되고 리턴값으로 해당 원인을 대략적으로 파악이 가능하다. 자주하는 실수는 다음과 같다.

     

    1" 섹터 번호를 0부터 하게 둔다.
    2" 드라이버 번호(DL)을 명시하지 않는다.
    3" 읽을 섹터 개수를 이상하게 지정한다.

    " 위와 같은 실수를 하면 대개는 리턴값으로 0x01(Invalid Command) 에러 코드를 반환받게 된다. 

     

    " 만약 QEMU에서 인식된 드라이브가 하드 디스크인데, 드라이브 넘버로 플로피 디스크 넘버(0x00)으로 BIOS 13h F20h를 호출하면 `Controller failure(20h)` 에러를 만날 수 있다.

     

    " 실린더, 섹터, 헤드를 설정하는 방법을 알아보자. AL을 통해 읽으려는 섹터 개수를 설정하고, CX와 DH를 통해 시작 섹터를 알려줘야 한다. CHS에 대한 전반적인 내용은 이 글을 참고하자.

     

    " CX 레지스터의 포맷은 아래와 같다.

    CX =       ---CH--- ---CL---
    cylinder : 76543210 98
    sector   :            543210

    " CH는 시작 실린더를 의미하고, CL은 시작 섹터를 의미한다. 역시나, DH도 시작 헤드를 의미를 한다. 결국 CHS를 통해 MBR 바로 뒤에 섹터부터 읽어오고 싶다면, 아래와 같이 설정하면 된다.

     

    MBR 바로 뒤 섹터부터 읽기  - CH:0 , CL:2 , DH:0

     

    " 디스크에서 데이터를 읽어왔으면, 이 값을 어딘가에 저장해야 한다. 그 데이터들은 ES:BX에 저장한다. 그래서 디스크를 데이터를 읽어오기 전에, ES:BX를 먼저 세팅한다. 즉, 램의 어디 위치에 해당 데이터를 저장할 것인지를 ES:BX 에 세팅해야 한다. 예를 들어, ES가 0x4F00이고 BX가 0x0F00이면 BIOS INT13 F2를 통해 디스크에서 읽어온 데이터는 0x4FF00에 저장된다.

     

    " 그런데 주의해야 할 사항이 있다. 리얼 모드에서 세그먼트의 최대 크기는 0x10000를 넘어서는 안된다. 이 말은 세그먼트 레지스터에 설정되는 베이스 주소는 상관없지만, 오프셋(크기)을 설정할 때 주의가 필요하다는 뜻이다. 아래의 내용을 보자.

    ES = segment         = 4F00h
    BX = offset          =  0F00h
    sum = memory address = 4FF00h
    would be a good choice because 0F00h + 2000h = 2F00h <= 10000h
    ES = segment         = 4000h
    BX = offset          =  FF00h
    sum = memory address = 4FF00h
    would not be a good choice because FF00h + 2000h = 11F00h > 10000h

     

    " 세그먼트의 베이스 주소를 0x4FF00라 가정하자. 16 섹터를 읽으려면, 여유분이 0x2000 만큼 필요하다. 그러면 오프셋은 0xD000으로 시작하면 안된다. 저 값보다 작은 값으로 설정해야 문제가 발생하지 않는다. 세그먼트 레지스터의 베이스 주소는 상관없다. 여기서는 `세그먼트의 오프셋(세그먼트 크기) + 읽으려는 섹터 바이트`가 0x10000이 넘어서는 안된다. 

     

    " 그렇다면, 리얼 모드에서 BIOS INT13h Function 02h 를 통해서 한 번에, 읽을 수 있는 섹터의 최대 개수는 127개라는 말일까? 맞다. 128개 이상을 읽으면 에러가 발생한다. 128개를 읽으면 128 * 512 = 0x10000 이다.

     

    " BIOS 13h F2h는 맨 앞 섹터를 기준으로 8GB밖에 읽지 못한다. 만약, 더 많은 디스크를 읽고 싶다면 BIOS INT 13h F41h를 사용하자. 

     

     

    F8h - Get Drive Parameters

    출처 -&nbsp;https://en.wikipedia.org/wiki/INT_13H

    : 드라이브를 읽기 전에 드라이브의 실린더, 헤드, 섹터가 어떻게 구성되어 있는지 알아야 한다. 왜냐면, BIOS INT13h F2h는 CHS 주소 지정 방식으로 읽기 때문에 실린더와 헤드가 몇 개인지, 트랙(실린더)당 섹터가 몇 개로 구성되어 있는지 알아내야 한다.

     

    : 위의 내용은 GDB를 통해 INT 13h F8h를 호출하여 얻어 낸 내용이다. 위의 내용을 정리해보면, 다음과 같다.

    1" DL(0x01) - 디스크 드라이브는 한 개다(플로피 디스크. 조금 있다가 알게 된다).
    2" DH(0x01) - 헤드의 개수로 헤드가 0부터 시작하므로, 1이 나온 거면 0과 1이 존재하는것이다. 즉, 헤드의 개수는 2개다.
    3" CH(0x4F) - 실린더의 개수로 0부터 시작하므로, 79가 나온 거면 80개가 존재하는 하는 것이다.
    4" CL(0x24) - 트랙당 섹터의 개수는 36개다.
    5" BL(0x05) - 크게 상관은 없는 값인데, 플로피 디스크일 때만 나오는 값이다. 이 값은 플로피 디스크 크기에 달라진다. 

     

    : BL(0x05)은 플로피 디스크의 타입을 말한다. 아래 그림에서 보면 알 수 있다시피 플로피 디스크 크기에 따라 번호가 매겨지는데, 0x05는 모호한 타입이라고 한다. 실제 YohdaOS에서 올린 플로피 디스크 이미지는 2.1M 정도이다. BL로 0x05가 나온 것은 맞다는 생각이든다.

    출처 -&nbsp;https://web.archive.org/web/20160619063203/http://www.ctyme.com/intr/rb-0621.htm

     

    :  `QEMU 모니터` 라고 해서 QEMU가 동작시키는 게스트 OS의 상태를 관리하는 유틸리티가 있다. 해당 프로그램을 통해서 YohdaOS의 드라이브 상태를 보면 다음과 같다.

     

    : 2개의 드라이브가 더 있지만, 실제로 붙어있는 드라이브는 플로피 밖에 없다. IDE 기반의 디스크로 부팅 시키고 싶었지만, 문제가 있었다. 이 글을 참고하자. 

     

    : AH=41h - Check Extenstions Present

    " BIOS 13H 확장 기능을 제공하는지 알려주는 기능이다.

     

    : AH=48h - Extended Read Drive Parameters

    " `BIOS 13h AH=41h`를 통해서 확장 기능을 제공하는 걸 알았다면, 이 기능과 `BIOS 13h AH=08h`이 비슷해 보일 수 있지만 둘은 완전히 다른 기능이다. `AH=08h`는 Logical CHS 주소를 제공해주지만, `AH=48h`는 실제 Physical CHS 주소를 제공해준다. 둘의 차이는 아래의 위키피디아 영문으로 대체한다. 1980년 후반부터 디스크 드라이브에 디스크 컨트롤러가 내장됬는데, 이 때 부터 `논리적 CHS` 주소 지정 방식이 나오기 시작한다. 

    As the geometry became more complicated (for example, with the introduction of zone bit recording and drive sizes grew over time, the CHS addressing method became restrictive. Since the late 1980s, hard drives began shipping with an embedded disk controller that had good knowledge of the physical geometry; they would however report a false geometry to the computer, e.g., a larger number of heads than actually present, to gain more addressable space. These logical CHS values would be translated by the controller, thus CHS addressing no longer corresponded to any physical attributes of the drive.

     

    : 참고로, `INT 13h AH=48h` 기능을 사용할 때 자주 에러가 발생하는데 이 글을 참고하자.

     

     

    - 에러 사항

    : BIOS 13h F02h 시, 리턴값으로 AH=0x01(Invalid command) 인 경우

    " QEMU에서 부팅 디스크로 플로피 디스크를 세팅했다. 그리고, BIOS 13H F02h를 호출했는데 발생한 에러다. 이 문제는 디스크 드라이브 넘버가 문제였다. 플로피 디스크 드라이브 넘버(0x00)로 세팅하니 문제가 발생하지 않았다.

     

    : BIOS 13h F02h 시, 리턴값으로 AH=0x20(Controller failure) 인 경우

    " QEMU에서 부팅 디스크로 하드 디스크를 세팅했다. 이 문제도 위와 같다. 근데 에러 넘버가 달라서 당황했다. 하드 디스크 드라이버에서 플로피 디스크 드라이브 넘버를 사용하니 이 에러가 발생했다. 하드 디스크 드라이브 넘버(0x80)를 사용했더니, 문제가 발생하지 않았다.

     

    : BIOS 13h F02h 시, 리턴값으로 AH=0x0C(Invalid command) 인 경우

    " QEMU에서 부팅 디스크로 하드 디스크를 세팅했다. 그리고 커널 사이즈가 2560B 정도였다. MBR 부트 섹터 512B + 스테이지 2 부트로더 2048B 였다. 그리고 32비트 커널을 0x10000에 로딩하기 위해 (0, 0, 6) 부터 하드 디스크를 읽었다. 그런데, 위의 에러가 발생했다. 이 에러의 발생원인은 하드 디스크에 더 이상 읽을 데이터가 없기 때문에 발생한 에러다. 실제로 커널 사이즈가 2560B 라서 더 이상 읽을 섹터가 없다. 뒤에 패딩으로 데이터를 추가해서 코드를 실행했는데 잘 동작되는 것을 확인했다. 커널 개발을 느낀 부분이지만, 디스크 관련 확실한 건 2가지가 있다.

    1" 디스크 이미지 크기는 미리미리 크게 설정한다.
    2" 적어도 디스크 이미지 사이즈는  커널 이미지 사이즈와 같아야 한다.

    '프로젝트 > 운영체제 만들기' 카테고리의 다른 글

    함수 호출 규약  (0) 2023.08.07
    코드 점프  (0) 2023.08.07
    [운영체제 만들기] ATA / IDE  (0) 2023.08.07
    [운영체제 만들기] Lazy Buddy Allocator  (0) 2023.08.07
    [운영체제 만들기] Buddy Allcator  (0) 2023.08.07
Designed by Tistory.