x86 보호모드
읽어보기 전에 ¶이 문서에서 사용된 표기는 실제 이름과 다를수 있습니다. 단지 설명의 용이성을 위해서 임의로 붙인 표기입니다.
이 문서에 기술된 표에서 특별한 설명이 없으면 0으로 간주하시면 됩니다. 이는 예약영역의 의미가 짙어서 0입니다.
방준영님의 깊은 조언과 정경주님의 오타지적 감사드립니다.
Segment register ¶
Segment descriptor ¶각각의 Segment는 8byte의 Segment descriptor라는 것에 의해 기술되는데 기술되는 종류로는 GDT(Global Descriptor Table) 또는 LDT(Local Descriptor Table)가 있습니다.
GDT는 1개만을 사용하지만 LDT는 각 Process마다 따로 소유할수 있습니다.
Memory상의 GDT와 LDT의 Descriptor의 주소와 한계는 gdtr과 ldtr이라는 Register에 읽혀집니다.
LDT는 GDT로부터 선택이되며 GDT중 한 개는 LDT를 주소지정하도록 되어 있습니다.
Descriptor의 구조 ¶
Selector의 구조 ¶
Segment 장치 ¶TI에 의해서 선택된 gdtr 또는 ldtr을 읽어 GDT 또는 LDT에서 Descriptor를 선택하고 선택된 Descriptor에서 Base + Offset register를 주소지정하게 되며 Limit*(G bit MUL)를 벋어난 Offset을 가질 경우 예외처리(흔히 말하는 Segment fault)가 수행 됩니다.
System descriptor의 종류 ¶위에서 Access right bit라고 칭한 부분중에서 E, ED/C, R/W, A를 S bit에 의해서 아래의 표를 참고로 하여 System descriptor로 성격이 바뀌게 됩니다.
Gate descriptor ¶
Word counter는 호출자의 Stack 으로부터 호출된 Gate에 의해 접근된 Procedure의 Stack으로 몇개의 Word가 전송될 것인지를 나타냅니다. 간단히 설명하자면 인자의 개수를 몇개로 받을지를 예기하는 겁니다. 물론 다른 목적도 있지만 교과서적인 내용으로 봤을때는 ... Word 개수 항목은 Interrupt gate와 함께 사용되지 않는다는데 유의할 필요가 있겠습니다. (즉, 이 경우 Word count=0) Gate가 접근될때 Selector의 내용이 TR(Task register)에 탑재된다는 것도 인지하고 있어야 합니다. General protection위반의 종류 ¶이것은 Interrupt 0x0d와 밀접한 관련이 있습니다.
Paging ¶
확장된 Paging ¶Pentium에서는 보다 확장된 Paging이 제공되며 Page frame의 크기가 4K에서 4M로 확장가능하므로 보다 큰 메모리블럭을 적은수의 페이지 디렉토리로 관리할수 있게 됩니다. 단, 이것이 항상 장점으로 남을수는 없습니다. 조그마한 메모리만을 Paging하기 원한다면 이것은 메모리 효율을 떨어뜨리는 결과를 초래할수 있겠습니다. 보통 32bit 머신에서는 4M의 필요성이 큰 효율을 가져다 주지는 못할것입니다. (필자는 개인적으로 소프트웨어가 2G이상의 메모리를 요구하는 경우 4M page를 종종 사용하는데 page의 관리차원의 Mapping 절차에 대한 단순함으로 큰 메모리 블럭을 자주 사용하는 task의 경우는 이를 종종 사용하였습니다. 어찌되었건 이것은 제 코드상에서는 성능 향상을 보인것으로 보아서 거대한 메모리 Management 특성을 가진 소프트웨어에서는 간혹 써볼만 한것 같습니다.)
CR4 register의 bit4번(Page size enable)을 Set하면 4M Page모드로 확장됩니다. 4M mode로 들어가면 다음과 같이 선형주소가 바뀝니다. (Page table이 없다는 점에 주의)
Three-level paging ¶RAM이 싸게 공급되어 용량이 커지더라도 Page table을 Memory공간에 낭비를 하는 것은 바람직하지 않으므로 64bit에서는 Three-level pasing을 사용해야 할것입니다. !Alpha와 UltraSPARC은 64bit계열이며 이는 Three-level paging을 여기에 적용할 수 있습니다. Page크기가 무조건 크게 한다고 좋은게 아니므로 16K Page가 적당합니다. 16K는 14bit로 주소지정이 가능하므로 offset 항목은 14bit로 하고 나머지 50bit중 25bit씩 각각 Directory와 Table로 사용한다면 3200만개의 Page가 가능합니다.
Alpha microprocessor에서는 다음과 같습니다. Page는 8K로 하여 13bit이고 PageTable은 10비트씩 3개로 쪼개어 Two-level에서는 10bit=1024만큼의 PageTable을 가질수 있게 합니다. 나머지 21bit는 항상 0입니다.
Hardware cache ¶PCD : Microprocessor의 PCD핀의 기능을 제어하며 이 것이 set되면 Page가 아닌 Bus사이클동안 논리 1이 됩니다. 이것은 외부 하드웨어가 Level-II cache memory를 제어할수 있게 합니다. (간단히 말해서 Cache할건지 안할건지)
PWT : Microprocessor의 PWT핀의 기능을 제어하며 Write-through cache를 제어하기 위해서Page가 아닌 Bus사이클동안 PWT핀에 나타납니다.
이 비트는 신중히 결정해야 한다고 필자는 강력히 주장합니다. 잘 선택하면 성능을 극대화 합니다. Translation Lookaside Buffers (TLB) ¶x86계열의 CPU에서는 Paging을 위해서 매번 Page directory를 읽는것은 줄이고자 TLB라는 일종의 Directory cache buffer를 사용합니다. Pentium부터 4 MByte paging을 지원하도록 되었으며 이것이 4K page와 차이점은 4M page는 Page table이 존재하지 않는것이 특징이며 Page directory 크기를 줄일수 있고 큰 블럭을 다루므로 속도적인 면에서 약간의 우위를 갖습니다. 다만, 메모리 효율적인 측면은 잘못하면 최악의 메모리 사용율이 발생할수 있으므로 이점은 반드시 고려되어야 합니다.
특권레벨이 높은 운영체제(레벨 0)등에서 이 Page directory를 효율적으로 관리하기 위해 자주 바꾸게 되는데 문제는 특권레벨이 낮은 응용프로그램에서는 이를 바뀌었는지 알 필요도 없고 TLB에 대한 제어도 할수 없으므로 매번 운영체제가 그것을 비워주거나 바뀐부분만을 TLB로부터 갱신해주어야 하는 문제가 있습니다. 다행이도 이것이 우연인지 아닌지 별로 그렇게 큰 신경을 쓸 필요가 없다는데 다행일뿐이지만 어쨌건 왜 다행인지는 알아야 겠죠? PUSHF ; 여기서는 PUSHF, CLI, POPF는 개념적인 이해를 위해 쓴겁니다. CLI MOV EAX, CR3 MOV CR3, EAX POPF 즉, 그냥 CR3 register를 읽고 다시 써주면 TLB가 자연스럽게 정리작업이 됩니다. 이것이 왜 자연스럽게 되는가? 그것은 Task를 전환할때 이미 이것이 작용하기 때문입니다. 그 다음은 Scheduler를 이해하면 되는데 이 문서는 범위를 여기까지만 소개하도록 하겠습니다. 참고로 임의의 TLB 특정 부분을 갱신할수 있는 명령이 있는데 잊어먹었습니다. 나중에 생각나면 쓰도록 하겠습니다. GDT/IDT Setup -> 보호모드 진입 및 리얼모드 돌아오기 예제 ¶; Copyright (c) MINZ ; Code by JaeHyuk Cho - <mailto:minzkn@infoeq.com> DEF_ASM_GO32 EQU "GO32.ASM" DEF_MAX_DefaultGDT = 2000h ; 8192d DEF_MAX_DefaultIDT = 0100h ; 256d PUBLIC RegisterIDT, RegisterGDT PUBLIC SetupIDT, SetupGDT PUBLIC Go32 PUBLIC L_Exit32 ; PUBLIC D_StackFrame PUBLIC D_IDTR, D_GDTR PUBLIC D_IDT_Item, D_GDT_Item PUBLIC D_IDT, D_GDT ASSUME CS:CODE_GO32, DS:DATA_GO32, ES:NOTHING, SS:STACK_DEFAULT CODE_GO32 SEGMENT여기까지는 그냥 일반적인 어셈블리의 앞 머리입니다. RegisterIDT PROC FAR ; void far pascal RegisterIDT(unsigned int s_intnum) ; IDT 추가 생성 PUSH BP MOV BP, SP PUSH EAX PUSH EDX XOR EAX, EAX MOV AX, WORD PTR [BP + 06h] ; s_intnum MOV DX, DEF_SIZE_Descriptor MUL DX ADD EAX, OFFSET DESC_GO32_IDT:D_IDT PUSH DESC_GO32_IDT PUSH AX PUSH DATA_GO32 PUSH OFFSET DATA_GO32:D_IDT_Item PUSH DEF_SIZE_Descriptor CALL FAR PTR CODE_MEMORY:MemCpy POP EDX POP EAX POP BP RETF 2 RegisterIDT ENDPIDT를 구조에 맞게 인자로 받아서 만들어 줍니다. RegisterGDT PROC FAR ; void far pascal RegisterGDT(unsigned int s_descnum) PUSH BP MOV BP, SP PUSH EAX PUSH EDX XOR EAX, EAX MOV AX, WORD PTR [BP + 06h] ; s_descnum MOV DX, DEF_SIZE_Descriptor MUL DX ADD EAX, OFFSET DESC_GO32_GDT:D_GDT PUSH DESC_GO32_GDT PUSH AX PUSH DATA_GO32 PUSH OFFSET DATA_GO32:D_GDT_Item PUSH DEF_SIZE_Descriptor CALL FAR PTR CODE_MEMORY:MemCpy POP EDX POP EAX POP BP RETF 2 RegisterGDT ENDPGDT를 구조에 맞게 인자로 받아서 만들어 줍니다. SetupIDT PROC FAR ; void far pascal SetupIDT(void) PUSH DS PUSH AX MOV AX, DATA_GO32 MOV DS, AX POP AX POP DS RETF SetupIDT ENDPIDT를 만드는 부분이지만 그냥 비어 있습니다. !SetupGDT함수처럼 RegisterIDT를 사용하여 안에 채워넣으면 됩니다. SetupGDT PROC FAR ; void far pascal SetupGDT(void) PUSH DS PUSH AX PUSH BX MOV AX, DATA_GO32 MOV DS, AX ; 0000h - Null descriptor XOR AX, AX MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_LimitLow, AX MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_BaseLow, AX MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseMid, AL MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access00, AL MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access01, AL MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseHigh, AL PUSH AX CALL FAR PTR CODE_GO32:RegisterGDT ; 0001h - IDT descriptor ; 0002h - GDT descriptor ; 0003h - Real code descriptor PUSH CODE_GO32 PUSH DEF_Null CALL FAR PTR CODE_CALC:ToPhysical MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_LimitLow, 0FFFFh MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_BaseLow, AX MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseMid, DL MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access00, 10011010b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access01, 00000000b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseHigh, DH PUSH 0003h CALL FAR PTR CODE_GO32:RegisterGDT ; 0004h - Real data descriptor PUSH DATA_GO32 PUSH DEF_Null CALL FAR PTR CODE_CALC:ToPhysical MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_LimitLow, 0FFFFh MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_BaseLow, AX MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseMid, DL MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access00, 10010010b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access01, 00000000b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseHigh, DH PUSH 0004h CALL FAR PTR CODE_GO32:RegisterGDT ; 0005h - Full data descriptor MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_LimitLow, 0FFFFh MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_BaseLow, 00000h MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseMid, 000h MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access00, 10010010b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access01, 11001111b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseHigh, 000h PUSH 0005h CALL FAR PTR CODE_GO32:RegisterGDT ; 0006h - Video data(TEXT) descriptor ; 0007h - Video data(GRAPHICS) descriptor ; 0008h - Code descriptor PUSH CODE_KN32 PUSH OFFSET CODE_KN32:KernelMain32 CALL FAR PTR CODE_CALC:ToPhysical MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_LimitLow, 0FFFFh MOV WORD PTR DATA_GO32:D_GDT_Item.STRUC_BaseLow, AX MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseMid, DL MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access00, 10011110b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_Access01, 11000000b MOV BYTE PTR DATA_GO32:D_GDT_Item.STRUC_BaseHigh, DH PUSH 0008h CALL FAR PTR CODE_GO32:RegisterGDT ; 0009h - Data descriptor ; 000Ah - Bss descriptor ; 000Bh - Stack descriptor ; 000Ch - Heap descriptor ; Load GDT MOV EAX, OFFSET DESC_GO32_GDT:D_GDT PUSH DESC_GO32_GDT PUSH AX CALL FAR PTR CODE_CALC:ToPhysical MOV WORD PTR DATA_GO32:D_GDTR.STRUC_LimitLow, (DEF_MAX_DefaultGDT * DEF_SIZE_Descriptor) - 1 MOV WORD PTR DATA_GO32:D_GDTR.STRUC_BaseLow, AX MOV BYTE PTR DATA_GO32:D_GDTR.STRUC_BaseMid, DL MOV BYTE PTR DATA_GO32:D_GDTR.STRUC_Access00, DH LGDT QWORD PTR DATA_GO32:D_GDTR POP BX POP AX POP DS RETF SetupGDT ENDP사용할 Descriptor를 성격에 맞게 등록합니다. Go32 PROC FAR ; void far pascal Go32(void) PUSH DS PUSH ES PUSH FS PUSH GS PUSHAD ; Print PUSH DATA_GO32 PUSH OFFSET DATA_GO32:S_MSG_Go32_Enter CALL FAR PTR CODE_TEXT:Puts ; Clear descriptor PUSH DESC_GO32_IDT MOV EAX, OFFSET DESC_GO32_IDT:D_IDT ; 32bit offset PUSH AX PUSH DEF_Null PUSH (DEF_MAX_DefaultIDT * DEF_SIZE_Descriptor) SHR 01h CALL FAR PTR CODE_MEMORY:MemSetW PUSH DESC_GO32_GDT MOV EAX, OFFSET DESC_GO32_GDT:D_GDT ; 32bit offset PUSH AX PUSH DEF_Null PUSH (DEF_MAX_DefaultGDT * DEF_SIZE_Descriptor) SHR 01h CALL FAR PTR CODE_MEMORY:MemSetW ; Setup descriptor & environ CALL FAR PTR CODE_GO32:SetupIDT CALL FAR PTR CODE_GO32:SetupGDT ; Set PE MOV AX, DATA_GO32 MOV DS, AX MOV AX, SS MOV WORD PTR DATA_GO32:D_StackFrame[DEF_Far_Segment], AX MOV WORD PTR DATA_GO32:D_StackFrame[DEF_Far_Offset], SP MOV EAX, CR0 OR AL, 01h MOV CR0, EAX ; 보호모드 진입점 MAC_ClearCache ; Code cache를 지우기 위해 그냥 바로 앞에 점프 "jmp $+2" MAC_JumpFar <0003h * DEF_SIZE_Descriptor>, <OFFSET CODE_GO32:L_GO32_Enter> ; DWORD jump 입니다. L_GO32_Enter LABEL FAR MOV AX, 0004h * DEF_SIZE_Descriptor MOV DS, AX MOV ES, AX MOV FS, AX MOV GS, AX ; MOV AX, 000Bh * DEF_SIZE_Descriptor ; MOV SS, AX ; XOR ESP, ESP XOR EAX, EAX XOR EBX, EBX XOR ECX, ECX XOR EDX, EDX XOR ESI, ESI XOR EDI, EDI XOR EBP, EBP MAC_JumpFar <0008h * DEF_SIZE_Descriptor>, <OFFSET CODE_KN32:KernelMain32> ; 실제 32비트 동작을 할 32비트 코드가 있는 곳 ; Clear PE L_Exit32 LABEL FAR MOV EAX, CR0 AND AL, 0FEh MOV CR0, EAX MAC_ClearCache MAC_JumpFar <CODE_GO32>, <OFFSET CODE_GO32:L_GO32_Return> ; Real mode로 돌아옵니다. 하지만 아직은 Real mode의 완전한 반환은 아닙니다. L_GO32_Return LABEL FAR ; Restore environ MOV AX, DATA_GO32 ; 여기서 실제로 Real mode를 위한 Segment를 배치하여 줍니다. MOV DS, AX XOR AX, AX MOV ES, AX MOV FS, AX MOV GS, AX LSS SP, DWORD PTR DATA_GO32:D_StackFrame ; Print PUSH DATA_GO32 PUSH OFFSET DATA_GO32:S_MSG_Go32_Return CALL FAR PTR CODE_TEXT:Puts POPAD POP GS POP FS POP ES POP DS RETF Go32 ENDP보호모드로 진입했다가 그냥 나옵니다. CODE_GO32 ENDS ASSUME CS:CODE_GO32, DS:DATA_GO32, ES:NOTHING, SS:STACK_DEFAULT DATA_GO32 SEGMENT S_MSG_Go32_Enter DB 0FEh, " Enter to 32bit processing." DB DEF_ASCII_CarrigeReturn, DEF_ASCII_LineFeed DB DEF_ASCII_EndOfString S_MSG_Go32_Return DB 0FEh, " Return to 16bit processing." DB DEF_ASCII_CarrigeReturn, DEF_ASCII_LineFeed DB DEF_ASCII_EndOfString D_StackFrame DW DEF_Null, STACK_DEFAULT D_IDTR STRUC_Descriptor <0000h, 0000h, 00h, 00h, 00h, 00h> D_GDTR STRUC_Descriptor <0000h, 0000h, 00h, 00h, 00h, 00h> D_IDT_Item STRUC_Descriptor <0000h, 0000h, 00h, 00h, 00h, 00h> D_GDT_Item STRUC_Descriptor <0000h, 0000h, 00h, 00h, 00h, 00h> DATA_GO32 ENDS ASSUME CS:CODE_GO32, DS:BSS_GO32, ES:NOTHING, SS:STACK_DEFAULT BSS_GO32 SEGMENT BSS_GO32 ENDS ASSUME CS:CODE_GO32, DS:DESC_GO32_IDT, ES:NOTHING, SS:STACK_DEFAULT DESC_GO32_IDT SEGMENT D_IDT STRUC_Descriptor DEF_MAX_DefaultIDT DUP (<>) DESC_GO32_IDT ENDS ASSUME CS:CODE_GO32, DS:DESC_GO32_GDT, ES:NOTHING, SS:STACK_DEFAULT DESC_GO32_GDT SEGMENT D_GDT STRUC_Descriptor DEF_MAX_DefaultGDT DUP (<>) DESC_GO32_GDT ENDS END ; End of source보시는 바와 같이 필요한 Data segment를 기술하였습니다. |