A Tour Through Pintos
*********************

This chapter is a brief tour through the Pintos code.  It covers the entire code base, but you'll only be using Pintos one part at a time, so you may find that you want to read each part as you work on the corresponding project.

Loading
=======

This section covers the Pintos loader and basic kernel initialization.

The Loader
----------

The first part of Pintos that runs is the loader, in threads/loader.S.  The PC BIOS loads the loader into memory. The loader, in turn, is responsible for initializing the CPU, loading the rest of Pintos into memory, and then jumping to its start.  It's not important to understand exactly what the loader does, but if you're interested, read on.  You should probably read along with the loader's source.  You should also understand the basics of the 80X86 architecture as described by chapter 3 of IA32-v1.

Because the PC BIOS loads the loader, the loader has to play by the BIOS's rules.  In particular, the BIOS only loads 512 bytes (one disk sector) into memory.  This is a severe restriction and it means that, practically speaking, the loader has to be written in assembly language.

Pintos' loader first initializes the CPU.  The first important part of this is to enable the A20 line, that is, the CPU's address line numbered 20.  For historical reasons, PCs start out with this address line fixed at 0, which means that attempts to access memory beyond the first 1 MB (2 raised to the 20th power) will fail.  Pintos wants to access more memory than this, so we have to enable it.

Next, the loader asks the BIOS for the PC's memory size.  Again for historical reasons, the function that we call in the BIOS to do this can only detect up to 64 MB of RAM, so that's the practical limit that Pintos can support.  The memory size is stashed away in a location in the loader that the kernel can read after it boots.

Third, the loader creates a basic page table.  This page table maps the 64 MB at the base of virtual memory (starting at virtual address 0) directly to the identical physical addresses.  It also maps the same physical memory starting at virtual address LOADER_PHYS_BASE, which defaults to 0xc0000000 (3 GB).  The Pintos kernel only wants the latter mapping, but there's a chicken-and-egg problem if we don't include the former: our current virtual address is roughly 0x7c00, the location where the BIOS loaded us, and we can't jump to 0xc0007c00 until we turn on the page table, but if we turn on the page table without jumping there, then we've just pulled the rug out from under ourselves.  At any rate, it's necessary.

After the page table is initialized, we load the CPU's control registers to turn on protected mode and paging, and then we set up the segment registers.  We aren't equipped to handle interrupts in protected mode yet, so we disable interrupts.

Finally it's time to load the kernel from disk.  We choose a simple, although inflexible, method to do this: we program the IDE disk controller directly.  We assume that the kernel is stored starting from the second sector of the first IDE disk (the first sector contains the boot loader itself).  We also assume that the BIOS has already set up the IDE controller for us.  We read KERNEL_LOAD_PAGES 4 kB pages of data from the disk directly into virtual memory starting LOADER_KERN_BASE bytes past LOADER_PHYS_BASE, which by default means that we load the kernel starting 1 MB into physical memory.

Then we jump to the start of the compiled kernel image.  Using the ``linker script'' in threads/kernel.lds.S, the kernel has arranged that it begins with the assembly module threads/start.S.  This assembly module just calls main(), which never returns.

There's one more trick to the loader: the Pintos kernel command line is stored in the boot loader.  The pintos program actually modifies the boot loader on disk each time it runs the kernel, putting in whatever command line arguments the user supplies to the kernel, and then the kernel at boot time reads those arguments out of the boot loader in memory.  This is something of a nasty hack, but it is simple and effective.

Kernel Initialization
---------------------

The kernel proper starts with the main() function.  The main() function is written in C, as will be most of the code we encounter in Pintos from here on out.

When main() starts, the system is in a pretty raw state.  We're in protected mode with paging enabled, but hardly anything else is ready.  Thus, the main() function consists primarily of calls into other Pintos modules' initialization functions, which are typically named MODULE_init(), where MODULE is the module's name.

First we initialize kernel RAM in ram_init().  The first step is to clear out the kernel's so-called ``BSS'' segment.  The BSS is a segment that should be initialized to all zeros.  In C, whenever you declare a variable outside a function without providing an initializer, that variable goes into the BSS. (This isn't actually required by the ANSI C standard, but it is the case with most implementations of C on most machines.)  Because it's all zeros, the BSS isn't stored in the image that the loader brought into memory.  We just use memset() to zero it out.  The other task of ram_init() is to read out the machine's memory size from where the loader stored it and put it into the ram_pages variable for later use.

Threads
=======