SimpleOSdev

Writing a Simple Operating System — from Scratch(by Nick Blundell)

School of Computer Science, University of Birmingham, UK


First memory layout

mem layout


ASM tips

1
2
3
4
5
6
[org 0x7c00]
;tell the assembler our code will originate from memory 0x7c00
tag:
db 'Data'
;then the `tag` will be translated into `offset + 0x7c00`
;otherwise it is nothing but an offset

Stack

when storing value on stack, CPU decrease the sp register firstly and then store the value on stack

1
2
3
4
5
6
7
8
9
10
11
12
13
14
mov ax,0x8000
mov sp,ax
push 0x1234
;+--------------+ 0x8000
;| |
;+--------------+ 0x7fff
;| 0x12 |
;+--------------+ 0x7ffe
;| 0x34 |
;+--------------+ 0x7ffd
;| |
;+--------------+ 0x7ffc
;| |
;+--------------+ 0x7ffb

protect registers in function

1
2
3
4
5
function:
pusha
...
popa
ret

GDT and segment descriptor

A segment descriptor is an 8-byte structure that defines the following properties of a protected-mode segment:

  • Base address (32 bits), which defines where the segment begins in physical memory

  • Segment Limit (20 bits), which defines the size of the segment

  • Various flags, which affect how the CPU interprets the segment, such as the privilege level of code that runs within it or whether it is read- or write-only.

The simplest workable configuration of segment registers is described by Intel as the basic flat model, whereby two overlapping segments are defined that cover the full 4 GB of addressable memory, one for code and the other for data. The fact that in this model these two segments overlap means that there is no attempt to protect one segment from the other, nor is there any attempt to use the paging features for virtual memory.

The null descriptor is a simple mechanism to catch mistakes where we forget to set a particular segment register before accessing an address, which is easily done if we had some segment registers set to 0x0 and forgot to update them to the appropriate segment descriptors after switching to protected mode

let us look at how we might actually represent the GDT in assembly, a task that requires more patience than anything else.

interrupt handling is implemented completely differently in protected mode than in real mode


Far jump

Far jump do not mean jump a long distance physically but indicating that jump across segments


Addressing in 32-bit protected mode

img

80386 intel manual

segmentation translation

When paging is disabled you will directly get the physical address rather than a linear address

1
Physical address = Segment Base (Found from the descriptor GDT[]) + Offset

The 80386 stores information from descriptors in segment registers, thereby avoiding the need to consult a descriptor table every time it accesses memory.

The segment registers are actually larger than 16 bits but only 16 bits are visible!!!

img

Paging

img


Understand ld

-Ttext 0x0 has the same functionality with [org 0x0000], telling the linker how to offset the labels


Makefile

1
2
3
$< # first dependency
$^ # all dependencies
$@ # target
1
2
3
4
5
6
7
8
# generic rule for compiling object files
%.o : %.c
gcc $< -o $@
# select all file match the pattern
C_SOURCE = $(wildcard kernel/*.c driver/*.c)

# create object file names from source files
OBJ = $(C_SOURCE: .c=.o)

gcc inline assembly

% is used to denote registers, and this requires an ugly %%, since % is an escape character of the C compiler, and so %% means: escape the escape character, so that it will appear literally in the string

1
2
3
4
5
6
7
8
unsigned char port_byte_in ( unsigned short port ) {
// A handy C wrapper function that reads a byte from the specified port
// "=a" ( result ) means : put AL register in variable RESULT when finished
// "d" ( port ) means : load EDX with port
unsigned char result ;
__asm__ (" in %% dx , %% al " : "=a " ( result ) : "d " ( port ));
return result ;
}