CS452 - Real-Time Programming - Fall 2008
Lecture 7 - Architecture
Questions & Comments
Kernel: Part 1
Primitives to Implement
Test Program
Hardware Facts for Eos
Memory Map
- 0x00000000 to 0x0009FFFF: low memory (size is 640K)
- 0x000A0000 to 0x000FFFFF: VGA, etc (size is 360K)
- 0x00100000 to 0x00xFFFFF: downloaded code (size is x Mbyte)
- 0x00(x+1)00000 to 0x00yFFFFF: your kernel's code (size is y-x
Mbyte)
- 0x00(y+1)00000 to 0x7FFFFFFF: free memory for tasks (size is the
remainder of 2 Gbyte)
Note. x and y could be numbers above F
Devices
Accessed by special I/O instructions
Architecture of X86
General Purpose Registers (all 32-bit)
- E[A-D]X: general purpose registers
- ESP: the stack pointer
- EBP: the base pointer
- ESI, EDI: used for string copying
All these registers have special names for the low 16 bits. (drop the E),
and for the low 8 bits (add an L), and for the second 8 bits (add an H)
Special Purpose Registers
- IDTR: interrupt descriptor table register
- the IDTR is 48 bits, divided [--16--|---32---]
- 16 bit part is limit, the size of the table
- 32 bit part is base, the start of the table
- write using
lidt [idtr],
where idtr is a pointer a
struct containing an IDTR
- read using
sidt [idtr]
ID: interrupt descriptor
- an interrupt descriptor is 5 bytes divided
[-lb-|-ss-|-0-|-psw-|-hb-]
- lb, hb: low and high bytes of the ISR
- ss: segment in which ISR exists
- 0: zero, all zero bits
- psw: flags, low five bits must be 01110, bits 5&6 are
privilege level (0 for your kernel), bit 7 is present bit, which
indicates if the segment is in memory or not. (1 for your
kernel)
- The usual way to write an ID to the IDT is to
- make a ID struct and fill it in
- get the contents of the IDTR into an IDT struct
- cast the base part of the IDT struct to (ID *) idtb
- write the struct to *(idtb + n) where n is the number of the
interrupt vector
References
- GDTR: global descriptor table
- the GDTR is 48 bits, divided [--16--|---32---]
- 16 bit part is limit, the size of the table
- 32 bit part is base, the start of the table
- write using
lgdt [gdtr],
where idtr is a pointer a
struct containing an interrupt descriptor
- read using
sidt [gdtr]
GD: global descriptor, describes a memory segment
- the GD is 8 bytes, divided [--lim--|--lb--|-mb-|-gr-|-ac-|-hb-]
- lb, low 16 bits of segment base address; mb, next 8 bits of
base, hb, next 8 bits of base
- lim, low 16 bits of segment size
- gr, 8 bits divided [-gr-|-sz-| ---rest ---]
- gr, 1 bit, granularity, set to 1=1Kbyte (0=1 byte)
- sz, 1 bit, set to 1
- rest, set to 000000.
- ac, 8 bits divided [-p-|-|dpl-|-dt-|-ex-|-dc-|-rw-|-ac-]
- p, 1 bit, segment present in memory (1)
- dpl, 2 bits, protection level (00)
- dt, 1 bit, set to 1
- ex, set if executable (CS: 1, DS:0)
- dc, direction from base , set to 0 (grows up)
- rw, read/write( CS 1=readable, DS: 1=wrirable)
- ac, set to 0
- type of segment (CS: 1010, DS: 0010)
There is no GDT in place when the kernel boots
- You have to create the table
- You have to create the GDTs that go in the table
References
- CS, DS, ES, FS, GS, SS: segment registers
- EIP: program counter
Addressing Memory
Physical address is
- start of segment + offset
Helps to provide
- position independent (relocatable) code
- memory protection
- program owns segment(s)
- segment has low and high limits
- program can't access outside the limits
Six segment registers
- CS
- DS
- ES
- FS
- GS
- SS
Each segment register is an index into a Global Descriptor Table (GDT),
which contains
Index is actually contents of register >> 3.
Setting up a Task
Every task requires at least two GDTs
- accessed through CS and DS
- This includes the kernel
The compiler assumes that DS = SS
- That is, the stack is in the data segment.
Set DS = ES = FS = GS = SS for each task
There is no GDT in place when the kernel boots
How is this done
- The address of the table is kept in the special register GDTR
- lgdt sets GDTR; sgdt reads GDTR
Yet another register EFLAGS contains processor state such as
- condition codes
- what is enabled
- Getting started you want to have interrupts disabled!
Setting up a task
- CS points to code in ELF executable
- DS points to memory you allocate for the task.
Getting the Kernel Started
Each task is a C function
So is the kernel
Each gets compiled and linked into an ELF executable
- ELF = Executable and Linking Format
Then 452-post.py
(/u/cs452/public/tools/bin/452-post.py
) binds the ELFs together
to be downloaded.
Downloading
Before starting to execute the kernel needs to have
- A stack, on which it can place data, with the stack pointer set.
- Its first instruction in the PC
The boot loader does this. How?
- The bound executable obeys the multiboot specification
- The kernel is called by
int main( unsigned long magic,
multiboot_info_t *mbiaddr )
- The stack starts as
mbiaddr, magic
multiboot_info_t
is a struct with the following
interesting fields
typedef struct multiboot_info {
...
unsigned long mods_count;
unsigned long mods_addr;
...
} multiboot_info_t
The second is a pointer to module_t
, the first module
record.
- module_t is a struct with the following interesting fields
typedef struct module {
unsigned long mod_start;
unsigned long mod_end;
unsigned long string;
...
} module_t
You might find this
URL has some interesting information (and links) if you are really
interested in how executables are structured, or in the multiboot boot
process.
Return to: