In order to write an operating system, you need to understand many details. So let me enlighten you a little bit (but let's agree that you will read mana yourself so that there is something to talk about).
Honestly, in the open spaces of the network there is a cloud of obese materials on PM, and
iley and
pehat told
us a little about this mode, but I was asked to describe it anyway in the general framework. Now we will briefly give out the theory (in general, specially for this Intel wrote mana), then we will begin to write code.
Introduction to protected mode.So, PM is significantly different from all the usual since the days of DOS'a real mode (RM). Now we have to get used to it: there are no static, 64 kilobyte segments, interrupt tables in the 1st kilobyte, addresses of the bases of the segments in the segment registers, in general a completely new world.
Segments are now described in the
Global Descriptor Table (GDT) . This table can be only in one copy. She is a structure in mind. Not a segment! It can be located in memory anywhere, but its address and limit are written to the GDTR register. Here is its structure:

')
The table itself consists of the entries of the following structure (by the way, the zero entry is empty. This is important. When accessing the memory, described by the null descriptor, get #GP - General Protection Fault):
Let's take a closer look at this structure.
1. Segment Limit:The purpose of this field is understandable by name, but there is subtlety. The dog is buried in bit G (Granularity).
If it is not installed, the memory is 'counted' in bytes. In this case, the size of the segment can vary from 1 byte to 1 megabyte in size to 1 byte.
If we set it to 1, then memory paging will be entered. Then we can address from 4 kilobytes to 4 gigabytes of RAM with resizing by 4 kilobytes (page size). In general, paged addressing is preferable (compare (1MB + 64Kb-16Byte) and 4GB). Let's talk in this post only about segment addressing. Paging deserves a separate discussion.
2. Base Address:Here we indicate the physical address of the database.
3. Type field:Bit combinations determine the type of segment:
4. S (descriptor type):The Intel’s documentation says that if this bit is not set, then this descriptor is for the system segment, otherwise it is code or data. Under the system means LDT, TSS, Interrupt Gates and others like them (about them later).
5. DPL (Descriptor Privilege Level):Privileges of the described segment. All familiar Rings.
6. P (segment present):If this bit is set, the processor "knows" that the segment is already in memory (although it is better to say valid). If you load into the segment register a descriptor selector with the P bit not set, then the exception #NP (not present) will occur. In general, the meaning of this flowery phrase will explain later.
7. D / B:For segments of different types is interpreted differently.
1. For code segments:32 or 16 bit effective address length and operand dimension.
(1-32; 0-16);
2. For the stack:The stack pointer is 32 or 16 bit. (1-32; 0-16);
8. G:It affects the units in which (bytes, pages) the segment limit is measured. In general, Paging can be enabled when switching to PM by setting the 31 bits of the CR0 register.
Some more info:We guess that the word Global was set not in vain. So there is still some kind of label. True, there is also a
Local Descriptor Table . There may be a great many. For example, they can be used in the implementation of tasks, etc. But
LDT is already a segment! So get used to the phrases like 'descriptor segment of the local descriptor table'.
After we describe the table, it needs to be loaded into the
GDTR register . This is done by no means mov.
GDTR is filled with the
lgdt fword command (value) . That is, it is necessary to form this structure independently and load it into the aforementioned register. There are more teams working with this register, but we are galloping across Europe.
One more thing. In PM, segment registers are stored not in the base segment addresses (as in RM), but in specially trained pieces, called
selectors . Their structure is as follows:

Here Index is the sequence number of the descriptor in the table.
TI shows where to look for a descriptor (in
GDT or
LDT ).
Now, when it is already clear how to build a table, let's talk about how to go to PM (I note that this can only be done from RM). In general ... you just need to set bit 0 of the control register CR0. While lying. First you need to disable all interrupts (
NMI (
Non Maskable Interrupts ) including), open the
A20 address line (so that 32-bit addressing is available), load
GDTR , and jump to the start flag.
Let's use the bootloader (you can take KOLIBRI), which will load our code to the address 1000h: 0 (RM'ovsky, I note, the address).
Not everything will be as smooth here as in the manners when they go straight to PM from bootloader. Everything is a little more complicated. But first, let's analyze the code that the bootloader will load (we write everything on FASM). This is a kind of helloworld. Boot in, go to PM and print a greeting. Everything.
format binary
xor ax,ax
cli ;
mov ss,ax
xor sp,sp
sti
mov ax,3
int 10h
jmp 1000h:r_start
r_start:
mov ax,1000h;
mov ds,ax
mov es,ax
in al, 0x92; A20
or al, 2
out 0x92, al
cli ;
mov al,8Fh; NMI
out 70h,al
in al,71h
lgdt fword [GDTR]; GDTR
mov eax,cr0
or al,1; 0-
mov cr0,eax; PM
jmp fword 08h:Startup32; PM
align 8 ;
GDT:
dq 0 ;
db 0FFh,0FFh,0,0,0,9Ah,0CFh,0 ;
db 0FFh,0FFh,0,0,0,92h,0CFh,0;
db 0FFh,0FFh,0,80h,0Bh,92h,40h,0 ;
label GDT_SIZE at $-GDT
GDTR:
dw GDT_SIZE-1
dd GDT+10000h
; 32- . 1000h, 1000h*10h ( ; ) => GDTR (!) = 10000h ( )+offset
virtual ;, ,
rb 10000h-$;
end virtual
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;PM32 Entry;;;;;;;;;;;;;;;;;;;
use32
org $+10000h; : PM Flat-, ; PM org', ; Flat . .
Startup32: ; PM
mov ax,10h ; . (!
mov es,ax ;) - 08h. - 10h, - 18h
mov ds,ax
mov fs,ax
mov ss,ax
mov esp,10000h;
mov ax,18h
mov gs,ax
mov esi,hi_string ;,
call print
jmp $
;ESI -
print:
pushad
xor ebx,ebx
mov ah,07h;
puts:
mov al,[esi+ebx]
mov [gs:(ebx*2)],ax
inc ebx
test al,al
jnz puts
popad
ret
hi_string db 'Welcome to PM, dude',0
What have we done? The loader successfully loaded us at the address 1000h: 0, from where we continued execution. First, they turned on
A20 , banned all interruptions, loaded a suitable value into
GDTR , jumped to the input label. I note that we jumped on
jmp fword 08h:Startup32
Ie 08h - code descriptor selector. Get used to it.
Now how to run this miracle. I personally use WinImage and VirtualBox. We push the bootloader into the floppy disk sector and put the .bin file in the root. We save in .vfd, set the path to the floppy image in the properties of the virtual machine, run and see the result.
In the next issue we will look at interrupts, faults, traps, aborts and how they work, are caught and debugged. Let's start talking about architecture.
Information sources.
1) Just want to express my gratitude to Phantom_84 aka egos for pointing the right path and helping me in the very beginning. Without it, it would be much harder for me to figure it out.
2)
BrokenSword Articles BrokenSword Articles. They should pay attention.
3)
Intel System Programming Guides