Page 1 of 1

A Basic PMode Kernel with Paging

Posted: Mon Sep 06, 2004 1:26 pm
by kernel_journeyman
I've figured out so far that I start out in real mode in the boot sector, and load in the rest of the kernel with the BIOS routines (e.g. floppy disk, or CD-ROM if using floppy emulation), set the stack pointer to some arbitrary location (0x9000 in my case) and load the kernel at 4KB (0000:1000). I then far jump to 0000:1000.

At this point, I want to start in protected mode and enable paging. First, I must have a GDT and IDT ready. I have my GDT like this (expressed in NASM syntax):

align 8

gdt:
   dd   0
   dd   0
   ; Kernel code segment
   dd   0x0000FFFF
   db   0x00, 11001111b, 10001000b, 0x00
   ; Kernel data/stack segment
   dd   0x0000FFFF
   db   0x00, 11001111b, 10000010b, 0x00
end_gdt:

gdt_limit:
dw gdt_end - gdt - 1
gdt_location:
dd gdt

The first selector is the null selector. Is what I have above correct? I.e. the selector is always two dwords?

To load the GDT in NASM syntax:

lgdt [gdt_limit]

This seems funny to me... I thought I would have had to lgdt with a pointer to a word then a dword for the limit and location respectively. But here I am derefencing to the 6 bytes that lgdt needs instead. Presumably it works, but I don't understand why. Maybe internally the assembler arranges for the six bytes to be encoded in the instruction. I used ndisasm to have a look, but couldn't decipher it!

Anyway, what shall I do for my IDT? Do I need to re-program the PIC because of the system using Intel processor reserved IRQ vectors for other things? When the PIC is reprogrammed, does this mean it will cause the processor to fetch the correct IRQ vector -- i.e. the first hard disk is IRQ 14 but after reprogramming it will be something else because Intel reserves 14 for the page fault exception?

So presumably for my IDT I need to look in the Intel Manual System Programming Guide for the correct IRQ vector numbers, and install my handlers for them with IRQ 1 corresponding to the first descriptor in the IDT, IRQ 2 the second descriptor in the IDT, etc.?

If I want my syscall vector to be 0x60, e.g. in software:

mov eax, 1 ; syscall number 1 for read (just an example)
push dword [count] ; number of bytes to read
push dword buffer ; pointer to buffer for read
push dword 0 ; file descriptor 0 for stdin
int 0x60 ; call kernel sys_read
add esp, 12

... then I need to set up location 0x60 in the IDT to point to my syscall handler?

All the rest of the unused descriptors marked as not present?

Because the addresses for the GDT need to be linear, and in real mode they are set as logical addresses, how do I calculate this? Simply shift left the ds register (assuming it points correctly to data segment where GDT is) by 4 to multiply it by 16, then add the offset of the GDT from the ds register? Do I need to do this for the lidt instruction, too? (The Intel manual doesn't say so...) Do the pointers in the IDT need to be linear address pointers? I would assume so.

Once this is done, and I have done a lgdt and a lidt, I can enable the A20 gate and set the pmode bit and switch into pmode. Do I jump forward with a far jump to the next lot of instructions to force use of the selectors and such in the GDT? How does the processor know what selector in the GDT to use, since I have two, one for code and one for the stack/data area?

Okay, now assuming that I have gotten into pmode, and I want to enable paging, what are the minimum page tables that I need to setup? I want to map the kernel into the top 1GB of memory. This means, presumably, that I need to use a linker script to link the kernel at the correct physical and virtual addresses. Is that right?

Phew! That's all for the moment. Thanks for your help. ;D

Re:A Basic PMode Kernel with Paging

Posted: Mon Sep 06, 2004 3:46 pm
by Pype.Clicker
kernel_journeyman wrote: To load the GDT in NASM syntax:

lgdt [gdt_limit]

This seems funny to me... I thought I would have had to lgdt with a pointer to a word then a dword for the limit and location respectively. But here I am derefencing to the 6 bytes that lgdt needs instead.
That's just the wy it work: the LG/IDT instruction assume they're given the memory reference of a 6 bytes "structure" made of the limit and base. The assembler does nothing magic (esp. Nasm *never* does anything magic): if just uses your [gdt_limit] to issue the EffectiveAddress computation that will be used by the CPU to load GDTR.

much like when doing mov eax,[somevar], the content of "somevar" is nowhere in the instruction...
Do I need to re-program the PIC because of the system using Intel processor reserved IRQ vectors for other things?
yes. most of us remap IRQs to 0x20..0x2f. so timer calls IRQ0 -- mapped to int 0x20 (if unmasked), which is IDT entry .. number 32 ...
... then I need to set up location 0x60 in the IDT to point to my syscall handler?
All the rest of the unused descriptors marked as not present?
yes and yes (unless you want them for SomethingElse(tm))
Simply shift left the ds register (assuming it points correctly to data segment where GDT is) by 4 to multiply it by 16, then add the offset of the GDT from the ds register? Do I need to do this for the lidt instruction, too?
yes and yes (when in real mode). Though if you manage to have DS==0 while setting things up in real mode, it'll be easier for you.

Pointers within the IDT descriptors are offset within the given code segment

Re:A Basic PMode Kernel with Paging

Posted: Wed Sep 08, 2004 6:42 am
by kernel_journeyman
Thankyou very much Pype, I greatly appreciate your help.

I am trying to load like this:

[BITS 16]
[SECTION .text]

[ORG 0]

start:
   db 0xEA
   jmp 0x7C00:main
   
main:
   push cs
   pop ds      ; setup data seg to 7C00:0000
   push cs
   pop es      ; ditto extra seg

   mov cx, 256   ; 256 words for this bootsector size
   mov si, 0x7C00   ; move this
   mov di, 0x1000      ; to here
   cld
   rep movsw      ; do it
   
   ; presumably we are now at 1000:0000
   ; XXX do we need to far jump to reset seg registers???
setup:
   cli
   xor ax, ax
   mov ss, ax
   mov sp, 0xFFFF   ; stack seg is 0000:0000 to 0000:FFFF
   
   mov cx, 5         ; try 5 times
   mov bx, 0x1200   ; 0x1000 + 512 bytes
   
   ; After the bootsector, which is 512 bytes and starts at
   ; 1000:0000, I want the "kernel" to sit directly after it.
   ; Presumably this would be 0000:1200h ?
again:
   call reset_floppy
   call load_kernel
   dec cx      ; note that the calls preserve cx
   cmp cx, 0      ; five times? We're done... die.
   je die
   jc again      ; less than five times, but failed? retry
   
   sti         ; OK, we're done. It worked...
   
   ; Jump into kernel
   db 0xEA
   jmp 0000:0x1200      ; Jump into the "kernel"
   
   ; Put an E in video memory to tell about the error
   ; Not enough room for a full error message!
die:
   mov ax, 0xB800
   mov ds, ax
   mov byte [ds:0x00], 'E'   ; XXX address correct?
hang:
   hlt
   jmp hang

load_kernel:
   push cx
   ; Get ready to read in sectors 2 to 63
   mov ah, 2      ; read sector(s) into memory
   mov al, 63   ; number of sectors, here 63 = a whole track
   mov ch, 1      ; cylinder number
   mov cl, 2      ; starting sector
   mov dh, 1      ; XXX head number ???
   mov dl, 0      ; drive number
   int 13h
   pop cx
   ret

reset_floppy:
   mov ah, 0h
   mov dl, 0h
   int 13h
   ret
   
   times 510-($-$$) db 0
   dw 0xAA55


Now the "kernel":

[BITS 16]
[SECTION .text]

[ORG 0x1200]

start_kernel:
   push cs
   pop ds
   push 0xB800
   pop es
   
   mov si, message
   mov di, 0x0
   mov cx, [count]
   cld            ; err...
   rep movsb

[SECTION .data]
   
   message: db 'H e l l o W o r l d ! '
   count: dw $-message

--------------------------------------------------------------------------

I am a little confused at the direction flag. Anyway, I'll look it up in the Intel manual and figure out whether set is up or down and give it another try.

One thing that really confuses me is heads, sectors and cylinders. I understand that heads is synonymous with tracks. How do I know how many cylinders, heads/tracks and sectors are on a floppy disk? How do I know, once reading 63 sectors which seems to be a whole track/head, what to switch to in terms of cylinders and heads/tracks? For instance, sectors are numbered starting with 1, but cylinders and heads/tracks from 0?

If I want to read from the whole floppy, supposing I had a 1440KB boot image, how would I do it? Are the cylinders/heads/sectors the same for a 2.88MB floppy image (e.g. a bootable CDROM?)

Is it the case that 63 sectors (sectors 1 to 63) is head 0, cylinder 0? And sectors 64 to 127 is head 1, cylinder 0? When do cylinders change? Do the heads simply change every 63 sectors? Are they numbered 0 up to however many sectors there are on a floppy? E.g. 1.44MB / 512b / 63 = number of heads?

Thanks...

Re:A Basic PMode Kernel with Paging

Posted: Wed Sep 08, 2004 7:27 am
by Brendan
Hi,

Code: Select all

start:
   db 0xEA
   jmp 0x7C00:main

...

   db 0xEA
   jmp 0000:0x1200      ; Jump into the "kernel"
The "db 0xEA" things are will make it crash! Just remove them :)

Code: Select all

   mov cx, 256   ; 256 words for this bootsector size
   mov si, 0x7C00   ; move this
   mov di, 0x1000      ; to here
   cld
   rep movsw      ; do it
   
   ; presumably we are now at 1000:0000
   ; XXX do we need to far jump to reset seg registers???
Yes - you would need at least a far jump and reloading segments would be a good idea, but... is there any reason for this relocation?

kernel_journeyman wrote: I am a little confused at the direction flag. Anyway, I'll look it up in the Intel manual and figure out whether set is up or down and give it another try.
The "CLD" instruction will make addresses increase. Normally you don't wan't STDs :)
kernel_journeyman wrote: One thing that really confuses me is heads, sectors and cylinders. I understand that heads is synonymous with tracks. How do I know how many cylinders, heads/tracks and sectors are on a floppy disk? How do I know, once reading 63 sectors which seems to be a whole track/head, what to switch to in terms of cylinders and heads/tracks? For instance, sectors are numbered starting with 1, but cylinders and heads/tracks from 0?
Imagine that a disc consists of a set of rings ranging in size so that each ring fits inside the next largest ring. Each ring is a "cylinder" which has a top and a bottom, where the top is one track and the bottom is another track. These tracks are read/written via heads (one head on the top of the ring/cylinder and another on the bottom). The heads are mounted on a moving arm that can move from the outside ring/cylinder/track to the inside ring/cylinder/track. A sector is a part of a track, so there's multiple sectors around each track. This description is for 2 sided floppy disks only - single sided floppies don't have a head on the bottom, and hard drives have multiple discs with a head on the top and bottom of each disc.

Everything is numbered starting with 0, except for sectors. A standard "1.44 Mb" floppy disk has 2 heads/sides, 80 tracks/cylinders and 18 sectors per track. Each sector is 512 bytes, so the floppy will hold 2 * 80 * 18 * 512 bytes (or 1440 Kb) of data.
kernel_journeyman wrote: If I want to read from the whole floppy, supposing I had a 1440KB boot image, how would I do it? Are the cylinders/heads/sectors the same for a 2.88MB floppy image (e.g. a bootable CDROM?)
A 2880 Kb floppy is the same as a 1440 Kb floppy, except that it has twice as many sectors per track (36 of them).
kernel_journeyman wrote: Is it the case that 63 sectors (sectors 1 to 63) is head 0, cylinder 0? And sectors 64 to 127 is head 1, cylinder 0? When do cylinders change? Do the heads simply change every 63 sectors? Are they numbered 0 up to however many sectors there are on a floppy? E.g. 1.44MB / 512b / 63 = number of heads?
For a 1440 Kb floppy sectors 1 to 18 are head 0, cylinder 0. Sectors 19 to 36 are head 1, cylinder 0, and sectors 37 to 54 are head 0, cylinder 1.

Cylinders change after every sector in the current cyclinder is read (ie. each sector in each track under each each head). This is because electronically selecting which head to use is fast, and actually moving the heads takes the longest.

Taking all that into account:

Code: Select all

   ; Get ready to read in sectors 2 to 18 (8.5 Kb)
   mov ah, 2      ; read sector(s) into memory
   mov al, 18     ; number of sectors, here 18 = a whole track
   mov ch, 0      ; cylinder number
   mov cl, 2      ; starting sector
   mov dh, 0      ; head number
   mov dl, 0      ; drive number

Cheers,

Brendan

Re:A Basic PMode Kernel with Paging

Posted: Wed Sep 08, 2004 7:31 am
by DennisCGc
One thing that really confuses me is heads, sectors and cylinders. I understand that heads is synonymous with tracks.
Uhm, no, tracks synonymous with cylinders.
How do I know how many cylinders, heads/tracks and sectors are on a floppy disk?
They're fixed.
For instance a 1.44 MB floppy has 80 cylinders, 18 sectors per track, and 2 heads.
720 kb: 40/2/18 (C/H/S)
2.88 MB: 80/2/36 (don't know sure, someone ?) (C/H/S)
For instance, sectors are numbered starting with 1, but cylinders and heads/tracks from 0?
Yeah, correct, every HD/FD has that "numbering" system
HTH a bit ;)

DennisCGc

Re:A Basic PMode Kernel with Paging

Posted: Wed Sep 08, 2004 8:05 am
by kernel_journeyman
Ah, thanks guys!!!

You're the best! ;D

Re:A Basic PMode Kernel with Paging

Posted: Wed Sep 08, 2004 10:22 am
by Candy
DennisCGc wrote:
One thing that really confuses me is heads, sectors and cylinders. I understand that heads is synonymous with tracks.
Uhm, no, tracks synonymous with cylinders.
Almost.

A track is a unique combination of a head and a cylinder. It's the cylinder-part of one side of one platter. A floppy disk (1.44M) thus has 160 tracks of 18 sectors each, total of 2880 sectors.