0% found this document useful (0 votes)
23 views46 pages

12 xv6 Boot Process

The document provides an overview of the XV6 bootloader, detailing its compilation process, memory management, and the transition from real mode to protected mode in x86 architecture. It explains the bootloader's role in loading the operating system from a known location and the significance of segmentation and paging in memory management. Additionally, it covers the structure of ELF files and the process of loading the kernel into memory, including the handling of program headers and the setup of the Global Descriptor Table (GDT).

Uploaded by

ndipteofficial
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views46 pages

12 xv6 Boot Process

The document provides an overview of the XV6 bootloader, detailing its compilation process, memory management, and the transition from real mode to protected mode in x86 architecture. It explains the bootloader's role in loading the operating system from a known location and the significance of segmentation and paging in memory management. Additionally, it covers the structure of ELF files and the process of loading the kernel into memory, including the handling of program headers and the setup of the Global Descriptor Table (GDT).

Uploaded by

ndipteofficial
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 46

XV6 bootloader

Abhijit A. M.
[email protected]

Credits:
xv6 book by Cox, Kaashoek, Morris
Notes by Prof. Sorav Bansal
A word of caution
We begin reading xv6 code
But it’s not possible to read this code in a “linear
fashion”
The dependency between knowing OS concepts
and reading/writing a kernel that is written using all
concepts
What we have seen ....
Compilation process, calling conventions
Basics of Memory Management by OS
Basics of x86 architecture
Registers, segments, memory management unit,
addressing, some basic machine instructions,
ELF files
Objdump, program headers
Symbol tables
Boot-process
Bootloader itself
Is loaded by the BIOS at a fixed location in
memory and BIOS makes it run
Our job, as OS programmers, is to write the
bootloader code
Bootloader does
Pick up code of OS from a ‘known’ location and
loads it in memory
Makes the OS run
Xv6 bootloader: bootasm.S bootmain.c (see
bootloader
BIOS Runs (automatically)
Loads boot sector into RAM at 0x7c00
Starts executing that code
Make sure that your bootloader is loaded at 0x7c00
Makefile has
bootblock: bootblock.S bootmain.c
$(CC) $(CFLAGS) -fno-pic -nostdinc -I. -c bootasm.S
.....
...
Processor starts in real mode
Processor starts in real mode – works like 16 bit 8088
eight 16-bit general-purpose registers,
Segment registers %cs, %ds, %es, and %ss -->
additional bits necessary to generate 20-bit memory
addresses from 16-bit registers.
addr = seg << 4 + addr
Virtual ddress = offset Address

Effective memory translation in the beginning


At _start in bootasm.S:

%cs=0 %ip=7c00.

So effective address = 0*16+ip = ip


bootloader
First instruction is ‘cli’
disable interrupts
So that until your code loads all hardware interrupt
handlers, no interrupt will occur
Zeroing registers
# Zero data segment registers DS, ES, and SS.
zero ax and ds, es, ss
xorw %ax,%ax # Set %ax to zero

movw %ax,%ds # -> Data Segment BIOS did not put in


movw %ax,%es # -> Extra Segment anything perhaps
movw %ax,%ss # -> Stack Segment
Seg:off with 16 bit
A not so necessary detail segments can actually
Enable 21 bit address
address more than 20 bits
of memory. After
seta20.1:
0x100000 (=2^20), 8086
inb $0x64,%al wrapped addresses to 0.
# Wait for not busy
80286 introduced 21st bit
testb $0x2,%al of address. But older
jnz seta20.1 software required 20 bits
only. BIOS disabled 21st
movb $0xd1,%al
bit. Some OS needed 21st
# 0xd1 -> port 0x64
Bit. So enable it.
outb %al,$0x64
Write to Port 0x64 and
seta20.2: 0x60 -> keyboard
After this

Some instructions are run


to enter protected mode

And further code runs in protected mode


Real mode Vs protected mode
Real mode 16 bit registers
Protected mode
Enables segmentation + Paging both
No longer seg*16+offset calculations
Segment registers is index into segment descriptor
table. But segment:offset pairs continue
mov %esp, $32 # SS will be used with esp
More in next few slides
Other segment registers need to be explicitely
mentioned in instructions
X86 address : protected mode address
translation

Both Segmentation and Paging are used in x86


X86 allows optionally one-level or two-level paging
Segmentation is a must to setup, paging is optional (needs to be enabled)
Hence different OS can use segmentation+paging in different ways
X86 segmentation
Paging concept, hierarchical paging

Paging scheme
PTBR
X86 paging
Page Directory Entry (PDE)
Page Table Entry (PTE)
CR3

CR4
Segmentation + Paging

Selector value is implicit based on address being accessed.


Instruction: CS
Stack Variable: SS
Data: DS
etc.
Segmentation + Paging setup of xv6

xv6 configures the segmentation hardware by


setting Base = 0, Limit = 4 GB
translate logical to linear addresses without
change, so that they are always equal.
Segmentation is pratically off
Once paging is enabled, the only interesting
address mapping in the system will be linear to
physical.
In xv6 paging is NOT enabled while loading kernel
After kernel is loaded 4 MB pages are used for a
GDT Entry

asm.h
#define SEG_ASM(type,base,lim) \
.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
.byte (((base) >> 16) & 0xff), (0x90 | (type)), \
(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
Segment selector

Note in 16 bit mode, segment selector is 16 bit, here it’s 13 bit + 3 bits

EFLAGS register
lgdt gdtdesc lgdt
... load the processor’s
(GDT) register with the
# Bootstrap GDT
value gdtdesc which
.p2align 2 # force 4 byte points to the table gdt.
alignment
table gdt : The table has a
gdt: null entry, one entry for
SEG_NULLASM # executable code, and one
null seg entry to data.
all segments have a base
SEG_ASM(STA_X|STA_ address of zero and the
R, 0x0, 0xffffffff) # code maximum possible limit
seg The code segment
bootasm.S after “lgdt gdtdesc”
till jump to “entry”
Still
Logical Address = Physical addres

But with GDT in picture and


Logical Address = offset
Physical
Protected Mode operation
Address

During this time,

Loading kernel from ELF into p

Base Limit Permissions Addresses in “kernel” file transl

0 4GB Write

0 4GB Read, Execute

0 0 0

GDT
Prepare to enable protected mode
Prepare to enable movl %cr0, %eax
protected mode by orl $CR0_PE, %eax
setting the 1 bit
(CR0_PE) in register movl %eax, %cr0
%cr0
CR0

PG: Paging enabled or not WP: Write protecion on/off


PE: Protection Enabled --> protected mode.
Complete transition to 32 bit mode
ljmp $(SEG_KCODE<<3), $start32
Complete the transition
to 32-bit protected mode
by using a long jmp
to reload %cs (=1) and
%eip (=start32).
Note that ‘start32’ is the
address of next
instruction after ljmp.
Note: The segment
descriptors are set up
Jumping to “C” code
movw Setup Data, extra, stack
$(SEG_KDATA<<3), segment with
%ax # Our data SEG_KDATA (=2), FS &
segment selector GS (=0)
movw %ax, %ds Copy “$start” i.e. 7c00
# -> DS: Data Segment to stack-ptr
movw %ax, %es It will grow from 7c00 to
# -> ES: Extra Segment 0000
movw %ax, %ss Call bootmain() a C
# -> SS: Stack Segment function
Setup now

Logical Address = offset


Physical
Address

CS, SS, etc.

Base Limit Permissions


Selector

2 0 4GB Write

1 0 4GB Read, Execute


DS

0 0 0 0
SS

CS GDT
bootmain(): already in memory, as
part of ‘bootblock’
bootmain.c , expects to void
find a copy of the kernel bootmain(void)
executable on the disk
starting at the second {
sector (sector = 1). struct elfhdr *elf;
Why? struct proghdr *ph,
The kernel is an ELF *eph;
format binary void (*entry)(void);
Bootmain loads the first uchar* pa;
4096 bytes of the ELF
binary. It places the in-
bootmain()
Check if it’s really ELF // Is this an ELF
or not executable?
Next load kernel code if(elf->magic !=
from ELF file “kernel” ELF_MAGIC)
into memory return; // let
bootasm.S handle error
struct elfhdr {
ELF uint magic; // must
equal ELF_MAGIC
uchar elf[12];
ushort type;
ushort machine;
uint version;
uint entry;
uint phoff; // where is
program header table
uint shoff;
uint flags;
// Program header
ELF struct proghdr {
uint type; // Loadable
segment , Dynamic
linking information ,
Interpreter information ,
Thread-Local Storage
template , etc.
uint off; //Offset of the
segment in the file image.
uint vaddr; //Virtual
address of the segment in
memory.
kernel: Run ‘objdump
file format elf32-i386 -x -a kernel | head -15’ & see this
kernel Diff between mems
Code to be loaded at KERNBASE + KE
architecture: i386, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x0010000c

Program Header:
LOAD off 0x00001000 vaddr 0x80100000 paddr 0x00100000 align 2**12
filesz 0x0000a516 memsz 0x000154a8 flags rwx
STACK off 0x00000000 vaddr 0x00000000 paddr 0x00000000 align 2**4
filesz 0x00000000 memsz 0x00000000 flags rwx

Stack : everything zeroes


// Load each program segment (ignores ph flags).
Load code from ELF to memory
ph = (struct proghdr*)((uchar*)elf + elf->phoff);
eph = ph + elf->phnum;
// Abhijit: number of program headers
for(; ph < eph; ph++){
// Abhijit: iterate over each program header
pa = (uchar*)ph->paddr;
// Abhijit: the physical address to load program
/* Abhijit: read ph->filesz bytes, into 'pa',
from ph->off in kernel/disk */
readseg(pa, ph->filesz, ph->off);
if(ph->memsz > ph->filesz)
stosb(pa + ph->filesz, 0, ph->memsz - ph-
>filesz); // Zero the reminder section*/
Jump to Entry
// Call the entry point from the
ELF header.
// Does not return!
/* Abhijit:
* elf->entry was set by Linker
using kernel.ld
* This is address 0x80100000
specified in kernel.ld
* See kernel.asm for kernel
assembly code).
To understand
further
code

Remember: 4
MB pages
are possible
From entry: RAM
Till: inside main(), before kvmalloc()

Logical Address = offset


Linear Address

4MB

CS, SS, etc. 0


Base Limit Permissions
Selector

3 512 0 P,W,PS

2 0 4GB Write .

.
1 0 4GB Read, Execute
DS .
0 0 0 0
3
SS
GDT 2

CS 1
CR3
0 0 P,W,PS
entrypgdir
From entry: RAM
Till: inside main(), before kvmalloc()

Physical Addr

Logical Address = offset


Dir Offset

4MB

CS, SS, etc. 0

Base Limit Permissions


Selector

3 512 0 P,W,PS

0 4GB Write .
2
.
0 4GB Read, Execute
1
DS .
0 0 0
0 3
SS
GDT 2

1
CS Even now, every Logical address = Physical address, but through
CR3 Page dir
0 0 P,W,PS
entrypgdir
entrypgdir in main.c, is used by
entry()
__attribute__((__aligned__(PGSIZE)))
#define PTE_P 0x001 // Present
pde_t entrypgdir[NPDENTRIES] = {
#define PTE_W 0x002 // Writeable
#define PTE_U 0x004 // User
#define PTE_PS 0x080 // Page Size
// Map VA's [0, 4MB) to PA's [0, 4MB) #define PDXSHIFT 22 // offset of PDX in
[0] = (0) | PTE_P | PTE_W | PTE_PS,

// Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB). This is entry 512

[KERNBASE>>PDXSHIFT] = (0) | PTE_P | PTE_W | PTE_PS,

};

This is entry page directory during entry(), beginning of kernel


Mapping 0:0x400000 (i.e. 0: 4MB) to physical addresses 0:0x400000. is required as long
as entry is executing at low addresses, but will eventually be removed.
This mapping restricts the kernel instructions and data to 4 Mbytes.
entry() in entry.S
entry:
movl %cr4, %eax # Turn on page size
extension for 4Mbyte
orl $(CR4_PSE), pages
%eax
# Set page directory. 4
movl %eax, %cr4 MB pages (temporarily
movl only. More later)
$(V2P_WO(entrypgdir)), # Turn on paging.
%eax
# Set up the stack
movl %eax, %cr3 pointer.
movl %cr0, %eax # Jump to main(), and
More about entry()
movl $(V2P_WO(entrypgdir)), %eax
V2P is simple: substract
movl %eax, %cr3
0x80000000 i.e.
KERNBASE from
-> Here we use physical address
address using V2P_WO
because paging is not
turned on yet
More about entry()
movl %cr0, %eax But we have already set
orl 0’th entry in pgdir to
$(CR0_PG|CR0_WP), address 0
%eax So it still works!
movl %eax, %cr0

This turns on paging


After this also, entry() is
running and processor is
executing code at lower
entry()
# Set up the stack
movl $(stack + pointer.
KSTACKSIZE), %esp # Abhijit:
+KSTACKSIZE is done
mov $main, %eax
as stack grows
jmp *%eax downwards
.comm stack, # Jump to main(), and
KSTACKSIZE switch to executing at
# Abhijit: allocate here 'stack' of size = high addresses. The
KSTACKSIZE indirect call is needed
because the assembler
bootmasm.S bootmain.c: Steps
1) Starts in “real” mode, 16 bit mode. Does some
misc legacy work.
2) Runs instructions to do MMU set-up for
protected-mode & only segmentation (0-4GB,
identity mapping), changes to protected mode.
3) Reads kernel ELF file and loads it in RAM, as
per instructions in ELF file
4) Sets up paging (4 MB pages)
5) Runs main() of kernel
Code from bootasm.S bootmain.c is over!
Kernel is loaded.
Now kernel is going to prepare itself

You might also like