kernel/kernel.ld
About this file
The compiler turns each source file into a separate object file (.o) with its code and
data at made-up addresses starting from 0. The linker (ld) glues them into one program,
kernel/kernel, and this linker script tells it exactly where in memory each
part goes.
For an ordinary program those details are left to defaults. A kernel cannot leave them:
- its first instruction must be at
0x80000000, where QEMU jumps; - the trampoline code must fill exactly one page of its own, because the kernel maps that page into every process;
- C code needs to know where the code ends (
etext) and where free memory starts (end), and only the linker knows those addresses.
The resulting layout of the kernel in physical memory:
0x80000000 .text code (entry.S first), then the trampoline page
etext ← end of code, page-aligned
.rodata constants, string literals
.data initialized globals
.bss zero-initialized globals (e.g. stack0)
end ← first free byte; kalloc hands out pages from here up
Read next: kernel/entry.S.
What kind of program this is
Two settings for the whole output file: the machine it is for, and the address of its first instruction.
Mark the output file as RISC-V code (OUTPUT_ARCH).
Record _entry as the entry point in the ELF header of kernel/kernel
(ENTRY). Tools such as gdb and objdump read it from the header. QEMU
ignores it here: its boot ROM jumps to 0x80000000 regardless, which is why line 13 has
to make sure _entry is there.
Start at 0x80000000
SECTIONS { ... } (lines 4–45) describes the output file from the lowest address to the
highest. Line 10 sets the location counter . to 0x80000000, so the
first byte placed after it, the start of .text, lands there. That address is not
arbitrary: on QEMU’s virt machine RAM begins at 0x80000000, and QEMU’s boot ROM
sends every hart there. The comment on lines 6–9 says the same.
Set the location counter to 0x80000000. Everything below is placed at or
above this address.
Code: .text, entry.S first, the trampoline page last
This builds the output section .text, all of the kernel’s machine code, in three
parts:
- Lines 13–14: the code of every object file, with
kernel/entry.Sfirst so that_entryis at0x80000000. - Lines 15–19: the trampoline (
kernel/trampoline.S) on a page of its own. The kernel maps this one page at the same virtual address,TRAMPOLINE, in the kernel’s and every process’s page table (kernel/vm.c:47), which is why it must start on a page boundary and fit in one page. - Line 20: the symbol
etextmarks the end of all code. It is page-aligned, sokvmmakecan map everything below it read-and-execute and everything above it read-and-write, page by page (kernel/vm.c:39).
Begin the output section named .text. The linker places it at the current location
counter, 0x80000000.
This line makes kernel/entry.S come first in memory, but not in the way it appears.
The syntax file(name) means “the input section called name from this file”, and
entry.o has no section called _entry (_entry is a label; the code is in .text).
So the line selects nothing. What it does do is mention kernel/entry.o by name, which
makes the linker read that file before the object files listed on the command line.
The next line then copies .text sections in file order, so entry.o’s code lands
first, at 0x80000000.
You can check this. If this line is deleted and entry.o is moved to the end of the
link command, _entry ends up at 0x80005bb0, and the harts would start in whatever
function happened to be first instead. If
_entry here is replaced by any made-up name, nothing changes. (In the real Makefile,
$K/entry.o is also listed first in OBJS, so either one alone would do.)
Put every input file’s code here: sections named .text, and .text. followed by
anything (GCC sometimes creates sections like .text.startup). * means “from all input
files”.
Round up to a 4096-byte page boundary (no change if already on one; ALIGN). The trampoline must begin a page.
Name the current address, the start of the trampoline page, _trampoline. It is used
only by the check on line 19. C code finds the same address through the label
trampoline in kernel/trampoline.S, which is the first thing in this section.
Place the trampsec section here. kernel/trampoline.S:16 puts its code in a
section with this made-up name precisely so this line can single it out.
Round up to the next page boundary again, so that the trampoline page is padded to a full page and whatever follows starts on a fresh page.
A build-time check (ASSERT): after rounding up, the distance from
_trampoline must be exactly one page (0x1000 = 4096 bytes). If the trampoline code
ever grew past 4096 bytes, the distance would be 2 or more pages and the build would
stop with this message instead of producing a kernel that crashes mysteriously. (An
empty trampoline would fail the check too: the distance would be 0.)
Define etext (“end of text”) as the current, page-aligned address
(PROVIDE). C code declares it with extern char etext[];
(kernel/vm.c:16) to learn where the code ends.
End of the .text output section.
Constants: .rodata
Read-only data: string literals, const tables. .srodata holds small read-only
objects (small data sections (.sdata, .sbss, .srodata)); as the comment says, xv6 does not need them separate, so
both go into one output section.
Despite the name, the kernel’s page table maps this section writable: kvmmake maps
everything from etext to PHYSTOP read-write.
Begin the .rodata output section, right after the code.
Start the group that follows on a 16-byte boundary. (The linker also honors each input section’s own required alignment.)
Small read-only data from all files (.srodata). The comment notes it could be merged
with .rodata, which is what happens here.
Ordinary read-only data from all files.
Initialized variables: .data
Global and static variables with a non-zero starting value, for example
static int first = 1; in forkret. Their initial bytes are stored in the
kernel/kernel file and copied into memory by QEMU. .sdata is the small-object
version, merged in the same way as above.
Begin the .data output section.
Small initialized data (.sdata), merged as above.
Ordinary initialized data.
Zeroed variables: .bss
Global and static variables with no initializer, such as stack0, proc
(the process table) and cpus. C guarantees they start as zero. The file stores only
the size of this section, not its bytes.
xv6 never clears .bss itself: it relies on the memory being zero when the kernel
starts, which QEMU provides when it loads the kernel.
Begin the .bss output section.
Small zero-initialized data (.sbss).
Ordinary zero-initialized data.
Mark where free memory begins
After .bss, the location counter is at the first byte not used by the kernel image.
Line 44 names that address end. kinit (kernel/kalloc.c:30) gives every page
from end (rounded up to a page boundary) up to PHYSTOP to the page allocator.
Line 45 closes the SECTIONS block from line 4.
Define end, the first address after the whole kernel. kernel/kalloc.c:14 declares
extern char end[]; and uses it as the start of free memory.