x86-64 운영체제를 만들자 [1]

cppig1995의 이미지

레거시 모드와 롱 모드

AMD64 프로세서에는 크게 나누어 레거시 모드(보호 모드, 실제 모드, 가상 8086 모드)와 롱 모드(64비트 모드, 호환성 모드)가 있습니다. AMD64 프로세서는 롱 모드가 활성화되기 전까지는 일반적인 x86 32비트 CPU와 같이 동작합니다. 그래서, 64비트 기능을 활용하려면 롱 모드를 활성화시켜야 합니다.

레거시 모드와 롱 모드의 차이
* RAX-RDX, RDI, RSI, RBP, RSP 등 GPR이 64비트 크기입니다.
* R8-R15 레지스터가 새로 생겼습니다.
* XMM8-XMM15 SSE 레지스터가 새로 생겼습니다.
* 롱 모드를 활성화시키는 등 확장 기능을 활성/비활성화하기 위한 EFER 레지스터 및 다른 MSR들
* 4K 페이징(4단계 페이지 맵 -> 페이지 디렉토리 포인터 테이블 -> 페이지 디렉토리 -> 페이지 테이블 -> 4KiB 물리 페이지)
* 2M 페이징(4단계 페이지 맵 -> 페이지 디렉토리 포인터 테이블 -> 페이지 디렉토리 -> 2MiB 물리 페이지)

롱 모드의 지원 여부 확인

롱 모드를 활성화시키기 전에 롱 모드를 지원하는지 살펴봐야 합니다. 롱 모드를 지원하는지 확인하는 코드는 문서의 350쪽에 나와 있습니다.

; Use CPUID to determine if the processor supports long mode. ;
mov eax, 80000000h ; Extended-function 8000000h.
cpuid ; Is largest extended function
cmp eax, 80000000h ; any function > 80000000h?
jbe no_long_mode ; If not, no long mode.
mov eax, 80000001h ; Extended-function 8000001h.
cpuid ; Now EDX = extended-features flags.
bt edx, 29 ; Test if long mode is supported.
jnc no_long_mode ; Exit if not supported.

일단, EAX 레지스터에 0x80000000을 대입한 뒤 CPUID 명령을 실행하여 0x80000000보다 큰 명령 번호가 있는지 살펴봅니다. 밑의 CMP문으로 가장 큰 명령 번호가 0x80000000 이하인지 확인하고, 그런 경우 JBE문(Jump if Below or Equal)으로 no_long_mode로 분기합니다.
그 다음, 0x80000001을 EAX에 대입하고 CPUID를 실행하여 롱 모드가 가능한지 실제로 테스트합니다. BT 명령으로 EDX 레지스터의 비트 29를 테스트합니다. 0인 경우 JNC문(Jump if Not Carry)으로 no_long_mode로 분기합니다.

롱 모드의 허용과 활성화

롱 모드를 활성화시키기 위해서는, 일단 롱 모드를 가능하게 만들(enable)어야 합니다. 롱 모드가 가능하고, 페이징 및 PAE가 설정(set)되어 있으면 CPU는 롱 모드를 활성화시킵니다.

잠깐 문서를 살펴봅시다.

14.6.1 Activating Long Mode
Switching the processor to long mode requires several steps. In general, the sequence involves
disabling paging (CR0.PG=0), enabling physical-address extensions (CR4.PAE=1), loading CR3,
enabling long mode (EFER.LME=1), and finally enabling paging (CR0.PG=1).
Specifically, software must follow this sequence to activate long mode:
1. If starting from page-enabled protected mode, disable paging by clearing CR0.PG to 0. This
requires that the MOV CR0 instruction used to disable paging be located in an identity-mapped
page (virtual address equals physical address).
2. In any order:
- Enable physical-address extensions by setting CR4.PAE to 1. Long mode requires the use of
physical-address extensions (PAE) in order to support physical-address sizes greater than 32
bits. Physical-address extensions must be enabled before enabling paging.
- Load CR3 with the physical base-address of the level-4 page-map-table (PML4). See “Long-
Mode Page Translation” on page 132 for details on creating the 4-level page translation tables
required by long mode.
- Enable long mode by setting EFER.LME to 1.
3. Enable paging by setting CR0.PG to 1. This causes the processor to set the EFER.LMA bit to 1.
The instruction following the MOV CR0 that enables paging must be a branch, and both the MOV
CR0 and the following branch instruction must be located in an identity-mapped page.

14.6.1 롱 모드 활성화시키기
프로세서를 롱 모드에 진입시키기 위해서는 여러 단계가 필요합니다. 일반적으로, 페이징을 비활성화하고,
물리 주소 확장을 활성화하고, CR3에 4단계 페이지 맵(PML4; Page Map Level 4) 기반 주소(base address)를
불러옵니다. 그리고 EFER 레지스터의 LME 비트를 1로 만들고, 최종적으로 페이징을 활성화시킵니다.
구체적으로, 소프트웨어는 롱 모드를 활성화시키기 위해 반드시 다음 과정을 거쳐야 합니다:
1. 만약 페이징이 활성화된 보호 모드에서 시작한다면, 페이징을 비활성화(CR0.PG = 0)합니다. 이를 위해서,
MOV CR0 명령이 가상 주소와 물리 주소가 같은(identity-mapped) 페이지에 있어야 합니다.
2. 아무 순서로나:
- CR4 제어 레지스터의 PAE 비트를 1로 설정하여 물리 주소 확장을 활성화시킵니다. 롱 모드에서는 32비트
이상의 물리 주소를 사용해야 하기 때문에 PAE를 활성화시켜야 합니다. PAE는 페이징을 활성화하기 전에
반드시 활성화되어 있어야 합니다.
- 4단계 페이지 맵의 기반 주소를 CR3에 불러옵니다. (132쪽 참고)
- EFER 레지스터의 LME(롱 모드 허용) 비트를 1로 설정합니다.
3. CR0 제어 레지스터의 PG 비트를 1로 설정합니다. 이것으로 프로세서는 EFER 레지스터의 LMA(롱 모드 활성화)
비트를 1로 설정합니다. 페이징을 허용하는 MOV CR0 명령의 다음 명령은 반드시 분기문이어야 하며,
이 두 명령 모두 identity-mapped 페이지에 있어야 합니다.

롱 모드 진입 예제 코드

그러면, 14.6.1에 있는 과정만 있으면 되냐 하면, 그것도 그렇지 않습니다. 64비트 IDT와 GDT를 준비하여야 하며, 롱 모드에 진입하기 위해서 보호 모드로 진입하는 것이 필요하므로 32비트 IDT, GDT도 필요하게 됩니다. 너무 복잡하죠? 14장 8절에는 아예 예제가 있습니다.

mydata segment para
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; This generic data-segment holds pseudo-descriptors used
; by the LGDT and LIDT instructions.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Establish a temporary 32-bit GDT and IDT.
;
pGDT32 label fword ; Used by LGDT.
dw gdt32_limit ; GDT limit ...
dd gdt32_base ; and 32-bit GDT base
pIDT32 label fword ; Used by LIDT.
dw idt32_limit ; IDT limit ...
dd idt32_base ; and 32-bit IDT base
;
; Establish a 64-bit GDT and IDT (64-bit linear base-
; address)
;
pGDT64 label tbyte ; Used by LGDT.
dw gdt64_limit ; GDT limit ...
dq gdt64_base ; and 64-bit GDT base
pIDT64 label tbyte ; Used by LIDT.
dw idt64_limit ; IDT limit ...
dq idt64_base ; and 64-bit IDT base
mydata ends ; end of data segment
code16 segment para use16 ; 16-bit code segment
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; 16-bit code, real mode
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;
; Initialize DS to point to the data segment containing
; pGDT32 and PIDT32. Set up a real-mode stack pointer, SS:SP,
; in case of interrupts and exceptions.
;
cli
mov ax, seg mydata
mov ds, ax
mov ax, seg mystack
mov ss, ax
mov sp, esp0
;
; Use CPUID to determine if the processor supports long mode. ;
mov eax, 80000000h ; Extended-function 8000000h.
cpuid ; Is largest extended function
cmp eax, 80000000h ; any function > 80000000h?
jbe no_long_mode ; If not, no long mode.
mov eax, 80000001h ; Extended-function 8000001h.
cpuid ; Now EDX = extended-features flags.
bt edx, 29 ; Test if long mode is supported.
jnc no_long_mode ; Exit if not supported.
;
; Load the 32-bit GDT before entering protected mode.
; This GDT must contain, at a minimum, the following
; descriptors:
; 1) a CPL=0 16-bit code descriptor for this code segment.
; 2) a CPL=0 32/64-bit code descriptor for the 64-bit code.
; 3) a CPL=0 read/write data segment, usable as a stack
; (referenced by SS).
;
; Load the 32-bit IDT, in case any interrupts or exceptions
; occur after entering protected mode, but before enabling
; long mode).
;
; Initialize the GDTR and IDTR to point to the temporary
; 32-bit GDT and IDT, respectively.
;
lgdt ds:[pGDT32]
lidt ds:[pIDT32]
;
; Enable protected mode (CR0.PE=1).
;
mov eax, 000000011h
mov cr0, eax
;
; Execute a far jump to turn protected mode on.
; code16_sel must point to the previously-established 16-bit
; code descriptor located in the GDT (for the code currently
; being executed).
;
db 0eah ;Far jump...
dw offset now_in_prot;to offset...
dw code16_sel ;in current code segment.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; At this point we are in 16-bit protected mode, but long
; mode is still disabled.
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
now_in_prot:
;
; Set up the protected-mode stack pointer, SS:ESP.
; Stack_sel must point to the previously-established stack
; descriptor (read/write data segment), located in the GDT.
; Skip setting DS/ES/FS/GS, because we are jumping right to
; 64-bit code.
;
mov ax, stack_sel
mov ss, ax
mov esp, esp0
;
; Enable the 64-bit page-translation-table entries by
; setting CR4.PAE=1 (this is _required_ before activating
; long mode). Paging is not enabled until after long mode
; is enabled.
;
mov eax, cr4
bts eax, 5
mov cr4, eax
;
; Create the long-mode page tables, and initialize the
; 64-bit CR3 (page-table base address) to point to the base
; of the PML4 page table. The PML4 page table must be located
; below 4 Gbytes because only 32 bits of CR3 are loaded when
; the processor is not in 64-bit mode.
;
mov eax, pml4_base ; Pointer to PML4 table (<4GB).
mov cr3, eax ; Initialize CR3 with PML4 base.
;
; Enable long mode (set EFER.LME=1).
;
mov ecx, 0c0000080h ; EFER MSR number.
rdmsr ; Read EFER.
bts eax, 8 ; Set LME=1.
wrmsr ; Write EFER.
;
; Enable paging to activate long mode (set CR0.PG=1)
;
mov eax, cr0 ; Read CR0.
bts eax, 31 ; Set PE=1.
mov cr0, eax ; Write CR0.
;
; At this point, we are in 16-bit compatibility mode
; ( LMA=1, CS.L=0, CS.D=0 ).
; Now, jump to the 64-bit code segment. The offset must be
; equal to the linear address of the 64-bit entry point,
; because 64-bit code is in an unsegmented address space.
; The selector points to the 32/64-bit code selector in the
; current GDT.
;
db 066h
db 0eah
dd start64_linear
dw code64_sel
code16ends ; End of the 16-bit code segment
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Start of 64-bit code
;;
;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
code64 para use64
start64: ; At this point, we're using 64-bit code
;
; Point the 64-bit RSP register to the stack’s _linear_
; address. There is no need to set SS here, because the SS
; register is not used in 64-bit mode.
;
mov rsp, stack0_linear
;
; This LGDT is only needed if the long-mode GDT is to be
; located at a linear address above 4 Gbytes. If the long
; mode GDT is located at a 32-bit linear address, putting
; 64-bit descriptors in the GDT pointed to by [pGDT32] is
; just fine. pGDT64_linear is the _linear_ address of the
; 10-byte GDT pseudo-descriptor.
;
; The new GDT should have a valid CPL0 64-bit code segment
; descriptor at the entry-point corresponding to the current
; CS selector. Alternatively, a far transfer to a valid CPL0
; 64-bit code segment descriptor in the new GDT must be done
; before enabling interrupts.
;
lgdt [pGDT64_linear]
;
; Load the 64-bit IDT. This is _required_, because the 64-bit
; IDT uses 64-bit interrupt descriptors, while the 32-bit
; IDT used 32-bit interrupt descriptors. pIDT64_linear is
; the _linear_ address of the 10-byte IDT pseudo-descriptor.
;
lidt [pIDT64_linear]
;
; Set the current TSS. tss_sel should point to a 64-bit TSS
; descriptor in the current GDT. The TSS is used for
; inner-level stack pointers and the IO bit-map.
;
mov ax, tss_sel
ltr ax
;
; Set the current LDT. ldt_sel should point to a 64-bit LDT
; descriptor in the current GDT.
;
mov ax, ldt_sel
lldt ax
;
; Using fs: and gs: prefixes on memory accesses still uses
; the 32-bit fs.base and gs.base. Reload these 2 registers
; before using the fs: and gs: prefixes. FS and GS can be
; loaded from the GDT using a normal “mov fs,foo” type
; instructions, which loads a 32-bit base into FS or GS.
; Alternatively, use WRMSR to assign 64-bit base values to
; MSR_FS_base or MSR_GS_base.
;
mov ecx, MSR_FS_base
mov eax, FsbaseLow
mov edx, FsbaseHi
wrmsr
;
; Reload CR3 if long-mode page tables are to be located above
; 4 Gbytes. Because the original CR3 load was done in 32-bit
; legacy mode, it could only load 32 bits into CR3. Thus, the
; current page tables are located in the lower 4 Gbytes of
; physical memory. This MOV to CR3 is only needed if the
; actual long-mode page tables should be located at a linear
; address above 4 Gbytes.
;
mov rax, final_pml4_base ; Point to PML4
mov cr3, rax ; Load 64-bit CR3
;
; Enable interrupts.
;
sti ; Enabled INTR
<insert 64-bit code here>

2부부터는 NASM을 이용해 직접 64비트 부트로더와 커널을 제작해 보도록 하겠습니다.

Forums: 
익명사용자의 이미지

풀소스로 완성된건 없나요?

cronex의 이미지

글쓰시는 돼지님도 이제 공부하며 만들어가시는 중으로 알고 있습니다.
하나하나 같이 따라가면서 배우는걸로 만족하도록 하죠. ^^

------------------------------------------------------------
이 멍청이~! 나한테 이길 수 있다고 생각했었냐~?
광란의 귀공자 데코스 와이즈멜 님이라구~!

------------------------------------------------------------
이 멍청이~! 나한테 이길 수 있다고 생각했었냐~?
광란의 귀공자 데코스 와이즈멜 님이라구~!

태훈의 이미지

잘보고있습니다. 다음강좌는 언제 올라오나요?

Just do it!

cppig1995의 이미지

요즘 중학교 입학후 첫시험인 진단평가(3월 20일) 때문에 공부하느라 나름대로 바쁩니다.
2부에서는 부트로더를 다룹니다. 이틀 안으로 올릴 생각입니다.
읽어 주셔서 감사합니다.
------------------------------------------------------
In simplexitate est opportunitas. --cppig1995
[낡배밀] 낡은 리눅스 배포판을 밀어내야 한다고 생각합니다.

Real programmers /* don't */ comment their code.
If it was hard to write, it should be /* hard to */ read.

namhw의 이미지

대단하네요. 기대하고 있겠습니다~ ^^

촌놈.

cppig1995의 이미지

2부 올렸습니다. http://kldp.org/taxonomy/3663 에서 모든 강좌를 보실 수 있습니다.
------------------------------------------------------
In simplexitate est opportunitas. --cppig1995
[낡배밀] 낡은 리눅스 배포판을 밀어내야 한다고 생각합니다.

Real programmers /* don't */ comment their code.
If it was hard to write, it should be /* hard to */ read.

nthroot의 이미지

위에 링크... http://kldp.org/taxonomy/term/3663 여기인것같네요

------식은이 처------
길이 끝나는 저기엔 아무 것도 없어요. 희망이고 나발이고 아무 것도 없어.

cppig1995의 이미지

앗 실수. 왠지 뭔가 빠진것 같았는데... 지적 감사드립니다.
------------------------------------------------------
In simplexitate est opportunitas. --cppig1995
"x86-64 운영체제를 만들자" 강좌: http://kldp.org/taxonomy/term/3663
2007학년도 대전월평중학교 1학년 3반 학급카페: http://cafe.naver.com/djwp0713

Real programmers /* don't */ comment their code.
If it was hard to write, it should be /* hard to */ read.

댓글 달기

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