티스토리 뷰

1. 보호모드가 무엇인가?


메모리보호, 가상메모리, 멀티태스킹, 640K 이상 메모리 등을 지원하지 않는 8086의 단점을 보완하기위해 80386 이상은 8086 계열과 호환을 유지하면서 새로운 기능들을 제공한다. 386은 8086과 286의 모든 기능과 함께 많은 추가 기능을 가지고 있다. 이전 프로세서와 같이 리얼모드(real mode)로 작동할 수 있고, 286과 같이 보호모드로 작동할 수도 있다. 그러나 386의 보호모드는 286과 내부적으로 매우 다르다. 386의 보호모드는 프로그래머에게 더 나은 메모리 보호와 더 큰 메모리 지원 제공한다. 보호모드의 목적은 프로그램을 보호하는 것이 아니다. 보호모드의 목적은 당신의 프로그램으로부터 (운영체제를 포함한) 다른 모든 것을 지키기위함이다.


1.1 보호모드 대 리얼모드


피상적으로 보호모드와 리얼모드는 많이 다른 것같지 않다. 둘 모두 하드웨어를 다루기위해 메모리 세그먼트(segmentation), 인터럽트(interrupt), 장치 드라이버(device driver)를 사용한다. 그러나 이 두 모드를 나누는 차이점이 있다. 리얼모드에서 메모리는 서로 최소한 16 바이트 떨어져있는 64k개의 세그먼트로 구성된다. 세그먼트는 세그먼트 레지스터와 프로세서 내부 원리를 이용하여 처리된다. 이 세그먼트 레지스터(CS,DS,SS...)의 내용은 CPU가 주소 버스(address bus)로 보낼 물리주소(physical address)의 일부가 된다. 물리주소는 세그먼트 레지스터에 16을 곱하고 16 비트 옵셋(offset)을 더한 값이다. 이 16 비트 옵셋이 세그먼트 크기를 64k (216)로 제한한다.


그림 1 : 리얼모드의 주소 방식











보호모드에서 세그먼트는 기술표(descriptor table)라는 표에 정의된다. 세그먼트 레지스터(선택자)는 이 표의 항목을 가리킨다. 메모리 세그먼트를 정의하는 표는, 전역기술표(GDT, Global Descriptor Table)와 지역기술표(LDT, Local Descriptor Table), 두 종류가 있다. GDT는 모든 프로그램에서 접근할 수 있는 기본적인 기술자(descriptor)들을 저장한다. 리얼보드에서 한 세그먼트는 서로 16 바이트 간격으로 크기가 64k였다. 보호모드에서 세그먼트는 크기가 최대 4G 바이트이고 (간격을 지킬 필요없이) 아무 곳에서나 시작할 수 있다. LDT는 각 프로그램과 관련된 세그먼트 정보를 저장한다. 예를 들어 운영체제는 시스템이 사용할 기술자로 GDT를 만들고, 각 프로그램마다 관련된 기술자로 각각 LDT를 만든다. 기술자는 길이가 8 바이트이다. 기술자의 내용은 그림 3과 같다. 세그먼트 레지스터를 읽어들일 때마다 해당된 기술자에서 시작주소(base address)를 가져온다. 기술자의 내용은 프로그래머가 못보는 그림자(shadow) 레지스터라는 곳에 저장되어, 같은 세그먼트를 또 읽을때 표를 매번 참조하지않고 이 정보를 바로 사용한다. 물리주소는 그림자 레지스터에 저장된 시작주소에 16 혹은 32 비트 옵셋을 더해서 만든다. 그림 1과 2는 이 차이를 보여준다.


(역주;


  • 세그먼트 segment
  • 옵셋 offset
  • 물리주소 physical address
  • 논리주소 logical address
  • 기술자 descriptor
  • 선택자 selector
  • 크기 limit
  • 시작주소 base address
)

그림 2 : 보호모드의 주소 방식



그림 3 : 세그먼트 기술자 내용



인터럽트 기술표(Interrupt Descriptor Table, IDT)라는 것도 있다. IDT는 인터럽트 기술자를 저장한다. 프로세서는 이를 사용하여 인터럽트 핸들러(interrupt handler)가 어디있는지 찾는다. IDT는 리얼모드와 보호모드 모두 한 인터럽트당 한 항목씩을 저장하나, 항목의 내용은 서로 완전히 다르다. 우리는 보호모드로 변환하는 코드에 IDT를 사용하지 않으므로 자세한 내용은 다루지 않는다.


2. 보호모드로 들어가기


386에는 CR0, CR1, CR2, CR3이라는 32 비트 조절 레지스터(control register)가 네개 있다. CR1은 이후에 나올 프로세서를 위해 남겨둔 것으로 386에서는 정의되있지 않다. CR0에는 페이징(paging)과 보호(protection)를 키고끄는 비트와 부동소수점 코프로세서(floating point coprocessor)의 동작을 조절하는 비트가 있다. CR2와 CR3은 페이징과 관련이 있다. 우리는 CR0 레지스터의 0번째 비트인 PE (protection enable, 보호 킴) 비트에 관심이 있다. PE = 1 일때, 프로세서는 앞에서 설명한 세그먼트 구조를 가진 보호모드로 동작한다. PE = 0일때, 프로세서는 리얼모드로 동작한다. 386은 또 GDTR, LDTR, IDTR같은 세그먼트표 시작 레지스터 (segmentation table base register)를 가지고 있다. 이들 레지스터는 각각의 기술표를 가리킨다. GDTR은 GDT를 가리킨다. 48 비트 GDTR은 32 비트 주소와 16 비트 크기로 GDT의 시작주소와 크기를 정의한다.


보호모드로 옮기는 것은 기본적으로 PE 비트를 끼는 것이다. 그러나 우리가 해야할 일이 몇개있다. 프로그램은 시스템 세그먼트 레지스터와 조절 레지스터를 초기화해야 한다. PE 비트를 1로 한 직후 우리는 실행파이프라인(execution pipeline)에서 리얼모드에서 미리 얻어왔을지도 모르는 명령어를 지우기위해 jump 명령을 실행한다. 이 jump는 보통 바로다음 명령어로 건너뛴다. 보호모드로 옮기는 과정은 다음과 같다.



  1. GDT를 만든다.
  2. CR0의 PE 비트를 켜서 보호모드로 간다.
  3. 미리 가져온 명령어(prefetch queue)를 지우기위해 jump한다.

그러면 이를 위한 코드를 보자.


3. 필요한 것



  • 빈 플로피
  • NASM 어셈블러

여기서 코드를 받아라. 파일명을 abc.asm으로 바꾸고, nasm abc.asm을 실행하여 어셈블한다. 그러면 abc라는 파일이 생긴다. 플로피를 넣고 dd if=abc of=/dev/fd0 명령을 실행하여 파일 abc를 플로피의 첫번째 섹터에 쓴다. 시스템을 재시작하면 다음 문구를 순서대로 볼 수 있다.



  • Our os booting........................
  • A (갈색)
  • Switching to protected mode....
  • A (흰색)

4. 실행되는 코드 !


먼저 보호모드로 전환하는 코드를 살펴봐라. 뒤에 자세한 설명이 나온다.


이전 글(1부)에서 언급한대로 BIOS는 부트디바이스를 선택하고 첫번째 섹터를 주소 0x7c00에 옮긴다. 그래서 우리는 0x7c00에 코드를 쓴다. 이것이 org 지시어가 의미하는 것이다.


사용하는 함수


print_mesg: 이 함수는 BIOS 인터럽트 10h의 하위기능 13h을 이용하여 화면에 문자열을 쓴다. 적절한 값은 여러 레지스터에 두어 속성을 지정한다. 인터럽트 10h는 다양한 문자열 작업에 사용된다. 우리는 하위기능번호 13h을 ah에 두어, 문자열을 출력하고 싶다고 알린다. al 레지스터의 비트 0은 문자열을 출력한 후 커서 위치를 지정한다. 이 비트가 0이면 함수가 끝난후 다음 줄 시작으로 커서를 옮기고, 1이면 커서를 출력한 마지막 문자 바로 뒤에 둔다.


비디오 메모리는 비디오 출력 페이지(video display page)라는 여러 페이지로 나뉜다. 이중 한번에 한 페이지만 보인다. (비디오 메모리에 대해 더 자세한 내용은 1부를 참고하라.) bh는 페이지 번호, bl은 출력할 문자색, cx는 출력할 문자열 길이를 지정한다. 레지스터 dx는 커서 위치(dh는 행, dl은 열)를 지정한다. 모든 속성을 설정한후 BIOS 인터럽트 10h를 호출한다.


get_key: 다음 키입력을 얻어오는 BIOS 인터럽트 16h의 하위기능 00h를 사용한다. 하위기능번호는 레지스터 ah에 둔다.


clrscr: 문자열을 출력하기전에 화면을 지우기위해 인터럽트 10h의 하위기능 06h를 사용한다. 이를 위해, al에 0, cx와 dx에는 지울 화면 크기를 (이 경우 화면 전체) 담는다. 레지스터 bh는 화면에 채울 색을 지정한다. 여기서는 검정색이다.


모든 것이 시작하는 곳 !!


첫 어셈블리어 문장은 begin_boot로 건너뛴다. 우리는 리얼모드에서 갈색 'A'를 출력하고, GDT를 만들고 보호모드로 간 후, 흰색 'A'를 출력할 것이다. 이 두 모드는 서로 다른 주소 방식을 사용한다.


리얼모드에서 :


우리는 비디오 메모리를 가리키는 세그먼트 레지스터 gs를 사용한다. (기본 시작주소가 0xb8000인) CGA 어탭터(adapter)를 사용한다. 그러나 코드에서 0을 하나를 안적었다. 리얼모드 세그먼트 구조가 이 0을 제공하기 때문이다. 이 때문에 8086은 20비트 주소를 다룰 수 있다. 이 방식은 386의 리얼모드에도 그대로 이어진다. A의 아스키값은 0x41이다. 0x06은 문자색으로 갈색을 지칭한다. 화면은 키를 누를때까지 그대로 있다. 다음 우리는 화면에 보호모드로 넘어간다는 문구를 출력한다. 그래서 bp (base pointer, 시작을 가리키는) 레지스터로 출력할 문구를 지정한다.


보호모드로 발사 :


우리는 보호모드에 있는 동안 우리를 방해할 인터럽트가 없길 바란다. 그래서 인터럽트를 끈다. 이것이 cli가 하는 일이다. 우리는 나중에 다시 킬 것이다. 시작으로 GDT를 만든다. 우리는 보호모드로 전환하기위해 기술자를 4개 초기화한다. 이는 코드 세그먼트 (code_gdt)와 자료/스택 세그먼트 (data_gdt), 비디오 메모리에 접근하기위한 비디오 세그먼트이다. 잘못되지않는 한 사용되지 않을 가짜(dummy) 기술자도 초기화한다. 이것이 null 기술자다. 세그먼트 기술자의 내용을 좀 살펴보자.



  • 첫번째 워드(word)는 세그먼트의 크기를 담는다. 여기서는 간단히 최대값 FFFF (4G)를 사용한다. 비디오 세그먼트에는 이미 정의된 값 3999 (80 열 * 25 행 * 2 바이트 - 1)를 사용한다.
  • 코드와 자료 세그먼트의 시작주소는 0x0000이다. 비디오 세그먼트의 경우 0xb8000 (비디오 메모리 시작 주소)이다.

GDT 시작주소는 GDTR 시스템 레지스터에 있어야 한다. GDT의 첫번째 워드에 있는 크기와 두번째 dword (double word)에 있는 시작주소를 가지고 gdtr 세그먼트를 읽어들인다. lgdt 명령어는 GDTR 레지스터에 gdt 세그먼트를 읽어들인다. 우리는 이제 보호모드로 옮겨갈 준비가 끝났다. CR0의 최하위비트(즉, PE 비트)를 1로 바꾼다. 그러나 아직 우리는 완전한 보호모드에 있진 않다!


INTEL 80386 PROGRAMMER'S REFERENCE MANUAL 1986의 10.3 절은 다음과 같다 : PE 비트를 설정한 직후 초기화 코드는 JMP 명령어를 실행하여 프로세서가 미리 가져온 명령어를 지워야 한다. 80386은 명령어와 주소를 사용하기 전에 미리 가져온다. 그러나 보호모드로 전환한 후, 미리 가져온 (리얼모드 주소 방식을 사용하는) 명령어 정보는 사용할 수 없게 된다. JMP 명령어는 프로세서가 잘못된 정보를 버리게 한다.


우리는 이제 보호모드에 들어왔다. 확인하고 싶은가? 흰색으로 A를 출력해보자. 그러기위해 세그먼트 레지스터 ds, es를 자료 세그먼트 선택자(selector) (datasel)로 초기화한다. gs는 비디오 메모리 선택자 (videosel)로 초기화한다. 흰색 'A'를 표시하려면 ascii 값과 속성을 담은 워드를 [gs:0000] (즉, b8000 : 0000) 위치로 옮긴다. 시스템을 재시작할때까지 반복문이 화면에 글씨를 유지한다.


times 명령어는 섹터에서 사용하지않은 남은 바이트를 0으로 채운다. 부팅가능한 섹터임을 알리기위해 511, 512번째 바이트에 AA55를 쓴다. 이제 끝났다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함