summaryrefslogtreecommitdiff
path: root/doc/littleosbook.github.io.txt
diff options
context:
space:
mode:
Diffstat (limited to 'doc/littleosbook.github.io.txt')
-rw-r--r--doc/littleosbook.github.io.txt3261
1 files changed, 3261 insertions, 0 deletions
diff --git a/doc/littleosbook.github.io.txt b/doc/littleosbook.github.io.txt
new file mode 100644
index 0000000..4600b11
--- /dev/null
+++ b/doc/littleosbook.github.io.txt
@@ -0,0 +1,3261 @@
+The little book about OS development
+
+Erik Helin, Adam Renberg
+
+ [1]2015-01-19 | Commit: fe83e27dab3c39930354d2dea83f6d4ee2928212 [2]PDF
+ version
+
+Contents
+
+ * [3]1 Introduction
+ + [4]1.1 About the Book
+ + [5]1.2 The Reader
+ + [6]1.3 Credits, Thanks and Acknowledgements
+ + [7]1.4 Contributors
+ + [8]1.5 Changes and Corrections
+ + [9]1.6 Issues and where to get help
+ + [10]1.7 License
+ * [11]2 First Steps
+ + [12]2.1 Tools
+ o [13]2.1.1 Quick Setup
+ o [14]2.1.2 Programming Languages
+ o [15]2.1.3 Host Operating System
+ o [16]2.1.4 Build System
+ o [17]2.1.5 Virtual Machine
+ + [18]2.2 Booting
+ o [19]2.2.1 BIOS
+ o [20]2.2.2 The Bootloader
+ o [21]2.2.3 The Operating System
+ + [22]2.3 Hello Cafebabe
+ o [23]2.3.1 Compiling the Operating System
+ o [24]2.3.2 Linking the Kernel
+ o [25]2.3.3 Obtaining GRUB
+ o [26]2.3.4 Building an ISO Image
+ o [27]2.3.5 Running Bochs
+ + [28]2.4 Further Reading
+ * [29]3 Getting to C
+ + [30]3.1 Setting Up a Stack
+ + [31]3.2 Calling C Code From Assembly
+ o [32]3.2.1 Packing Structs
+ + [33]3.3 Compiling C Code
+ + [34]3.4 Build Tools
+ + [35]3.5 Further Reading
+ * [36]4 Output
+ + [37]4.1 Interacting with the Hardware
+ + [38]4.2 The Framebuffer
+ o [39]4.2.1 Writing Text
+ o [40]4.2.2 Moving the Cursor
+ o [41]4.2.3 The Driver
+ + [42]4.3 The Serial Ports
+ o [43]4.3.1 Configuring the Serial Port
+ o [44]4.3.2 Configuring the Line
+ o [45]4.3.3 Configuring the Buffers
+ o [46]4.3.4 Configuring the Modem
+ o [47]4.3.5 Writing Data to the Serial Port
+ o [48]4.3.6 Configuring Bochs
+ o [49]4.3.7 The Driver
+ + [50]4.4 Further Reading
+ * [51]5 Segmentation
+ + [52]5.1 Accessing Memory
+ + [53]5.2 The Global Descriptor Table (GDT)
+ + [54]5.3 Loading the GDT
+ + [55]5.4 Further Reading
+ * [56]6 Interrupts and Input
+ + [57]6.1 Interrupts Handlers
+ + [58]6.2 Creating an Entry in the IDT
+ + [59]6.3 Handling an Interrupt
+ + [60]6.4 Creating a Generic Interrupt Handler
+ + [61]6.5 Loading the IDT
+ + [62]6.6 Programmable Interrupt Controller (PIC)
+ + [63]6.7 Reading Input from the Keyboard
+ + [64]6.8 Further Reading
+ * [65]7 The Road to User Mode
+ + [66]7.1 Loading an External Program
+ o [67]7.1.1 GRUB Modules
+ + [68]7.2 Executing a Program
+ o [69]7.2.1 A Very Simple Program
+ o [70]7.2.2 Compiling
+ o [71]7.2.3 Finding the Program in Memory
+ o [72]7.2.4 Jumping to the Code
+ + [73]7.3 The Beginning of User Mode
+ * [74]8 A Short Introduction to Virtual Memory
+ + [75]8.1 Virtual Memory Through Segmentation?
+ + [76]8.2 Further Reading
+ * [77]9 Paging
+ + [78]9.1 Why Paging?
+ + [79]9.2 Paging in x86
+ o [80]9.2.1 Identity Paging
+ o [81]9.2.2 Enabling Paging
+ o [82]9.2.3 A Few Details
+ + [83]9.3 Paging and the Kernel
+ o [84]9.3.1 Reasons to Not Identity Map the Kernel
+ o [85]9.3.2 The Virtual Address for the Kernel
+ o [86]9.3.3 Placing the Kernel at 0xC0000000
+ o [87]9.3.4 Higher-half Linker Script
+ o [88]9.3.5 Entering the Higher Half
+ o [89]9.3.6 Running in the Higher Half
+ + [90]9.4 Virtual Memory Through Paging
+ + [91]9.5 Further Reading
+ * [92]10 Page Frame Allocation
+ + [93]10.1 Managing Available Memory
+ o [94]10.1.1 How Much Memory is There?
+ o [95]10.1.2 Managing Available Memory
+ + [96]10.2 How Can We Access a Page Frame?
+ + [97]10.3 A Kernel Heap
+ + [98]10.4 Further reading
+ * [99]11 User Mode
+ + [100]11.1 Segments for User Mode
+ + [101]11.2 Setting Up For User Mode
+ + [102]11.3 Entering User Mode
+ + [103]11.4 Using C for User Mode Programs
+ o [104]11.4.1 A C Library
+ + [105]11.5 Further Reading
+ * [106]12 File Systems
+ + [107]12.1 Why a File System?
+ + [108]12.2 A Simple Read-Only File System
+ + [109]12.3 Inodes and Writable File Systems
+ + [110]12.4 A Virtual File System
+ + [111]12.5 Further Reading
+ * [112]13 System Calls
+ + [113]13.1 Designing System Calls
+ + [114]13.2 Implementing System Calls
+ + [115]13.3 Further Reading
+ * [116]14 Multitasking
+ + [117]14.1 Creating New Processes
+ + [118]14.2 Cooperative Scheduling with Yielding
+ + [119]14.3 Preemptive Scheduling with Interrupts
+ o [120]14.3.1 Programmable Interval Timer
+ o [121]14.3.2 Separate Kernel Stacks for Processes
+ o [122]14.3.3 Difficulties with Preemptive Scheduling
+ + [123]14.4 Further Reading
+
+1 Introduction
+
+ This text is a practical guide to writing your own x86 operating
+ system. It is designed to give enough help with the technical details
+ while at the same time not reveal too much with samples and code
+ excerpts. We've tried to collect parts of the vast (and often
+ excellent) expanse of material and tutorials available, on the web and
+ otherwise, and add our own insights into the problems we encountered
+ and struggled with.
+
+ This book is not about the theory behind operating systems, or how any
+ specific operating system (OS) works. For OS theory we recommend the
+ book Modern Operating Systems by Andrew Tanenbaum [1]. Lists and
+ details on current operating systems are available on the Internet.
+
+ The starting chapters are quite detailed and explicit, to quickly get
+ you into coding. Later chapters give more of an outline of what is
+ needed, as more and more of the implementation and design becomes up to
+ the reader, who should now be more familiar with the world of kernel
+ development. At the end of some chapters there are links for further
+ reading, which might be interesting and give a deeper understanding of
+ the topics covered.
+
+ In [124]chapter 2 and [125]3 we set up our development environment and
+ boot up our OS kernel in a virtual machine, eventually starting to
+ write code in C. We continue in [126]chapter 4 with writing to the
+ screen and the serial port, and then we dive into segmentation in
+ [127]chapter 5 and interrupts and input in [128]chapter 6.
+
+ After this we have a quite functional but bare-bones OS kernel. In
+ [129]chapter 7 we start the road to user mode applications, with
+ virtual memory through paging ([130]chapter 8 and [131]9), memory
+ allocation ([132]chapter 10), and finally running a user application in
+ [133]chapter 11.
+
+ In the last three chapters we discuss the more advanced topics of file
+ systems ([134]chapter 12), system calls ([135]chapter 13), and
+ multitasking ([136]chapter 14).
+
+1.1 About the Book
+
+ The OS kernel and this book were produced as part of an advanced
+ individual course at the Royal Institute of Technology [2], Stockholm.
+ The authors had previously taken courses in OS theory, but had only
+ minor practical experience with OS kernel development. In order to get
+ more insight and a deeper understanding of how the theory from the
+ previous OS courses works out in practice, the authors decided to
+ create a new course, which focused on the development of a small OS.
+ Another goal of the course was writing a thorough tutorial on how to
+ develop a small OS basically from scratch, and this short book is the
+ result.
+
+ The x86 architecture is, and has been for a long time, one of the most
+ common hardware architectures. It was not a difficult choice to use the
+ x86 architecture as the target of the OS, with its large community,
+ extensive reference material and mature emulators. The documentation
+ and information surrounding the details of the hardware we had to work
+ with was not always easy to find or understand, despite (or perhaps due
+ to) the age of the architecture.
+
+ The OS was developed in about six weeks of full-time work. The
+ implementation was done in many small steps, and after each step the OS
+ was tested manually. By developing in this incremental and iterative
+ way, it was often easier to find any bugs that were introduced, since
+ only a small part of the code had changed since the last known good
+ state of the code. We encourage the reader to work in a similar way.
+
+ During the six weeks of development, almost every single line of code
+ was written by the authors together (this way of working is also called
+ pair-programming). It is our belief that we managed to avoid a lot of
+ bugs due to this style of development, but this is hard to prove
+ scientifically.
+
+1.2 The Reader
+
+ The reader of this book should be comfortable with UNIX/Linux, systems
+ programming, the C language and computer systems in general (such as
+ hexadecimal notation [3]). This book could be a way to get started
+ learning those things, but it will be more difficult, and developing an
+ operating system is already challenging on its own. Search engines and
+ other tutorials are often helpful if you get stuck.
+
+1.3 Credits, Thanks and Acknowledgements
+
+ We'd like to thank the OSDev community [4] for their great wiki and
+ helpful members, and James Malloy for his eminent kernel development
+ tutorial [5]. We'd also like to thank our supervisor Torbjörn Granlund
+ for his insightful questions and interesting discussions.
+
+ Most of the CSS formatting of the book is based on the work by Scott
+ Chacon for the book Pro Git, [137]http://progit.org/.
+
+1.4 Contributors
+
+ We are very grateful for the patches that people send us. The following
+ users have all contributed to this book:
+ * [138]alexschneider
+ * [139]Avidanborisov
+ * [140]nirs
+ * [141]kedarmhaswade
+ * [142]vamanea
+ * [143]ansjob
+
+1.5 Changes and Corrections
+
+ This book is hosted on Github - if you have any suggestions, comments
+ or corrections, just fork the book, write your changes, and send us a
+ pull request. We'll happily incorporate anything that makes this book
+ better.
+
+1.6 Issues and where to get help
+
+ If you run into problems while reading the book, please check the
+ issues on Github for help:
+ [144]https://github.com/littleosbook/littleosbook/issues.
+
+1.7 License
+
+ All content is under the Creative Commons Attribution Non Commercial
+ Share Alike 3.0 license,
+ [145]http://creativecommons.org/licenses/by-nc-sa/3.0/us/. The code
+ samples are in the public domain - use them however you want.
+ References to this book are always received with warmth.
+
+2 First Steps
+
+ Developing an operating system (OS) is no easy task, and the question
+ "How do I even begin to solve this problem?" is likely to come up
+ several times during the course of the project for different problems.
+ This chapter will help you set up your development environment and
+ booting a very small (and primitive) operating system.
+
+2.1 Tools
+
+2.1.1 Quick Setup
+
+ We (the authors) have used Ubuntu [6] as the operating system for doing
+ OS development, running it both physically and virtually (using the
+ virtual machine VirtualBox [7]). A quick way to get everything up and
+ running is to use the same setup as we did, since we know that these
+ tools work with the samples provided in this book.
+
+ Once Ubuntu is installed, either physical or virtual, the following
+ packages should be installed using apt-get:
+ sudo apt-get install build-essential nasm genisoimage bochs bochs-sdl
+
+2.1.2 Programming Languages
+
+ The operating system will be developed using the C programming language
+ [8][9], using GCC [10]. We use C because developing an OS requires a
+ very precise control of the generated code and direct access to memory.
+ Other languages that provide the same features can also be used, but
+ this book will only cover C.
+
+ The code will make use of one type attribute that is specific for GCC:
+ __attribute__((packed))
+
+ This attribute allows us to ensure that the compiler uses a memory
+ layout for a struct exactly as we define it in the code. This is
+ explained in more detail in the next chapter.
+
+ Due to this attribute, the example code might be hard to compile using
+ a C compiler other than GCC.
+
+ For writing assembly code, we have chosen NASM [11] as the assembler,
+ since we prefer NASM's syntax over GNU Assembler.
+
+ Bash [12] will be used as the scripting language throughout the book.
+
+2.1.3 Host Operating System
+
+ All the code examples assumes that the code is being compiled on a UNIX
+ like operating system. All code examples have been successfully
+ compiled using Ubuntu [6] versions 11.04 and 11.10.
+
+2.1.4 Build System
+
+ Make [13] has been used when constructing the Makefile examples.
+
+2.1.5 Virtual Machine
+
+ When developing an OS it is very convenient to be able to run your code
+ in a virtual machine instead of on a physical computer, since starting
+ your OS in a virtual machine is much faster than getting your OS onto a
+ physical medium and then running it on a physical machine. Bochs [14]
+ is an emulator for the x86 (IA-32) platform which is well suited for OS
+ development due to its debugging features. Other popular choices are
+ QEMU [15] and VirtualBox [7]. This book uses Bochs.
+
+ By using a virtual machine we cannot ensure that our OS works on real,
+ physical hardware. The environment simulated by the virtual machine is
+ designed to be very similar to their physical counterparts, and the OS
+ can be tested on one by just copying the executable to a CD and finding
+ a suitable machine.
+
+2.2 Booting
+
+ Booting an operating system consists of transferring control along a
+ chain of small programs, each one more "powerful" than the previous
+ one, where the operating system is the last "program". See the
+ following figure for an example of the boot process:
+ An example of the boot process. Each box is a program.
+
+ An example of the boot process. Each box is a program.
+
+2.2.1 BIOS
+
+ When the PC is turned on, the computer will start a small program that
+ adheres to the Basic Input Output System (BIOS) [16] standard. This
+ program is usually stored on a read only memory chip on the motherboard
+ of the PC. The original role of the BIOS program was to export some
+ library functions for printing to the screen, reading keyboard input
+ etc. Modern operating systems do not use the BIOS' functions, they use
+ drivers that interact directly with the hardware, bypassing the BIOS.
+ Today, BIOS mainly runs some early diagnostics (power-on-self-test) and
+ then transfers control to the bootloader.
+
+2.2.2 The Bootloader
+
+ The BIOS program will transfer control of the PC to a program called a
+ bootloader. The bootloader's task is to transfer control to us, the
+ operating system developers, and our code. However, due to some
+ restrictions[146]^1 of the hardware and because of backward
+ compatibility, the bootloader is often split into two parts: the first
+ part of the bootloader will transfer control to the second part, which
+ finally gives control of the PC to the operating system.
+
+ Writing a bootloader involves writing a lot of low-level code that
+ interacts with the BIOS. Therefore, an existing bootloader will be
+ used: the GNU GRand Unified Bootloader (GRUB) [17].
+
+ Using GRUB, the operating system can be built as an ordinary ELF [18]
+ executable, which will be loaded by GRUB into the correct memory
+ location. The compilation of the kernel requires that the code is laid
+ out in memory in a specific way (how to compile the kernel will be
+ discussed later in this chapter).
+
+2.2.3 The Operating System
+
+ GRUB will transfer control to the operating system by jumping to a
+ position in memory. Before the jump, GRUB will look for a magic number
+ to ensure that it is actually jumping to an OS and not some random
+ code. This magic number is part of the multiboot specification [19]
+ which GRUB adheres to. Once GRUB has made the jump, the OS has full
+ control of the computer.
+
+2.3 Hello Cafebabe
+
+ This section will describe how to implement of the smallest possible OS
+ that can be used together with GRUB. The only thing the OS will do is
+ write 0xCAFEBABE to the eax register (most people would probably not
+ even call this an OS).
+
+2.3.1 Compiling the Operating System
+
+ This part of the OS has to be written in assembly code, since C
+ requires a stack, which isn't available (the chapter [147]"Getting to
+ C" describes how to set one up). Save the following code in a file
+ called loader.s:
+ global loader ; the entry symbol for ELF
+
+ MAGIC_NUMBER equ 0x1BADB002 ; define the magic number constant
+ FLAGS equ 0x0 ; multiboot flags
+ CHECKSUM equ -MAGIC_NUMBER ; calculate the checksum
+ ; (magic number + checksum + flags should eq
+ual 0)
+
+ section .text: ; start of the text (code) section
+ align 4 ; the code must be 4 byte aligned
+ dd MAGIC_NUMBER ; write the magic number to the machine code
+,
+ dd FLAGS ; the flags,
+ dd CHECKSUM ; and the checksum
+
+ loader: ; the loader label (defined as entry point i
+n linker script)
+ mov eax, 0xCAFEBABE ; place the number 0xCAFEBABE in the registe
+r eax
+ .loop:
+ jmp .loop ; loop forever
+
+ The only thing this OS will do is write the very specific number
+ 0xCAFEBABE to the eax register. It is very unlikely that the number
+ 0xCAFEBABE would be in the eax register if the OS did not put it there.
+
+ The file loader.s can be compiled into a 32 bits ELF [18] object file
+ with the following command:
+ nasm -f elf32 loader.s
+
+2.3.2 Linking the Kernel
+
+ The code must now be linked to produce an executable file, which
+ requires some extra thought compared to when linking most programs. We
+ want GRUB to load the kernel at a memory address larger than or equal
+ to 0x00100000 (1 megabyte (MB)), because addresses lower than 1 MB are
+ used by GRUB itself, BIOS and memory-mapped I/O. Therefore, the
+ following linker script is needed (written for GNU LD [20]):
+ENTRY(loader) /* the name of the entry label */
+
+SECTIONS {
+ . = 0x00100000; /* the code should be loaded at 1 MB */
+
+ .text ALIGN (0x1000) : /* align at 4 KB */
+ {
+ *(.text) /* all text sections from all files */
+ }
+
+ .rodata ALIGN (0x1000) : /* align at 4 KB */
+ {
+ *(.rodata*) /* all read-only data sections from all files */
+ }
+
+ .data ALIGN (0x1000) : /* align at 4 KB */
+ {
+ *(.data) /* all data sections from all files */
+ }
+
+ .bss ALIGN (0x1000) : /* align at 4 KB */
+ {
+ *(COMMON) /* all COMMON sections from all files */
+ *(.bss) /* all bss sections from all files */
+ }
+}
+
+ Save the linker script into a file called link.ld. The executable can
+ now be linked with the following command:
+ ld -T link.ld -melf_i386 loader.o -o kernel.elf
+
+ The final executable will be called kernel.elf.
+
+2.3.3 Obtaining GRUB
+
+ The GRUB version we will use is GRUB Legacy, since the OS ISO image can
+ then be generated on systems using both GRUB Legacy and GRUB 2. More
+ specifically, the GRUB Legacy stage2_eltorito bootloader will be used.
+ This file can be built from GRUB 0.97 by downloading the source from
+ [148]ftp://alpha.gnu.org/gnu/grub/grub-0.97.tar.gz. However, the
+ configure script doesn't work well with Ubuntu [21], so the binary file
+ can be downloaded from
+ [149]http://littleosbook.github.com/files/stage2_eltorito. Copy the
+ file stage2_eltorito to the folder that already contains loader.s and
+ link.ld.
+
+2.3.4 Building an ISO Image
+
+ The executable must be placed on a media that can be loaded by a
+ virtual or physical machine. In this book we will use ISO [22] image
+ files as the media, but one can also use floppy images, depending on
+ what the virtual or physical machine supports.
+
+ We will create the kernel ISO image with the program genisoimage. A
+ folder must first be created that contains the files that will be on
+ the ISO image. The following commands create the folder and copy the
+ files to their correct places:
+ mkdir -p iso/boot/grub # create the folder structure
+ cp stage2_eltorito iso/boot/grub/ # copy the bootloader
+ cp kernel.elf iso/boot/ # copy the kernel
+
+ A configuration file menu.lst for GRUB must be created. This file tells
+ GRUB where the kernel is located and configures some options:
+ default=0
+ timeout=0
+
+ title os
+ kernel /boot/kernel.elf
+
+ Place the file menu.lst in the folder iso/boot/grub/. The contents of
+ the iso folder should now look like the following figure:
+ iso
+ |-- boot
+ |-- grub
+ | |-- menu.lst
+ | |-- stage2_eltorito
+ |-- kernel.elf
+
+ The ISO image can then be generated with the following command:
+ genisoimage -R \
+ -b boot/grub/stage2_eltorito \
+ -no-emul-boot \
+ -boot-load-size 4 \
+ -A os \
+ -input-charset utf8 \
+ -quiet \
+ -boot-info-table \
+ -o os.iso \
+ iso
+
+ For more information about the flags used in the command, see the
+ manual for genisoimage.
+
+ The ISO image os.iso now contains the kernel executable, the GRUB
+ bootloader and the configuration file.
+
+2.3.5 Running Bochs
+
+ Now we can run the OS in the Bochs emulator using the os.iso ISO image.
+ Bochs needs a configuration file to start and an example of a simple
+ configuration file is given below:
+ megs: 32
+ display_library: sdl
+ romimage: file=/usr/share/bochs/BIOS-bochs-latest
+ vgaromimage: file=/usr/share/bochs/VGABIOS-lgpl-latest
+ ata0-master: type=cdrom, path=os.iso, status=inserted
+ boot: cdrom
+ log: bochslog.txt
+ clock: sync=realtime, time0=local
+ cpu: count=1, ips=1000000
+
+ You might need to change the path to romimage and vgaromimage depending
+ on how you installed Bochs. More information about the Bochs config
+ file can be found at Boch's website [23].
+
+ If you saved the configuration in a file named bochsrc.txt then you can
+ run Bochs with the following command:
+ bochs -f bochsrc.txt -q
+
+ The flag -f tells Bochs to use the given configuration file and the
+ flag -q tells Bochs to skip the interactive start menu. You should now
+ see Bochs starting and displaying a console with some information from
+ GRUB on it.
+
+ After quitting Bochs, display the log produced by Boch:
+ cat bochslog.txt
+
+ You should now see the contents of the registers of the CPU simulated
+ by Bochs somewhere in the output. If you find RAX=00000000CAFEBABE or
+ EAX=CAFEBABE (depending on if you are running Bochs with or without 64
+ bit support) in the output then your OS has successfully booted!
+
+2.4 Further Reading
+
+ * Gustavo Duertes has written an in-depth article about what actually
+ happens when a x86 computer boots up,
+ [150]http://duartes.org/gustavo/blog/post/how-computers-boot-up
+ * Gustavo continues to describe what the kernel does in the very
+ early stages at
+ [151]http://duartes.org/gustavo/blog/post/kernel-boot-process
+ * The OSDev wiki also contains a nice article about booting an x86
+ computer: [152]http://wiki.osdev.org/Boot_Sequence
+
+3 Getting to C
+
+ This chapter will show you how to use C instead of assembly code as the
+ programming language for the OS. Assembly is very good for interacting
+ with the CPU and enables maximum control over every aspect of the code.
+ However, at least for the authors, C is a much more convenient language
+ to use. Therefore, we would like to use C as much as possible and use
+ assembly code only where it make sense.
+
+3.1 Setting Up a Stack
+
+ One prerequisite for using C is a stack, since all non-trivial C
+ programs use a stack. Setting up a stack is not harder than to make the
+ esp register point to the end of an area of free memory (remember that
+ the stack grows towards lower addresses on the x86) that is correctly
+ aligned (alignment on 4 bytes is recommended from a performance
+ perspective).
+
+ We could point esp to a random area in memory since, so far, the only
+ thing in the memory is GRUB, BIOS, the OS kernel and some memory-mapped
+ I/O. This is not a good idea - we don't know how much memory is
+ available or if the area esp would point to is used by something else.
+ A better idea is to reserve a piece of uninitialized memory in the bss
+ section in the ELF file of the kernel. It is better to use the bss
+ section instead of the data section to reduce the size of the OS
+ executable. Since GRUB understands ELF, GRUB will allocate any memory
+ reserved in the bss section when loading the OS.
+
+ The NASM pseudo-instruction resb [24] can be used to declare
+ uninitialized data:
+ KERNEL_STACK_SIZE equ 4096 ; size of stack in bytes
+
+ section .bss
+ align 4 ; align at 4 bytes
+ kernel_stack: ; label points to beginning of m
+emory
+ resb KERNEL_STACK_SIZE ; reserve stack for the kernel
+
+ There is no need to worry about the use of uninitialized memory for the
+ stack, since it is not possible to read a stack location that has not
+ been written (without manual pointer fiddling). A (correct) program can
+ not pop an element from the stack without having pushed an element onto
+ the stack first. Therefore, the memory locations of the stack will
+ always be written to before they are being read.
+
+ The stack pointer is then set up by pointing esp to the end of the
+ kernel_stack memory:
+ mov esp, kernel_stack + KERNEL_STACK_SIZE ; point esp to the start of the
+ ; stack (end of memory area)
+
+3.2 Calling C Code From Assembly
+
+ The next step is to call a C function from assembly code. There are
+ many different conventions for how to call C code from assembly code
+ [25]. This book uses the cdecl calling convention, since that is the
+ one used by GCC. The cdecl calling convention states that arguments to
+ a function should be passed via the stack (on x86). The arguments of
+ the function should be pushed on the stack in a right-to-left order,
+ that is, you push the rightmost argument first. The return value of the
+ function is placed in the eax register. The following code shows an
+ example:
+ /* The C function */
+ int sum_of_three(int arg1, int arg2, int arg3)
+ {
+ return arg1 + arg2 + arg3;
+ }
+ ; The assembly code
+ external sum_of_three ; the function sum_of_three is defined elsewhere
+
+ push dword 3 ; arg3
+ push dword 2 ; arg2
+ push dword 1 ; arg1
+ call sum_of_three ; call the function, the result will be in eax
+
+3.2.1 Packing Structs
+
+ In the rest of this book, you will often come across "configuration
+ bytes" that are a collection of bits in a very specific order. Below
+ follows an example with 32 bits:
+Bit: | 31 24 | 23 8 | 7 0 |
+Content: | index | address | config |
+
+ Instead of using an unsigned integer, unsigned int, for handling such
+ configurations, it is much more convenient to use "packed structures":
+ struct example {
+ unsigned char config; /* bit 0 - 7 */
+ unsigned short address; /* bit 8 - 23 */
+ unsigned char index; /* bit 24 - 31 */
+ };
+
+ When using the struct in the previous example there is no guarantee
+ that the size of the struct will be exactly 32 bits - the compiler can
+ add some padding between elements for various reasons, for example to
+ speed up element access or due to requirements set by the hardware
+ and/or compiler. When using a struct to represent configuration bytes,
+ it is very important that the compiler does not add any padding,
+ because the struct will eventually be treated as a 32 bit unsigned
+ integer by the hardware. The attribute packed can be used to force GCC
+ to not add any padding:
+ struct example {
+ unsigned char config; /* bit 0 - 7 */
+ unsigned short address; /* bit 8 - 23 */
+ unsigned char index; /* bit 24 - 31 */
+ } __attribute__((packed));
+
+ Note that __attribute__((packed)) is not part of the C standard - it
+ might not work with all C compilers.
+
+3.3 Compiling C Code
+
+ When compiling the C code for the OS, a lot of flags to GCC need to be
+ used. This is because the C code should not assume the presence of a
+ standard library, since there is no standard library available for our
+ OS. For more information about the flags, see the GCC manual.
+
+ The flags used for compiling the C code are:
+ -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles
+ -nodefaultlibs
+
+ As always when writing C programs we recommend turning on all warnings
+ and treat warnings as errors:
+ -Wall -Wextra -Werror
+
+ You can now create a function kmain in a file called kmain.c that you
+ call from loader.s. At this point, kmain probably won't need any
+ arguments (but in later chapters it will).
+
+3.4 Build Tools
+
+ Now is also probably a good time to set up some build tools to make it
+ easier to compile and test-run the OS. We recommend using make [13],
+ but there are plenty of other build systems available. A simple
+ Makefile for the OS could look like the following example:
+ OBJECTS = loader.o kmain.o
+ CC = gcc
+ CFLAGS = -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector \
+ -nostartfiles -nodefaultlibs -Wall -Wextra -Werror -c
+ LDFLAGS = -T link.ld -melf_i386
+ AS = nasm
+ ASFLAGS = -f elf
+
+ all: kernel.elf
+
+ kernel.elf: $(OBJECTS)
+ ld $(LDFLAGS) $(OBJECTS) -o kernel.elf
+
+ os.iso: kernel.elf
+ cp kernel.elf iso/boot/kernel.elf
+ genisoimage -R \
+ -b boot/grub/stage2_eltorito \
+ -no-emul-boot \
+ -boot-load-size 4 \
+ -A os \
+ -input-charset utf8 \
+ -quiet \
+ -boot-info-table \
+ -o os.iso \
+ iso
+
+ run: os.iso
+ bochs -f bochsrc.txt -q
+
+ %.o: %.c
+ $(CC) $(CFLAGS) $< -o $@
+
+ %.o: %.s
+ $(AS) $(ASFLAGS) $< -o $@
+
+ clean:
+ rm -rf *.o kernel.elf os.iso
+
+ The contents of your working directory should now look like the
+ following figure:
+ .
+ |-- bochsrc.txt
+ |-- iso
+ | |-- boot
+ | |-- grub
+ | |-- menu.lst
+ | |-- stage2_eltorito
+ |-- kmain.c
+ |-- loader.s
+ |-- Makefile
+
+ You should now be able to start the OS with the simple command make
+ run, which will compile the kernel and boot it up in Bochs (as defined
+ in the Makefile above).
+
+3.5 Further Reading
+
+ * Kernigan & Richie's book, The C Programming Language, Second
+ Edition, [8] is great for learning about all the aspects of C.
+
+4 Output
+
+ This chapter will present how to display text on the console as well as
+ writing data to the serial port. Furthermore, we will create our first
+ driver, that is, code that acts as a layer between the kernel and the
+ hardware, providing a higher abstraction than communicating directly
+ with the hardware. The first part of this chapter is about creating a
+ driver for the framebuffer [26] to be able to display text on the
+ console. The second part shows how to create a driver for the serial
+ port. Bochs can store output from the serial port in a file,
+ effectively creating a logging mechanism for the operating system.
+
+4.1 Interacting with the Hardware
+
+ There are usually two different ways to interact with the hardware,
+ memory-mapped I/O and I/O ports.
+
+ If the hardware uses memory-mapped I/O then you can write to a specific
+ memory address and the hardware will be updated with the new data. One
+ example of this is the framebuffer, which will be discussed in more
+ detail later. For example, if you write the value 0x410F to address
+ 0x000B8000, you will see the letter A in white color on a black
+ background (see the section on [153]the framebuffer for more details).
+
+ If the hardware uses I/O ports then the assembly code instructions out
+ and in must be used to communicate with the hardware. The instruction
+ out takes two parameters: the address of the I/O port and the data to
+ send. The instruction in takes a single parameter, the address of the
+ I/O port, and returns data from the hardware. One can think of I/O
+ ports as communicating with hardware the same way as you communicate
+ with a server using sockets. The cursor (the blinking rectangle) of the
+ framebuffer is one example of hardware controlled via I/O ports on a
+ PC.
+
+4.2 The Framebuffer
+
+ The framebuffer is a hardware device that is capable of displaying a
+ buffer of memory on the screen [26]. The framebuffer has 80 columns and
+ 25 rows, and the row and column indices start at 0 (so rows are
+ labelled 0 - 24).
+
+4.2.1 Writing Text
+
+ Writing text to the console via the framebuffer is done with
+ memory-mapped I/O. The starting address of the memory-mapped I/O for
+ the framebuffer is 0x000B8000 [27]. The memory is divided into 16 bit
+ cells, where the 16 bits determine both the character, the foreground
+ color and the background color. The highest eight bits is the ASCII
+ [28] value of the character, bit 7 - 4 the background and bit 3 - 0 the
+ foreground, as can be seen in the following figure:
+Bit: | 15 14 13 12 11 10 9 8 | 7 6 5 4 | 3 2 1 0 |
+Content: | ASCII | FG | BG |
+
+ The available colors are shown in the following table:
+ Color Value Color Value Color Value Color Value
+ Black 0 Red 4 Dark grey 8 Light red 12
+ Blue 1 Magenta 5 Light blue 9 Light magenta 13
+ Green 2 Brown 6 Light green 10 Light brown 14
+ Cyan 3 Light grey 7 Light cyan 11 White 15
+
+ The first cell corresponds to row zero, column zero on the console.
+ Using an ASCII table, one can see that A corresponds to 65 or 0x41.
+ Therefore, to write the character A with a green foreground (2) and
+ dark grey background (8) at place (0,0), the following assembly code
+ instruction is used:
+ mov [0x000B8000], 0x4128
+
+ The second cell then corresponds to row zero, column one and its
+ address is therefore:
+ 0x000B8000 + 16 = 0x000B8010
+
+ Writing to the framebuffer can also be done in C by treating the
+ address 0x000B8000 as a char pointer, char *fb = (char *) 0x000B8000.
+ Then, writing A at place (0,0) with green foreground and dark grey
+ background becomes:
+ fb[0] = 'A';
+ fb[1] = 0x28;
+
+ The following code shows how this can be wrapped into a function:
+ /** fb_write_cell:
+ * Writes a character with the given foreground and background to position
+i
+ * in the framebuffer.
+ *
+ * @param i The location in the framebuffer
+ * @param c The character
+ * @param fg The foreground color
+ * @param bg The background color
+ */
+ void fb_write_cell(unsigned int i, char c, unsigned char fg, unsigned char b
+g)
+ {
+ fb[i] = c;
+ fb[i + 1] = ((fg & 0x0F) << 4) | (bg & 0x0F)
+ }
+
+ The function can then be used as follows:
+ #define FB_GREEN 2
+ #define FB_DARK_GREY 8
+
+ fb_write_cell(0, 'A', FB_GREEN, FB_DARK_GREY);
+
+4.2.2 Moving the Cursor
+
+ Moving the cursor of the framebuffer is done via two different I/O
+ ports. The cursor's position is determined with a 16 bits integer: 0
+ means row zero, column zero; 1 means row zero, column one; 80 means row
+ one, column zero and so on. Since the position is 16 bits large, and
+ the out assembly code instruction argument is 8 bits, the position must
+ be sent in two turns, first 8 bits then the next 8 bits. The
+ framebuffer has two I/O ports, one for accepting the data, and one for
+ describing the data being received. Port 0x3D4 [29] is the port that
+ describes the data and port 0x3D5 [29] is for the data itself.
+
+ To set the cursor at row one, column zero (position 80 = 0x0050), one
+ would use the following assembly code instructions:
+ out 0x3D4, 14 ; 14 tells the framebuffer to expect the highest 8 bits o
+f the position
+ out 0x3D5, 0x00 ; sending the highest 8 bits of 0x0050
+ out 0x3D4, 15 ; 15 tells the framebuffer to expect the lowest 8 bits of
+ the position
+ out 0x3D5, 0x50 ; sending the lowest 8 bits of 0x0050
+
+ The out assembly code instruction can't be executed directly in C.
+ Therefore it is a good idea to wrap out in a function in assembly code
+ which can be accessed from C via the cdecl calling standard [25]:
+ global outb ; make the label outb visible outside this file
+
+ ; outb - send a byte to an I/O port
+ ; stack: [esp + 8] the data byte
+ ; [esp + 4] the I/O port
+ ; [esp ] return address
+ outb:
+ mov al, [esp + 8] ; move the data to be sent into the al register
+ mov dx, [esp + 4] ; move the address of the I/O port into the dx regi
+ster
+ out dx, al ; send the data to the I/O port
+ ret ; return to the calling function
+
+ By storing this function in a file called io.s and also creating a
+ header io.h, the out assembly code instruction can be conveniently
+ accessed from C:
+ #ifndef INCLUDE_IO_H
+ #define INCLUDE_IO_H
+
+ /** outb:
+ * Sends the given data to the given I/O port. Defined in io.s
+ *
+ * @param port The I/O port to send the data to
+ * @param data The data to send to the I/O port
+ */
+ void outb(unsigned short port, unsigned char data);
+
+ #endif /* INCLUDE_IO_H */
+
+ Moving the cursor can now be wrapped in a C function:
+ #include "io.h"
+
+ /* The I/O ports */
+ #define FB_COMMAND_PORT 0x3D4
+ #define FB_DATA_PORT 0x3D5
+
+ /* The I/O port commands */
+ #define FB_HIGH_BYTE_COMMAND 14
+ #define FB_LOW_BYTE_COMMAND 15
+
+ /** fb_move_cursor:
+ * Moves the cursor of the framebuffer to the given position
+ *
+ * @param pos The new position of the cursor
+ */
+ void fb_move_cursor(unsigned short pos)
+ {
+ outb(FB_COMMAND_PORT, FB_HIGH_BYTE_COMMAND);
+ outb(FB_DATA_PORT, ((pos >> 8) & 0x00FF));
+ outb(FB_COMMAND_PORT, FB_LOW_BYTE_COMMAND);
+ outb(FB_DATA_PORT, pos & 0x00FF);
+ }
+
+4.2.3 The Driver
+
+ The driver should provide an interface that the rest of the code in the
+ OS will use for interacting with the framebuffer. There is no right or
+ wrong in what functionality the interface should provide, but a
+ suggestion is to have a write function with the following declaration:
+ int write(char *buf, unsigned int len);
+
+ The write function writes the contents of the buffer buf of length len
+ to the screen. The write function should automatically advance the
+ cursor after a character has been written and scroll the screen if
+ necessary.
+
+4.3 The Serial Ports
+
+ The serial port [30] is an interface for communicating between hardware
+ devices and although it is available on almost all motherboards, it is
+ seldom exposed to the user in the form of a DE-9 connector nowadays.
+ The serial port is easy to use, and, more importantly, it can be used
+ as a logging utility in Bochs. If a computer has support for a serial
+ port, then it usually has support for multiple serial ports, but we
+ will only make use of one of the ports. This is because we will only
+ use the serial ports for logging. Furthermore, we will only use the
+ serial ports for output, not input. The serial ports are completely
+ controlled via I/O ports.
+
+4.3.1 Configuring the Serial Port
+
+ The first data that need to be sent to the serial port is configuration
+ data. In order for two hardware devices to be able to talk to each
+ other they must agree upon a couple of things. These things include:
+ * The speed used for sending data (bit or baud rate)
+ * If any error checking should be used for the data (parity bit, stop
+ bits)
+ * The number of bits that represent a unit of data (data bits)
+
+4.3.2 Configuring the Line
+
+ Configuring the line means to configure how data is being sent over the
+ line. The serial port has an I/O port, the line command port, that is
+ used for configuration.
+
+ First the speed for sending data will be set. The serial port has an
+ internal clock that runs at 115200 Hz. Setting the speed means sending
+ a divisor to the serial port, for example sending 2 results in a speed
+ of 115200 / 2 = 57600 Hz.
+
+ The divisor is a 16 bit number but we can only send 8 bits at a time.
+ We must therefore send an instruction telling the serial port to first
+ expect the highest 8 bits, then the lowest 8 bits. This is done by
+ sending 0x80 to the line command port. An example is shown below:
+ #include "io.h" /* io.h is implement in the section "Moving the cursor" */
+
+ /* The I/O ports */
+
+ /* All the I/O ports are calculated relative to the data port. This is becau
+se
+ * all serial ports (COM1, COM2, COM3, COM4) have their ports in the same
+ * order, but they start at different values.
+ */
+
+ #define SERIAL_COM1_BASE 0x3F8 /* COM1 base port */
+
+ #define SERIAL_DATA_PORT(base) (base)
+ #define SERIAL_FIFO_COMMAND_PORT(base) (base + 2)
+ #define SERIAL_LINE_COMMAND_PORT(base) (base + 3)
+ #define SERIAL_MODEM_COMMAND_PORT(base) (base + 4)
+ #define SERIAL_LINE_STATUS_PORT(base) (base + 5)
+
+ /* The I/O port commands */
+
+ /* SERIAL_LINE_ENABLE_DLAB:
+ * Tells the serial port to expect first the highest 8 bits on the data port
+,
+ * then the lowest 8 bits will follow
+ */
+ #define SERIAL_LINE_ENABLE_DLAB 0x80
+
+ /** serial_configure_baud_rate:
+ * Sets the speed of the data being sent. The default speed of a serial
+ * port is 115200 bits/s. The argument is a divisor of that number, hence
+ * the resulting speed becomes (115200 / divisor) bits/s.
+ *
+ * @param com The COM port to configure
+ * @param divisor The divisor
+ */
+ void serial_configure_baud_rate(unsigned short com, unsigned short divisor)
+ {
+ outb(SERIAL_LINE_COMMAND_PORT(com),
+ SERIAL_LINE_ENABLE_DLAB);
+ outb(SERIAL_DATA_PORT(com),
+ (divisor >> 8) & 0x00FF);
+ outb(SERIAL_DATA_PORT(com),
+ divisor & 0x00FF);
+ }
+
+ The way that data should be sent must be configured. This is also done
+ via the line command port by sending a byte. The layout of the 8 bits
+ looks like the following:
+Bit: | 7 | 6 | 5 4 3 | 2 | 1 0 |
+Content: | d | b | prty | s | dl |
+
+ A description for each name can be found in the table below (and in
+ [31]):
+ Name Description
+ d Enables (d = 1) or disables (d = 0) DLAB
+ b If break control is enabled (b = 1) or disabled (b = 0)
+ prty The number of parity bits to use
+ s The number of stop bits to use (s = 0 equals 1, s = 1 equals 1.5 or
+ 2)
+ dl Describes the length of the data
+
+ We will use the mostly standard value 0x03 [31], meaning a length of 8
+ bits, no parity bit, one stop bit and break control disabled. This is
+ sent to the line command port, as seen in the following example:
+ /** serial_configure_line:
+ * Configures the line of the given serial port. The port is set to have a
+ * data length of 8 bits, no parity bits, one stop bit and break control
+ * disabled.
+ *
+ * @param com The serial port to configure
+ */
+ void serial_configure_line(unsigned short com)
+ {
+ /* Bit: | 7 | 6 | 5 4 3 | 2 | 1 0 |
+ * Content: | d | b | prty | s | dl |
+ * Value: | 0 | 0 | 0 0 0 | 0 | 1 1 | = 0x03
+ */
+ outb(SERIAL_LINE_COMMAND_PORT(com), 0x03);
+ }
+
+ The article on OSDev [31] has a more in-depth explanation of the
+ values.
+
+4.3.3 Configuring the Buffers
+
+ When data is transmitted via the serial port it is placed in buffers,
+ both when receiving and sending data. This way, if you send data to the
+ serial port faster than it can send it over the wire, it will be
+ buffered. However, if you send too much data too fast the buffer will
+ be full and data will be lost. In other words, the buffers are FIFO
+ queues. The FIFO queue configuration byte looks like the following
+ figure:
+Bit: | 7 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+Content: | lvl | bs | r | dma | clt | clr | e |
+
+ A description for each name can be found in the table below:
+ Name Description
+ lvl How many bytes should be stored in the FIFO buffers
+ bs If the buffers should be 16 or 64 bytes large
+ r Reserved for future use
+ dma How the serial port data should be accessed
+ clt Clear the transmission FIFO buffer
+ clr Clear the receiver FIFO buffer
+ e If the FIFO buffer should be enabled or not
+
+ We use the value 0xC7 = 11000111 that:
+ * Enables FIFO
+ * Clear both receiver and transmission FIFO queues
+ * Use 14 bytes as size of queue
+
+ The WikiBook on serial programming [32] explains the values in more
+ depth.
+
+4.3.4 Configuring the Modem
+
+ The modem control register is used for very simple hardware flow
+ control via the Ready To Transmit (RTS) and Data Terminal Ready (DTR)
+ pins. When configuring the serial port we want RTS and DTR to be 1,
+ which means that we are ready to send data.
+
+ The modem configuration byte is shown in the following figure:
+Bit: | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
+Content: | r | r | af | lb | ao2 | ao1 | rts | dtr |
+
+ A description for each name can be found in the table below:
+ Name Description
+ r Reserved
+ af Autoflow control enabled
+ lb Loopback mode (used for debugging serial ports)
+ ao2 Auxiliary output 2, used for receiving interrupts
+ ao1 Auxiliary output 1
+ rts Ready To Transmit
+ dtr Data Terminal Ready
+
+ We don't need to enable interrupts, because we won't handle any
+ received data. Therefore we use the configuration value 0x03 = 00000011
+ (RTS = 1 and DTS = 1).
+
+4.3.5 Writing Data to the Serial Port
+
+ Writing data to the serial port is done via the data I/O port. However,
+ before writing, the transmit FIFO queue has to be empty (all previous
+ writes must have finished). The transmit FIFO queue is empty if bit 5
+ of the line status I/O port is equal to one.
+
+ Reading the contents of an I/O port is done via the in assembly code
+ instruction. There is no way to use the in assembly code instruction
+ from C, therefore it has to be wrapped (the same way as the out
+ assembly code instruction):
+ global inb
+
+ ; inb - returns a byte from the given I/O port
+ ; stack: [esp + 4] The address of the I/O port
+ ; [esp ] The return address
+ inb:
+ mov dx, [esp + 4] ; move the address of the I/O port to the dx reg
+ister
+ in al, dx ; read a byte from the I/O port and store it in
+the al register
+ ret ; return the read byte
+ /* in file io.h */
+
+ /** inb:
+ * Read a byte from an I/O port.
+ *
+ * @param port The address of the I/O port
+ * @return The read byte
+ */
+ unsigned char inb(unsigned short port);
+
+ Checking if the transmit FIFO is empty can then be done from C:
+ #include "io.h"
+
+ /** serial_is_transmit_fifo_empty:
+ * Checks whether the transmit FIFO queue is empty or not for the given COM
+ * port.
+ *
+ * @param com The COM port
+ * @return 0 if the transmit FIFO queue is not empty
+ * 1 if the transmit FIFO queue is empty
+ */
+ int serial_is_transmit_fifo_empty(unsigned int com)
+ {
+ /* 0x20 = 0010 0000 */
+ return inb(SERIAL_LINE_STATUS_PORT(com)) & 0x20;
+ }
+
+ Writing to a serial port means spinning as long as the transmit FIFO
+ queue isn't empty, and then writing the data to the data I/O port.
+
+4.3.6 Configuring Bochs
+
+ To save the output from the first serial serial port the Bochs
+ configuration file bochsrc.txt must be updated. The com1 configuration
+ instructs Bochs how to handle first serial port:
+ com1: enabled=1, mode=file, dev=com1.out
+
+ The output from serial port one will now be stored in the file
+ com1.out.
+
+4.3.7 The Driver
+
+ We recommend that you implement a write function for the serial port
+ similar to the write function in the driver for the framebuffer. To
+ avoid name clashes with the write function for the framebuffer it is a
+ good idea to name the functions fb_write and serial_write to
+ distinguish them.
+
+ We further recommend that you try to write a printf-like function, see
+ section 7.3 in [8]. The printf function could take an additional
+ argument to decide to which device to write the output (framebuffer or
+ serial).
+
+ A final recommendation is that you create some way of distinguishing
+ the severeness of the log messages, for example by prepending the
+ messages with DEBUG, INFO or ERROR.
+
+4.4 Further Reading
+
+ * The book "Serial programming" (available on WikiBooks) has a great
+ section on programming the serial port,
+ [154]http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Prog
+ ramming#UART_Registers
+ * The OSDev wiki has a page with a lot of information about the
+ serial ports, [155]http://wiki.osdev.org/Serial_ports
+
+5 Segmentation
+
+ Segmentation in x86 means accessing the memory through segments.
+ Segments are portions of the address space, possibly overlapping,
+ specified by a base address and a limit. To address a byte in segmented
+ memory you use a 48-bit logical address: 16 bits that specifies the
+ segment and 32-bits that specifies what offset within that segment you
+ want. The offset is added to the base address of the segment, and the
+ resulting linear address is checked against the segment's limit - see
+ the figure below. If everything works out fine (including access-rights
+ checks ignored for now) the result is a linear address. When paging is
+ disabled, then the linear address space is mapped 1:1 onto the physical
+ address space, and the physical memory can be accessed. (See the
+ chapter [156]"Paging" for how to enable paging.)
+ Translation of logical addresses to linear addresses.
+
+ Translation of logical addresses to linear addresses.
+
+ To enable segmentation you need to set up a table that describes each
+ segment - a segment descriptor table. In x86, there are two types of
+ descriptor tables: the Global Descriptor Table (GDT) and Local
+ Descriptor Tables (LDT). An LDT is set up and managed by user-space
+ processes, and all processes have their own LDT. LDTs can be used if a
+ more complex segmentation model is desired - we won't use it. The GDT
+ is shared by everyone - it's global.
+
+ As we discuss in the sections on virtual memory and paging,
+ segmentation is rarely used more than in a minimal setup, similar to
+ what we do below.
+
+5.1 Accessing Memory
+
+ Most of the time when accessing memory there is no need to explicitly
+ specify the segment to use. The processor has six 16-bit segment
+ registers: cs, ss, ds, es, gs and fs. The register cs is the code
+ segment register and specifies the segment to use when fetching
+ instructions. The register ss is used whenever accessing the stack
+ (through the stack pointer esp), and ds is used for other data
+ accesses. The OS is free to use the registers es, gs and fs however it
+ want.
+
+ Below is an example showing implicit use of the segment registers:
+ func:
+ mov eax, [esp+4]
+ mov ebx, [eax]
+ add ebx, 8
+ mov [eax], ebx
+ ret
+
+ The above example can be compared with the following one that makes
+ explicit use of the segment registers:
+ func:
+ mov eax, [ss:esp+4]
+ mov ebx, [ds:eax]
+ add ebx, 8
+ mov [ds:eax], ebx
+ ret
+
+ You don't need to use ss for storing the stack segment selector, or ds
+ for the data segment selector. You could store the stack segment
+ selector in ds and vice versa. However, in order to use the implicit
+ style shown above, you must store the segment selectors in their
+ indented registers.
+
+ Segment descriptors and their fields are described in figure 3-8 in the
+ Intel manual [33].
+
+5.2 The Global Descriptor Table (GDT)
+
+ A GDT/LDT is an array of 8-byte segment descriptors. The first
+ descriptor in the GDT is always a null descriptor and can never be used
+ to access memory. At least two segment descriptors (plus the null
+ descriptor) are needed for the GDT, because the descriptor contains
+ more information than just the base and limit fields. The two most
+ relevant fields for us are the Type field and the Descriptor Privilege
+ Level (DPL) field.
+
+ Table 3-1 in chapter 3 of the Intel manual [33] specifies the values
+ for the Type field. The table shows that the Type field can't be both
+ writable and executable at the same time. Therefore, two segments are
+ needed: one segment for executing code to put in cs (Type is
+ Execute-only or Execute-Read) and one segment for reading and writing
+ data (Type is Read/Write) to put in the other segment registers.
+
+ The DPL specifies the privilege levels required to use the segment. x86
+ allows for four privilege levels (PL), 0 to 3, where PL0 is the most
+ privileged. In most operating systems (eg. Linux and Windows), only PL0
+ and PL3 are used. However, some operating system, such as MINIX, make
+ use of all levels. The kernel should be able to do anything, therefore
+ it uses segments with DPL set to 0 (also called kernel mode). The
+ current privilege level (CPL) is determined by the segment selector in
+ cs.
+
+ The segments needed are described in the table below.
+
+ CAPTION: The segment descriptors needed.
+
+ Index Offset Name Address range Type DPL
+ 0 0x00 null descriptor
+ 1 0x08 kernel code segment 0x00000000 - 0xFFFFFFFF RX PL0
+ 2 0x10 kernel data segment 0x00000000 - 0xFFFFFFFF RW PL0
+
+ Note that the segments overlap - they both encompass the entire linear
+ address space. In our minimal setup we'll only use segmentation to get
+ privilege levels. See the Intel manual [33], chapter 3, for details on
+ the other descriptor fields.
+
+5.3 Loading the GDT
+
+ Loading the GDT into the processor is done with the lgdt assembly code
+ instruction, which takes the address of a struct that specifies the
+ start and size of the GDT. It is easiest to encode this information
+ using a [157]"packed struct" as shown in the following example:
+ struct gdt {
+ unsigned int address;
+ unsigned short size;
+ } __attribute__((packed));
+
+ If the content of the eax register is the address to such a struct,
+ then the GDT can be loaded with the assembly code shown below:
+ lgdt [eax]
+
+ It might be easier if you make this instruction available from C, the
+ same way as was done with the assembly code instructions in and out.
+
+ After the GDT has been loaded the segment registers needs to be loaded
+ with their corresponding segment selectors. The content of a segment
+ selector is described in the figure and table below:
+Bit: | 15 3 | 2 | 1 0 |
+Content: | offset (index) | ti | rpl |
+
+ CAPTION: The layout of segment selectors.
+
+ Name Description
+ rpl Requested Privilege Level - we want to execute in PL0 for now.
+ ti Table Indicator. 0 means that this specifies a GDT segment, 1 means
+ an LDT Segment.
+ offset (index) Offset within descriptor table.
+
+ The offset of the segment selector is added to the start of the GDT to
+ get the address of the segment descriptor: 0x08 for the first
+ descriptor and 0x10 for the second, since each descriptor is 8 bytes.
+ The Requested Privilege Level (RPL) should be 0 since the kernel of the
+ OS should execute in privilege level 0.
+
+ Loading the segment selector registers is easy for the data registers -
+ just copy the correct offsets to the registers:
+ mov ds, 0x10
+ mov ss, 0x10
+ mov es, 0x10
+ .
+ .
+ .
+
+ To load cs we have to do a "far jump":
+ ; code here uses the previous cs
+ jmp 0x08:flush_cs ; specify cs when jumping to flush_cs
+
+ flush_cs:
+ ; now we've changed cs to 0x08
+
+ A far jump is a jump where we explicitly specify the full 48-bit
+ logical address: the segment selector to use and the absolute address
+ to jump to. It will first set cs to 0x08 and then jump to flush_cs
+ using its absolute address.
+
+5.4 Further Reading
+
+ * Chapter 3 of the Intel manual [33] is filled with low-level and
+ technical details about segmentation.
+ * The OSDev wiki has a page about segmentation:
+ [158]http://wiki.osdev.org/Segmentation
+ * The Wikipedia page on x86 segmentation might be worth looking into:
+ [159]http://en.wikipedia.org/wiki/X86_memory_segmentation
+
+6 Interrupts and Input
+
+ Now that the OS can produce output it would be nice if it also could
+ get some input. (The operating system must be able to handle interrupts
+ in order to read information from the keyboard). An interrupt occurs
+ when a hardware device, such as the keyboard, the serial port or the
+ timer, signals the CPU that the state of the device has changed. The
+ CPU itself can also send interrupts due to program errors, for example
+ when a program references memory it doesn't have access to, or when a
+ program divides a number by zero. Finally, there are also software
+ intterupts, which are interrupts that are caused by the int assembly
+ code instruction, and they are often used for system calls.
+
+6.1 Interrupts Handlers
+
+ Interrupts are handled via the Interrupt Descriptor Table (IDT). The
+ IDT describes a handler for each interrupt. The interrupts are numbered
+ (0 - 255) and the handler for interrupt i is defined at the ith
+ position in the table. There are three different kinds of handlers for
+ interrupts:
+ * Task handler
+ * Interrupt handler
+ * Trap handler
+
+ The task handlers use functionality specific to the Intel version of
+ x86, so they won't be covered here (see the Intel manual [33], chapter
+ 6, for more info). The only difference between an interrupt handler and
+ a trap handler is that the interrupt handler disables interrupts, which
+ means you cannot get an interrupt while at the same time handling an
+ interrupt. In this book, we will use trap handlers and disable
+ interrupts manually when we need to.
+
+6.2 Creating an Entry in the IDT
+
+ An entry in the IDT for an interrupt handler consists of 64 bits. The
+ highest 32 bits are shown in the figure below:
+Bit: | 31 16 | 15 | 14 13 | 12 | 11 | 10 9 8 | 7 6 5 | 4 3 2 1
+0 |
+Content: | offset high | P | DPL | 0 | D | 1 1 0 | 0 0 0 | reserved
+ |
+
+ The lowest 32 bits are presented in the following figure:
+Bit: | 31 16 | 15 0 |
+Content: | segment selector | offset low |
+
+ A description for each name can be found in the table below:
+ Name Description
+ offset high The 16 highest bits of the 32 bit address in the segment.
+ offset low The 16 lowest bits of the 32 bits address in the segment.
+ p If the handler is present in memory or not (1 = present, 0 = not
+ present).
+ DPL Descriptor Privilige Level, the privilege level the handler can be
+ called from (0, 1, 2, 3).
+ D Size of gate, (1 = 32 bits, 0 = 16 bits).
+ segment selector The offset in the GDT.
+ r Reserved.
+
+ The offset is a pointer to code (preferably an assembly code label).
+ For example, to create an entry for a handler whose code starts at
+ 0xDEADBEEF and that runs in privilege level 0 (therefore using the same
+ code segment selector as the kernel) the following two bytes would be
+ used:
+ 0xDEAD8E00
+ 0x0008BEEF
+
+ If the IDT is represented as an unsigned integer idt[512] then to
+ register the above example as an handler for interrupt 0
+ (divide-by-zero), the following code would be used:
+ idt[0] = 0xDEAD8E00
+ idt[1] = 0x0008BEEF
+
+ As written in the chapter [160]"Getting to C", we recommend that you
+ instead of using bytes (or unsigned integers) use packed structures to
+ make the code more readable.
+
+6.3 Handling an Interrupt
+
+ When an interrupt occurs the CPU will push some information about the
+ interrupt onto the stack, then look up the appropriate interrupt hander
+ in the IDT and jump to it. The stack at the time of the interrupt will
+ look like the following:
+ [esp + 12] eflags
+ [esp + 8] cs
+ [esp + 4] eip
+ [esp] error code?
+
+ The reason for the question mark behind error code is that not all
+ interrupts create an error code. The specific CPU interrupts that put
+ an error code on the stack are 8, 10, 11, 12, 13, 14 and 17. The error
+ code can be used by the interrupt handler to get more information on
+ what has happened. Also, note that the interrupt number is not pushed
+ onto the stack. We can only determine what interrupt has occurred by
+ knowing what code is executing - if the handler registered for
+ interrupt 17 is executing, then interrupt 17 has occurred.
+
+ Once the interrupt handler is done, it uses the iret instruction to
+ return. The instruction iret expects the stack to be the same as at the
+ time of the interrupt (see the figure above). Therefore, any values
+ pushed onto the stack by the interrupt handler must be popped. Before
+ returning, iret restores eflags by popping the value from the stack and
+ then finally jumps to cs:eip as specified by the values on the stack.
+
+ The interrupt handler has to be written in assembly code, since all
+ registers that the interrupt handlers use must be preserved by pushing
+ them onto the stack. This is because the code that was interrupted
+ doesn't know about the interrupt and will therefore expect that its
+ registers stay the same. Writing all the logic of the interrupt handler
+ in assembly code will be tiresome. Creating a handler in assembly code
+ that saves the registers, calls a C function, restores the registers
+ and finally executes iret is a good idea!
+
+ The C handler should get the state of the registers, the state of the
+ stack and the number of the interrupt as arguments. The following
+ definitions can for example be used:
+ struct cpu_state {
+ unsigned int eax;
+ unsigned int ebx;
+ unsigned int ecx;
+ .
+ .
+ .
+ unsigned int esp;
+ } __attribute__((packed));
+
+ struct stack_state {
+ unsigned int error_code;
+ unsigned int eip;
+ unsigned int cs;
+ unsigned int eflags;
+ } __attribute__((packed));
+
+ void interrupt_handler(struct cpu_state cpu, struct stack_state stack, unsig
+ned int interrupt);
+
+6.4 Creating a Generic Interrupt Handler
+
+ Since the CPU does not push the interrupt number on the stack it is a
+ little tricky to write a generic interrupt handler. This section will
+ use macros to show how it can be done. Writing one version for each
+ interrupt is tedious - it is better to use the macro functionality of
+ NASM [34]. And since not all interrupts produce an error code the value
+ 0 will be added as the "error code" for interrupts without an error
+ code. The following code shows an example of how this can be done:
+ %macro no_error_code_interrupt_handler %1
+ global interrupt_handler_%1
+ interrupt_handler_%1:
+ push dword 0 ; push 0 as error code
+ push dword %1 ; push the interrupt number
+ jmp common_interrupt_handler ; jump to the common handler
+ %endmacro
+
+ %macro error_code_interrupt_handler %1
+ global interrupt_handler_%1
+ interrupt_handler_%1:
+ push dword %1 ; push the interrupt number
+ jmp common_interrupt_handler ; jump to the common handler
+ %endmacro
+
+ common_interrupt_handler: ; the common parts of the generic in
+terrupt handler
+ ; save the registers
+ push eax
+ push ebx
+ .
+ .
+ .
+ push ebp
+
+ ; call the C function
+ call interrupt_handler
+
+ ; restore the registers
+ pop ebp
+ .
+ .
+ .
+ pop ebx
+ pop eax
+
+ ; restore the esp
+ add esp, 8
+
+ ; return to the code that got interrupted
+ iret
+
+ no_error_code_interrupt_handler 0 ; create handler for interrupt 0
+ no_error_code_interrupt_handler 1 ; create handler for interrupt 1
+ .
+ .
+ .
+ error_code_handler 7 ; create handler for interrupt 7
+ .
+ .
+ .
+
+ The common_interrupt_handler does the following:
+ * Push the registers on the stack.
+ * Call the C function interrupt_handler.
+ * Pop the registers from the stack.
+ * Add 8 to esp (because of the error code and the interrupt number
+ pushed earlier).
+ * Execute iret to return to the interrupted code.
+
+ Since the macros declare global labels the addresses of the interrupt
+ handlers can be accessed from C or assembly code when creating the IDT.
+
+6.5 Loading the IDT
+
+ The IDT is loaded with the lidt assembly code instruction which takes
+ the address of the first element in the table. It is easiest to wrap
+ this instruction and use it from C:
+ global load_idt
+
+ ; load_idt - Loads the interrupt descriptor table (IDT).
+ ; stack: [esp + 4] the address of the first entry in the IDT
+ ; [esp ] the return address
+ load_idt:
+ mov eax, [esp+4] ; load the address of the IDT into register eax
+ lidt eax ; load the IDT
+ ret ; return to the calling function
+
+6.6 Programmable Interrupt Controller (PIC)
+
+ To start using hardware interrupts you must first configure the
+ Programmable Interrupt Controller (PIC). The PIC makes it possible to
+ map signals from the hardware to interrupts. The reasons for
+ configuring the PIC are:
+ * Remap the interrupts. The PIC uses interrupts 0 - 15 for hardware
+ interrupts by default, which conflicts with the CPU interrupts.
+ Therefore the PIC interrupts must be remapped to another interval.
+ * Select which interrupts to receive. You probably don't want to
+ receive interrupts from all devices since you don't have code that
+ handles these interrupts anyway.
+ * Set up the correct mode for the PIC.
+
+ In the beginning there was only one PIC (PIC 1) and eight interrupts.
+ As more hardware were added, 8 interrupts were too few. The solution
+ chosen was to chain on another PIC (PIC 2) on the first PIC (see
+ interrupt 2 on PIC 1).
+
+ The hardware interrupts are shown in the table below:
+ PIC 1 Hardware PIC 2 Hardware
+ 0 Timer 8 Real Time Clock
+ 1 Keyboard 9 General I/O
+ 2 PIC 2 10 General I/O
+ 3 COM 2 11 General I/O
+ 4 COM 1 12 General I/O
+ 5 LPT 2 13 Coprocessor
+ 6 Floppy disk 14 IDE Bus
+ 7 LPT 1 15 IDE Bus
+
+ A great tutorial for configuring the PIC can be found at the SigOPS
+ website [35]. We won't repeat that information here.
+
+ Every interrupt from the PIC has to be acknowledged - that is, sending
+ a message to the PIC confirming that the interrupt has been handled. If
+ this isn't done the PIC won't generate any more interrupts.
+
+ Acknowledging a PIC interrupt is done by sending the byte 0x20 to the
+ PIC that raised the interrupt. Implementing a pic_acknowledge function
+ can thus be done as follows:
+ #include "io.h"
+
+ #define PIC1_PORT_A 0x20
+ #define PIC2_PORT_A 0xA0
+
+ /* The PIC interrupts have been remapped */
+ #define PIC1_START_INTERRUPT 0x20
+ #define PIC2_START_INTERRUPT 0x28
+ #define PIC2_END_INTERRUPT PIC2_START_INTERRUPT + 7
+
+ #define PIC_ACK 0x20
+
+ /** pic_acknowledge:
+ * Acknowledges an interrupt from either PIC 1 or PIC 2.
+ *
+ * @param num The number of the interrupt
+ */
+ void pic_acknowledge(unsigned integer interrupt)
+ {
+ if (interrupt < PIC1_START_INTERRUPT || interrupt > PIC2_END_INTERRUPT)
+{
+ return;
+ }
+
+ if (interrupt < PIC2_START_INTERRUPT) {
+ outb(PIC1_PORT_A, PIC_ACK);
+ } else {
+ outb(PIC2_PORT_A, PIC_ACK);
+ }
+ }
+
+6.7 Reading Input from the Keyboard
+
+ The keyboard does not generate ASCII characters, it generates scan
+ codes. A scan code represents a button - both presses and releases. The
+ scan code representing the just pressed button can be read from the
+ keyboard's data I/O port which has address 0x60. How this can be done
+ is shown in the following example:
+ #include "io.h"
+
+ #define KBD_DATA_PORT 0x60
+
+ /** read_scan_code:
+ * Reads a scan code from the keyboard
+ *
+ * @return The scan code (NOT an ASCII character!)
+ */
+ unsigned char read_scan_code(void)
+ {
+ return inb(KBD_DATA_PORT);
+ }
+
+ The next step is to write a function that translates a scan code to the
+ corresponding ASCII character. If you want to map the scan codes to
+ ASCII characters as is done on an American keyboard then Andries
+ Brouwer has a great tutorial [36].
+
+ Remember, since the keyboard interrupt is raised by the PIC, you must
+ call pic_acknowledge at the end of the keyboard interrupt handler.
+ Also, the keyboard will not send you any more interrupts until you read
+ the scan code from the keyboard.
+
+6.8 Further Reading
+
+ * The OSDev wiki has a great page on interrupts,
+ [161]http://wiki.osdev.org/Interrupts
+ * Chapter 6 of Intel Manual 3a [33] describes everything there is to
+ know about interrupts.
+
+7 The Road to User Mode
+
+ Now that the kernel boots, prints to screen and reads from keyboard -
+ what do we do? Usually, a kernel is not supposed to do the application
+ logic itself, but leave that for applications. The kernel creates the
+ proper abstractions (for memory, files, devices) to make application
+ development easier, performs tasks on behalf of applications (system
+ calls) and [162]schedules processes.
+
+ User mode, in contrast with kernel mode, is the environment in which
+ the user's programs execute. This environment is less privileged than
+ the kernel, and will prevent (badly written) user programs from messing
+ with other programs or the kernel. Badly written kernels are free to
+ mess up what they want.
+
+ There's quite a way to go until the OS created in this book can execute
+ programs in user mode, but this chapter will show how to easily execute
+ a small program in kernel mode.
+
+7.1 Loading an External Program
+
+ Where do we get the external program from? Somehow we need to load the
+ code we want to execute into memory. More feature-complete operating
+ systems usually have drivers and file systems that enable them to load
+ the software from a CD-ROM drive, a hard disk or other persistent
+ media.
+
+ Instead of creating all these drivers and file systems we will use a
+ feature in GRUB called modules to load the program.
+
+7.1.1 GRUB Modules
+
+ GRUB can load arbitrary files into memory from the ISO image, and these
+ files are usually referred to as modules. To make GRUB load a module,
+ edit the file iso/boot/grub/menu.lst and add the following line at the
+ end of the file:
+ module /modules/program
+
+ Now create the folder iso/modules:
+ mkdir -p iso/modules
+
+ The application program will be created later in this chapter.
+
+ The code that calls kmain must be updated to pass information to kmain
+ about where it can find the modules. We also want to tell GRUB that it
+ should align all the modules on page boundaries when loading them (see
+ the chapter [163]"Paging" for details about page alignment).
+
+ To instruct GRUB how to load our modules, the "multiboot header" - the
+ first bytes of the kernel - must be updated as follows:
+ ; in file `loader.s`
+
+
+ MAGIC_NUMBER equ 0x1BADB002 ; define the magic number constant
+ ALIGN_MODULES equ 0x00000001 ; tell GRUB to align modules
+
+ ; calculate the checksum (all options + checksum should equal 0)
+ CHECKSUM equ -(MAGIC_NUMBER + ALIGN_MODULES)
+
+ section .text: ; start of the text (code) section
+ align 4 ; the code must be 4 byte aligned
+ dd MAGIC_NUMBER ; write the magic number
+ dd ALIGN_MODULES ; write the align modules instruction
+ dd CHECKSUM ; write the checksum
+
+ GRUB will also store a pointer to a struct in the register ebx that,
+ among other things, describes at which addresses the modules are
+ loaded. Therefore, you probably want to push ebx on the stack before
+ calling kmain to make it an argument for kmain.
+
+7.2 Executing a Program
+
+7.2.1 A Very Simple Program
+
+ A program written at this stage can only perform a few actions.
+ Therefore, a very short program that writes a value to a register
+ suffices as a test program. Halting Bochs after a while and then check
+ that register contains the correct number by looking in the Bochs log
+ will verify that the program has run. This is an example of such a
+ short program:
+ ; set eax to some distinguishable number, to read from the log afterwards
+ mov eax, 0xDEADBEEF
+
+ ; enter infinite loop, nothing more to do
+ ; $ means "beginning of line", ie. the same instruction
+ jmp $
+
+7.2.2 Compiling
+
+ Since our kernel cannot parse advanced executable formats we need to
+ compile the code into a flat binary. NASM can do this with the flag -f:
+ nasm -f bin program.s -o program
+
+ This is all we need. You must now move the file program to the folder
+ iso/modules.
+
+7.2.3 Finding the Program in Memory
+
+ Before jumping to the program we must find where it resides in memory.
+ Assuming that the contents of ebx is passed as an argument to kmain, we
+ can do this entirely from C.
+
+ The pointer in ebx points to a multiboot structure [19]. Download the
+ multiboot.h file from
+ [164]http://www.gnu.org/software/grub/manual/multiboot/html_node/multib
+ oot.h.html, which describes the structure.
+
+ The pointer passed to kmain in the ebx register can be cast to a
+ multiboot_info_t pointer. The address of the first module is in the
+ field mods_addr. The following code shows an example:
+ int kmain(/* additional arguments */ unsigned int ebx)
+ {
+ multiboot_info_t *mbinfo = (multiboot_info_t *) ebx;
+ unsigned int address_of_module = mbinfo->mods_addr;
+ }
+
+ However, before just blindly following the pointer, you should check
+ that the module got loaded correctly by GRUB. This can be done by
+ checking the flags field of the multiboot_info_t structure. You should
+ also check the field mods_count to make sure it is exactly 1. For more
+ details about the multiboot structure, see the multiboot documentation
+ [19].
+
+7.2.4 Jumping to the Code
+
+ The only thing left to do is to jump to the code loaded by GRUB. Since
+ it is easier to parse the multiboot structure in C than assembly code,
+ calling the code from C is more convenient (it can of course be done
+ with jmp or call in assembly code as well). The C code could look like
+ this:
+ typedef void (*call_module_t)(void);
+ /* ... */
+ call_module_t start_program = (call_module_t) address_of_module;
+ start_program();
+ /* we'll never get here, unless the module code returns */
+
+ If we start the kernel, wait until it has run and entered the infinite
+ loop in the program, and then halt Bochs, we should see 0xDEADBEEF in
+ the register eax via the Bochs log. We have successfully started a
+ program in our OS!
+
+7.3 The Beginning of User Mode
+
+ The program we've written now runs at the same privilege level as the
+ kernel - we've just entered it in a somewhat peculiar way. To enable
+ applications to execute at a different privilege level we'll need to,
+ beside [165]segmentation, do [166]paging and [167]page frame
+ allocation.
+
+ It's quite a lot of work and technical details to go through, but in a
+ few chapters you'll have working user mode programs.
+
+8 A Short Introduction to Virtual Memory
+
+ Virtual memory is an abstraction of physical memory. The purpose of
+ virtual memory is generally to simplify application development and to
+ let processes address more memory than what is actually physically
+ present in the machine. We also don't want applications messing with
+ the kernel or other applications' memory due to security.
+
+ In the x86 architecture, virtual memory can be accomplished in two
+ ways: segmentation and paging. Paging is by far the most common and
+ versatile technique, and we'll implement it the next chapter. Some use
+ of segmentation is still necessary to allow for code to execute under
+ different privilege levels.
+
+ Managing memory is a big part of what an operating system does.
+ [168]Paging and [169]page frame allocation deals with that.
+
+ Segmentation and paging is described in the [33], chapter 3 and 4.
+
+8.1 Virtual Memory Through Segmentation?
+
+ You could skip paging entirely and just use segmentation for virtual
+ memory. Each user mode process would get its own segment, with base
+ address and limit properly set up. This way no process can see the
+ memory of another process. A problem with this is that the physical
+ memory for a process needs to be contiguous (or at least it is very
+ convenient if it is). Either we need to know in advance how much memory
+ the program will require (unlikely), or we can move the memory segments
+ to places where they can grow when the limit is reached (expensive,
+ causes fragmentation - can result in "out of memory" even though enough
+ memory is available). Paging solves both these problems.
+
+ It is interesting to note that in x86_64 (the 64-bit version of the x86
+ architecture), segmentation is almost completely removed.
+
+8.2 Further Reading
+
+ * LWN.net has an article on virtual memory:
+ [170]http://lwn.net/Articles/253361/
+ * Gustavo Duarte has also written an article about virtual memory:
+ [171]http://duartes.org/gustavo/blog/post/memory-translation-and-se
+ gmentation
+
+9 Paging
+
+ Segmentation translates a logical address into a linear address. Paging
+ translates these linear addresses onto the physical address space, and
+ determines access rights and how the memory should be cached.
+
+9.1 Why Paging?
+
+ Paging is the most common technique used in x86 to enable virtual
+ memory. Virtual memory through paging means that each process will get
+ the impression that the available memory range is 0x00000000 -
+ 0xFFFFFFFF even though the actual size of the memory might be much
+ less. It also means that when a process addresses a byte of memory it
+ will use a virtual (linear) address instead of physical one. The code
+ in the user process won't notice any difference (except for execution
+ delays). The linear address gets translated to a physical address by
+ the MMU and the page table. If the virtual address isn't mapped to a
+ physical address, the CPU will raise a page fault interrupt.
+
+ Paging is optional, and some operating systems do not make use of it.
+ But if we want to mark certain areas of memory accessible only to code
+ running at a certain privilege level (to be able to have processes
+ running at different privilege levels), paging is the neatest way to do
+ it.
+
+9.2 Paging in x86
+
+ Paging in x86 (chapter 4 in the Intel manual [33]) consists of a page
+ directory (PDT) that can contain references to 1024 page tables (PT),
+ each of which can point to 1024 sections of physical memory called page
+ frames (PF). Each page frame is 4096 byte large. In a virtual (linear)
+ address, the highest 10 bits specifies the offset of a page directory
+ entry (PDE) in the current PDT, the next 10 bits the offset of a page
+ table entry (PTE) within the page table pointed to by that PDE. The
+ lowest 12 bits in the address is the offset within the page frame to be
+ addressed.
+
+ All page directories, page tables and page frames need to be aligned on
+ 4096 byte addresses. This makes it possible to address a PDT, PT or PF
+ with just the highest 20 bits of a 32 bit address, since the lowest 12
+ need to be zero.
+
+ The PDE and PTE structure is very similar to each other: 32 bits (4
+ bytes), where the highest 20 bits points to a PTE or PF, and the lowest
+ 12 bits control access rights and other configurations. 4 bytes times
+ 1024 equals 4096 bytes, so a page directory and page table both fit in
+ a page frame themselves.
+
+ The translation of linear addresses to physical addresses is described
+ in the figure below.
+
+ While pages are normally 4096 bytes, it is also possible to use 4 MB
+ pages. A PDE then points directly to a 4 MB page frame, which needs to
+ be aligned on a 4 MB address boundary. The address translation is
+ almost the same as in the figure, with just the page table step
+ removed. It is possible to mix 4 MB and 4 KB pages.
+ Translating virtual addresses (linear addresses) to physical addresses.
+
+ Translating virtual addresses (linear addresses) to physical addresses.
+
+ The 20 bits pointing to the current PDT is stored in the register cr3.
+ The lower 12 bits of cr3 are used for configuration.
+
+ For more details on the paging structures, see chapter 4 in the Intel
+ manual [33]. The most interesting bits are U/S, which determine what
+ privilege levels can access this page (PL0 or PL3), and R/W, which
+ makes the memory in the page read-write or read-only.
+
+9.2.1 Identity Paging
+
+ The simplest kind of paging is when we map each virtual address onto
+ the same physical address, called identity paging. This can be done at
+ compile time by creating a page directory where each entry points to
+ its corresponding 4 MB frame. In NASM this can be done with macros and
+ commands (%rep, times and dd). It can of course also be done at
+ run-time by using ordinary assembly code instructions.
+
+9.2.2 Enabling Paging
+
+ Paging is enabled by first writing the address of a page directory to
+ cr3 and then setting bit 31 (the PG "paging-enable" bit) of cr0 to 1.
+ To use 4 MB pages, set the PSE bit (Page Size Extensions, bit 4) of
+ cr4. The following assembly code shows an example:
+ ; eax has the address of the page directory
+ mov cr3, eax
+
+ mov ebx, cr4 ; read current cr4
+ or ebx, 0x00000010 ; set PSE
+ mov cr4, ebx ; update cr4
+
+ mov ebx, cr0 ; read current cr0
+ or ebx, 0x80000000 ; set PG
+ mov cr0, ebx ; update cr0
+
+ ; now paging is enabled
+
+9.2.3 A Few Details
+
+ It is important to note that all addresses within the page directory,
+ page tables and in cr3 need to be physical addresses to the structures,
+ never virtual. This will be more relevant in later sections where we
+ dynamically update the paging structures (see the chapter [172]"User
+ Mode").
+
+ An instruction that is useful when an updating a PDT or PT is invlpg.
+ It invalidates the Translation Lookaside Buffer (TLB) entry for a
+ virtual address. The TLB is a cache for translated addresses, mapping
+ physical addresses corresponding to virtual addresses. This is only
+ required when changing a PDE or PTE that was previously mapped to
+ something else. If the PDE or PTE had previously been marked as not
+ present (bit 0 was set to 0), executing invlpg is unnecessary. Changing
+ the value of cr3 will cause all entries in the TLB to be invalidated.
+
+ An example of invalidating a TLB entry is shown below:
+ ; invalidate any TLB references to virtual address 0
+ invlpg [0]
+
+9.3 Paging and the Kernel
+
+ This section will describe how paging affects the OS kernel. We
+ encourage you to run your OS using identity paging before trying to
+ implement a more advanced paging setup, since it can be hard to debug a
+ malfunctioning page table that is set up via assembly code.
+
+9.3.1 Reasons to Not Identity Map the Kernel
+
+ If the kernel is placed at the beginning of the virtual address space -
+ that is, the virtual address space (0x00000000, "size of kernel") maps
+ to the location of the kernel in memory - there will be issues when
+ linking the user mode process code. Normally, during linking, the
+ linker assumes that the code will be loaded into the memory position
+ 0x00000000. Therefore, when resolving absolute references, 0x00000000
+ will be the base address for calculating the exact position. But if the
+ kernel is mapped onto the virtual address space (0x00000000, "size of
+ kernel"), the user mode process cannot be loaded at virtual address
+ 0x00000000 - it must be placed somewhere else. Therefore, the
+ assumption from the linker that the user mode process is loaded into
+ memory at position 0x00000000 is wrong. This can be corrected by using
+ a linker script which tells the linker to assume a different starting
+ address, but that is a very cumbersome solution for the users of the
+ operating system.
+
+ This also assumes that we want the kernel to be part of the user mode
+ process' address space. As we will see later, this is a nice feature,
+ since during system calls we don't have to change any paging structures
+ to get access to the kernel's code and data. The kernel pages will of
+ course require privilege level 0 for access, to prevent a user process
+ from reading or writing kernel memory.
+
+9.3.2 The Virtual Address for the Kernel
+
+ Preferably, the kernel should be placed at a very high virtual memory
+ address, for example 0xC0000000 (3 GB). The user mode process is not
+ likely to be 3 GB large, which is now the only way that it can conflict
+ with the kernel. When the kernel uses virtual addresses at 3 GB and
+ above it is called a higher-half kernel. 0xC0000000 is just an example,
+ the kernel can be placed at any address higher than 0 to get the same
+ benefits. Choosing the correct address depends on how much virtual
+ memory should be available for the kernel (it is easiest if all memory
+ above the kernel virtual address should belong to the kernel) and how
+ much virtual memory should be available for the process.
+
+ If the user mode process is larger than 3 GB, some pages will need to
+ be swapped out by the kernel. Swapping pages is not part of this book.
+
+9.3.3 Placing the Kernel at 0xC0000000
+
+ To start with, it is better to place the kernel at 0xC0100000 than
+ 0xC0000000, since this makes it possible to map (0x00000000,
+ 0x00100000) to (0xC0000000, 0xC0100000). This way, the entire range
+ (0x00000000, "size of kernel") of memory is mapped to the range
+ (0xC0000000, 0xC0000000 + "size of kernel").
+
+ Placing the kernel at 0xC0100000 isn't hard, but it does require some
+ thought. This is once again a linking problem. When the linker resolves
+ all absolute references in the kernel, it will assume that our kernel
+ is loaded at physical memory location 0x00100000, not 0x00000000, since
+ relocation is used in the linker script (see the section [173]"Linking
+ the kernel"). However, we want the jumps to be resolved using
+ 0xC0100000 as base address, since otherwise a kernel jump will jump
+ straight into the user mode process code (remember that the user mode
+ process is loaded at virtual memory 0x00000000).
+
+ However, we can't simply tell the linker to assume that the kernel
+ starts (is loaded) at 0xC01000000, since we want it to be loaded at the
+ physical address 0x00100000. The reason for having the kernel loaded at
+ 1 MB is because it can't be loaded at 0x00000000, since there is BIOS
+ and GRUB code loaded below 1 MB. Furthermore, we cannot assume that we
+ can load the kernel at 0xC0100000, since the machine might not have 3
+ GB of physical memory.
+
+ This can be solved by using both relocation (.=0xC0100000) and the AT
+ instruction in the linker script. Relocation specifies that
+ non-relative memory-references should should use the relocation address
+ as base in address calculations. AT specifies where the kernel should
+ be loaded into memory. Relocation is done at link time by GNU ld [37],
+ the load address specified by AT is handled by GRUB when loading the
+ kernel, and is part of the ELF format [18].
+
+9.3.4 Higher-half Linker Script
+
+ We can modify the [174]first linker script to implement this:
+ ENTRY(loader) /* the name of the entry symbol */
+
+ . = 0xC0100000 /* the code should be relocated to 3GB + 1MB */
+
+ /* align at 4 KB and load at 1 MB */
+ .text ALIGN (0x1000) : AT(ADDR(.text)-0xC0000000)
+ {
+ *(.text) /* all text sections from all files */
+ }
+
+ /* align at 4 KB and load at 1 MB + . */
+ .rodata ALIGN (0x1000) : AT(ADDR(.text)-0xC0000000)
+ {
+ *(.rodata*) /* all read-only data sections from all files */
+ }
+
+ /* align at 4 KB and load at 1 MB + . */
+ .data ALIGN (0x1000) : AT(ADDR(.text)-0xC0000000)
+ {
+ *(.data) /* all data sections from all files */
+ }
+
+ /* align at 4 KB and load at 1 MB + . */
+ .bss ALIGN (0x1000) : AT(ADDR(.text)-0xC0000000)
+ {
+ *(COMMON) /* all COMMON sections from all files */
+ *(.bss) /* all bss sections from all files */
+ }
+
+9.3.5 Entering the Higher Half
+
+ When GRUB jumps to the kernel code, there is no paging table.
+ Therefore, all references to 0xC0100000 + X won't be mapped to the
+ correct physical address, and will therefore cause a general protection
+ exception (GPE) at the very best, otherwise (if the computer has more
+ than 3 GB of memory) the computer will just crash.
+
+ Therefore, assembly code that doesn't use relative jumps or relative
+ memory addressing must be used to do the following:
+ * Set up a page table.
+ * Add identity mapping for the first 4 MB of the virtual address
+ space.
+ * Add an entry for 0xC0100000 that maps to 0x0010000
+
+ If we skip the identity mapping for the first 4 MB, the CPU would
+ generate a page fault immediately after paging was enabled when trying
+ to fetch the next instruction from memory. After the table has been
+ created, an jump can be done to a label to make eip point to a virtual
+ address in the higher half:
+ ; assembly code executing at around 0x00100000
+ ; enable paging for both actual location of kernel
+ ; and its higher-half virtual location
+
+ lea ebx, [higher_half] ; load the address of the label in ebx
+ jmp ebx ; jump to the label
+
+ higher_half:
+ ; code here executes in the higher half kernel
+ ; eip is larger than 0xC0000000
+ ; can continue kernel initialisation, calling C code, etc.
+
+ The register eip will now point to a memory location somewhere right
+ after 0xC0100000 - all the code can now execute as if it were located
+ at 0xC0100000, the higher-half. The entry mapping of the first 4 MB of
+ virtual memory to the first 4 MB of physical memory can now be removed
+ from the page table and its corresponding entry in the TLB invalidated
+ with invlpg [0].
+
+9.3.6 Running in the Higher Half
+
+ There are a few more details we must deal with when using a higher-half
+ kernel. We must be careful when using memory-mapped I/O that uses
+ specific memory locations. For example, the frame buffer is located at
+ 0x000B8000, but since there is no entry in the page table for the
+ address 0x000B8000 any longer, the address 0xC00B8000 must be used,
+ since the virtual address 0xC0000000 maps to the physical address
+ 0x00000000.
+
+ Any explicit references to addresses within the multiboot structure
+ needs to be changed to reflect the new virtual addresses as well.
+
+ Mapping 4 MB pages for the kernel is simple, but wastes memory (unless
+ you have a really big kernel). Creating a higher-half kernel mapped in
+ as 4 KB pages saves memory but is harder to set up. Memory for the page
+ directory and one page table can be reserved in the .data section, but
+ one needs to configure the mappings from virtual to physical addresses
+ at run-time. The size of the kernel can be determined by exporting
+ labels from the linker script [37], which we'll need to do later anyway
+ when writing the page frame allocator (see the chapter [175]"Page Frame
+ Allocation).
+
+9.4 Virtual Memory Through Paging
+
+ Paging enables two things that are good for virtual memory. First, it
+ allows for fine-grained access control to memory. You can mark pages as
+ read-only, read-write, only for PL0 etc. Second, it creates the
+ illusion of contiguous memory. User mode processes, and the kernel, can
+ access memory as if it were contiguous, and the contiguous memory can
+ be extended without the need to move data around in memory. We can also
+ allow the user mode programs access to all memory below 3 GB, but
+ unless they actually use it, we don't have to assign page frames to the
+ pages. This allows processes to have code located near 0x00000000 and
+ the stack at just below 0xC0000000, and still not require more than two
+ actual pages.
+
+9.5 Further Reading
+
+ * Chapter 4 (and to some extent chapter 3) of the Intel manual [33]
+ are your definitive sources for the details about paging.
+ * Wikipedia has an article on paging:
+ [176]http://en.wikipedia.org/wiki/Paging
+ * The OSDev wiki has a page on paging:
+ [177]http://wiki.osdev.org/Paging and a tutorial for making a
+ higher-half kernel:
+ [178]http://wiki.osdev.org/Higher_Half_bare_bones
+ * Gustavo Duarte's article on how a kernel manages memory is well
+ worth a read:
+ [179]http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-m
+ emory
+ * Details on the linker command language can be found at Steve
+ Chamberlain's website [37].
+ * More details on the ELF format can be found in this presentation:
+ [180]http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf
+
+10 Page Frame Allocation
+
+ When using virtual memory, how does the OS know which parts of memory
+ are free to use? That is the role of the page frame allocator.
+
+10.1 Managing Available Memory
+
+10.1.1 How Much Memory is There?
+
+ First we need to know how much memory is available on the computer the
+ OS is running on. The easiest way to do this is to read it from the
+ multiboot structure [19] passed to us by GRUB. GRUB collects the
+ information we need about the memory - what is reserved, I/O mapped,
+ read-only etc. We must also make sure that we don't mark the part of
+ memory used by the kernel as free (since GRUB doesn't mark this memory
+ as reserved). One way to know how much memory the kernel uses is to
+ export labels at the beginning and the end of the kernel binary from
+ the linker script:
+ ENTRY(loader) /* the name of the entry symbol */
+
+ . = 0xC0100000 /* the code should be relocated to 3 GB + 1 MB */
+
+ /* these labels get exported to the code files */
+ kernel_virtual_start = .;
+ kernel_physical_start = . - 0xC0000000;
+
+ /* align at 4 KB and load at 1 MB */
+ .text ALIGN (0x1000) : AT(ADDR(.text)-0xC0000000)
+ {
+ *(.text) /* all text sections from all files */
+ }
+
+ /* align at 4 KB and load at 1 MB + . */
+ .rodata ALIGN (0x1000) : AT(ADDR(.rodata)-0xC0000000)
+ {
+ *(.rodata*) /* all read-only data sections from all files */
+ }
+
+ /* align at 4 KB and load at 1 MB + . */
+ .data ALIGN (0x1000) : AT(ADDR(.data)-0xC0000000)
+ {
+ *(.data) /* all data sections from all files */
+ }
+
+ /* align at 4 KB and load at 1 MB + . */
+ .bss ALIGN (0x1000) : AT(ADDR(.bss)-0xC0000000)
+ {
+ *(COMMON) /* all COMMON sections from all files */
+ *(.bss) /* all bss sections from all files */
+ }
+
+ kernel_virtual_end = .;
+ kernel_physical_end = . - 0xC0000000;
+
+ These labels can directly be read from assembly code and pushed on the
+ stack to make them available to C code:
+ extern kernel_virtual_start
+ extern kernel_virtual_end
+ extern kernel_physical_start
+ extern kernel_physical_end
+
+ ; ...
+
+ push kernel_physical_end
+ push kernel_physical_start
+ push kernel_virtual_end
+ push kernel_virtual_start
+
+ call kmain
+
+ This way we get the labels as arguments to kmain. If you want to use C
+ instead of assembly code, one way to do it is to declare the labels as
+ functions and take the addresses of these functions:
+ void kernel_virtual_start(void);
+
+ /* ... */
+
+ unsigned int vaddr = (unsigned int) &kernel_virtual_start;
+
+ If you use GRUB modules you need to make sure the memory they use is
+ marked as reserved as well.
+
+ Note that the available memory does not need to be contiguous. In the
+ first 1 MB there are several I/O-mapped memory sections, as well as
+ memory used by GRUB and the BIOS. Other parts of the memory might be
+ similarly unavailable.
+
+ It's convenient to divide the memory sections into complete page
+ frames, as we can't map part of pages into memory.
+
+10.1.2 Managing Available Memory
+
+ How do we know which page frames are in use? The page frame allocator
+ needs to keep track of which are free and which aren't. There are
+ several ways to do this: bitmaps, linked lists, trees, the Buddy System
+ (used by Linux) etc. For more information about the different
+ algorithms see the article on OSDev [38].
+
+ Bitmaps are quite easy to implement. One bit is used for each page
+ frame and one (or more) page frames are dedicated to store the bitmap.
+ (Note that this is just one way to do it, other designs might be better
+ and/or more fun to implement.)
+
+10.2 How Can We Access a Page Frame?
+
+ The page frame allocator returns the physical start address of the page
+ frame. This page frame is not mapped in - no page table points to this
+ page frame. How can we read and write data to the frame?
+
+ We need to map the page frame into virtual memory, by updating the PDT
+ and/or PT used by the kernel. What if all available page tables are
+ full? Then we can't map the page frame into memory, because we'd need a
+ new page table - which takes up an entire page frame - and to write to
+ this page frame we'd need to map its page frame... Somehow this
+ circular dependency must be broken.
+
+ One solution is to reserve a part of the first page table used by the
+ kernel (or some other higher-half page table) for temporarily mapping
+ page frames to make them accessible. If the kernel is mapped at
+ 0xC0000000 (page directory entry with index 768), and 4 KB page frames
+ are used, then the kernel has at least one page table. If we assume -
+ or limit us to - a kernel of size at most 4 MB minus 4 KB we can
+ dedicate the last entry (entry 1023) of this page table for temporary
+ mappings. The virtual address of pages mapped in using the last entry
+ of the kernel's PT will be:
+ (768 << 22) | (1023 << 12) | 0x000 = 0xC03FF000
+
+ After we've temporarily mapped the page frame we want to use as a page
+ table, and set it up to map in our first page frame, we can add it to
+ the paging directory, and remove the temporary mapping.
+
+10.3 A Kernel Heap
+
+ So far we've only been able to work with fixed-size data, or directly
+ with raw memory. Now that we have a page frame allocator we can
+ implement malloc and free to use in the kernel.
+
+ Kernighan and Ritchie [8] have an example implementation in their book
+ [8] that we can draw inspiration from. The only modification we need to
+ do is to replace calls to sbrk/brk with calls to the page frame
+ allocator when more memory is needed. We must also make sure to map the
+ page frames returned by the page frame allocator to virtual addresses.
+ A correct implementation should also return page frames to the page
+ frame allocator on call to free, whenever sufficiently large blocks of
+ memory are freed.
+
+10.4 Further reading
+
+ * The OSDev wiki page on page frame allocation:
+ [181]http://wiki.osdev.org/Page_Frame_Allocation
+
+11 User Mode
+
+ User mode is now almost within our reach, there are just a few more
+ steps required to get there. Although these steps might seem easy they
+ way they are presented in this chapter, they can be tricky to
+ implement, since there are a lot of places where small errors will
+ cause bugs that are hard to find.
+
+11.1 Segments for User Mode
+
+ To enable user mode we need to add two more segments to the GDT. They
+ are very similar to the kernel segments we added when we [182]set up
+ the GDT in the [183]chapter about segmentation:
+
+ CAPTION: The segment descriptors needed for user mode.
+
+ Index Offset Name Address range Type DPL
+ 3 0x18 user code segment 0x00000000 - 0xFFFFFFFF RX PL3
+ 4 0x20 user data segment 0x00000000 - 0xFFFFFFFF RW PL3
+
+ The difference is the DPL, which now allows code to execute in PL3. The
+ segments can still be used to address the entire address space, just
+ using these segments for user mode code will not protect the kernel.
+ For that we need paging.
+
+11.2 Setting Up For User Mode
+
+ There are a few things every user mode process needs:
+ * Page frames for code, data and stack. At the moment it suffices to
+ allocate one page frame for the stack and enough page frames to fit
+ the program's code. Don't worry about setting up a stack that can
+ be grow and shrink at this point in time, focus on getting a basic
+ implementation work first.
+ * The binary from the GRUB module has to be copied to the page frames
+ used for the programs code.
+ * A page directory and page tables are needed to map the page frames
+ described above into memory. At least two page tables are needed,
+ because the code and data should be mapped in at 0x00000000 and
+ increasing, and the stack should start just below the kernel, at
+ 0xBFFFFFFB, growing towards lower addresses. The U/S flag has to be
+ set to allow PL3 access.
+
+ It might be convenient to store this information in a struct
+ representing a process. This process struct can be dynamically
+ allocated with the kernel's malloc function.
+
+11.3 Entering User Mode
+
+ The only way to execute code with a lower privilege level than the
+ current privilege level (CPL) is to execute an iret or lret instruction
+ - interrupt return or long return, respectively.
+
+ To enter user mode we set up the stack as if the processor had raised
+ an inter-privilege level interrupt. The stack should look like the
+ following:
+ [esp + 16] ss ; the stack segment selector we want for user mode
+ [esp + 12] esp ; the user mode stack pointer
+ [esp + 8] eflags ; the control flags we want to use in user mode
+ [esp + 4] cs ; the code segment selector
+ [esp + 0] eip ; the instruction pointer of user mode code to execute
+
+ See the Intel manual [33], section 6.2.1, figure 6-4 for more
+ information.
+
+ The instruction iret will then read these values from the stack and
+ fill in the corresponding registers. Before we execute iret we need to
+ change to the page directory we setup for the user mode process. It is
+ important to remember that to continue executing kernel code after
+ we've switched PDT, the kernel needs to be mapped in. One way to
+ accomplish this is to have a separate PDT for the kernel, which maps
+ all data at 0xC0000000 and above, and merge it with the user PDT (which
+ only maps below 0xC0000000) when performing the switch. Remember that
+ physical address of the PDT has to be used when setting the register
+ cr3.
+
+ The register eflags contains a set of different flags, specified in
+ section 2.3 of the Intel manual [33]. Most important for us is the
+ interrupt enable (IF) flag. The assembly code instruction sti can't be
+ used in privilege level 3 for enabling interrupts. If interrupts are
+ disabled when entering user mode, then interrupts can't enabled once
+ user mode is entered. Setting the IF flag in the eflags entry on the
+ stack will enable interrupts in user mode, since the assembly code
+ instruction iret will set the register eflags to the corresponding
+ value on the stack.
+
+ For now, we should have interrupts disabled, as it requires a little
+ more work to get inter-privilege level interrupts to work properly (see
+ the section [184]"System calls").
+
+ The value eip on the stack should point to the entry point for the user
+ code - 0x00000000 in our case. The value esp on the stack should be
+ where the stack starts - 0xBFFFFFFB (0xC0000000 - 4).
+
+ The values cs and ss on the stack should be the segment selectors for
+ the user code and user data segments, respectively. As we saw in the
+ [185]segmentation chapter, the lowest two bits of a segment selector is
+ the RPL - the Requested Privilege Level. When using iret to enter PL3,
+ the RPL of cs and ss should be 0x3. The following code shows an
+ example:
+ USER_MODE_CODE_SEGMENT_SELECTOR equ 0x18
+ USER_MODE_DATA_SEGMENT_SELECTOR equ 0x20
+ mov cs, USER_MODE_CODE_SEGMENT_SELECTOR | 0x3
+ mov ss, USER_MODE_DATA_SEGMENT_SELECTOR | 0x3
+
+ The register ds, and the other data segment registers, should be set to
+ the same segment selector as ss. They can be set the ordinary way, with
+ the mov assembly code instruction.
+
+ We are now ready to execute iret. If everything has been set up right,
+ we should now have a kernel that can enter user mode.
+
+11.4 Using C for User Mode Programs
+
+ When C is used as the programming language for user mode programs, it
+ is important to think about the structure of the file that will be the
+ result of the compilation.
+
+ The reason we can use ELF [18] as the file format for for the kernel
+ executable is because GRUB knows how to parse and interpret the ELF
+ file format. If we implemented an ELF parser, we could compile the user
+ mode programs into ELF binaries as well. We leave this as an exercise
+ for the reader.
+
+ One thing we can do to make it easier to develop user mode programs is
+ to allow the programs to be written in C, but compile them to flat
+ binaries instead of ELF binaries. In C the layout of the generated code
+ is more unpredictable and the entry point, main, might not be at offset
+ 0 in the binary. One common way to work around this is to add a few
+ assembly code lines placed at offset 0 which calls main:
+ extern main
+
+ section .text
+ ; push argv
+ ; push argc
+ call main
+ ; main has returned, eax is return value
+ jmp $ ; loop forever
+
+ If this code is saved in a file called start.s, then the following code
+ show an example of a linker script that places these instructions first
+ in executable (remember that start.s gets compiled to start.o):
+ OUTPUT_FORMAT("binary") /* output flat binary */
+
+ SECTIONS
+ {
+ . = 0; /* relocate to address 0 */
+
+ .text ALIGN(4):
+ {
+ start.o(.text) /* include the .text section of start.o */
+ *(.text) /* include all other .text sections */
+ }
+
+ .data ALIGN(4):
+ {
+ *(.data)
+ }
+
+ .rodata ALIGN(4):
+ {
+ *(.rodata*)
+ }
+ }
+
+ Note: *(.text) will not include the .text section of start.o again.
+
+ With this script we can write programs in C or assembler (or any other
+ language that compiles to object files linkable with ld), and it is
+ easy to load and map for the kernel (.rodata will be mapped in as
+ writeable, though).
+
+ When we compile user programs we want the following GCC flags:
+ -m32 -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles
+ -nodefaultlibs
+
+ For linking, the followings flags should be used:
+ -T link.ld -melf_i386 # emulate 32 bits ELF, the binary output is specified
+ # in the linker script
+
+ The option -T instructs the linker to use the linker script link.ld.
+
+11.4.1 A C Library
+
+ It might now be interesting to start thinking about writing a small
+ "standard library" for your programs. Some of the functionality
+ requires [186]system calls to work, but some, such as the functions in
+ string.h, does not.
+
+11.5 Further Reading
+
+ * Gustavo Duarte has an article on privilege levels:
+ [187]http://duartes.org/gustavo/blog/post/cpu-rings-privilege-and-p
+ rotection
+
+12 File Systems
+
+ We are not required to have file systems in our operating system, but
+ it is a very usable abstraction, and it often plays a central part of
+ many operating systems, especially UNIX-like operating systems. Before
+ we start the process of supporting multiple processes and system calls
+ we might want to consider implementing a simple file system.
+
+12.1 Why a File System?
+
+ How do we specify what programs to run in our OS? Which is the first
+ program to run? How do programs output data or read input?
+
+ In UNIX-like systems, with their almost-everything-is-a-file
+ convention, these problems are solved by the file system. (It might
+ also be interesting to read a bit about the Plan 9 project, which takes
+ this idea one step further.)
+
+12.2 A Simple Read-Only File System
+
+ The simplest file system might be what we already have - one file,
+ existing only in RAM, loaded by GRUB before the kernel starts. When the
+ kernel and operating system grows this is probably too limiting.
+
+ A file system that is slightly more advanced than just the bits of one
+ file is a file with metadata. The metadata can describe the type of the
+ file, the size of the file and so on. A utility program can be created
+ that runs at build time, adding this metadata to a file. This way, a
+ "file system in a file" can be constructed by concatenating several
+ files with metadata into one large file. The result of this technique
+ is a read-only file system that resides in memory (once GRUB has loaded
+ the file).
+
+ The program constructing the file system can traverse a directory on
+ the host system and add all subdirectories and files as part of the
+ target file system. Each object in the file system (directory or file)
+ can consist of a header and a body, where the body of a file is the
+ actual file and the body of a directory is a list of entries - names
+ and "addresses" of other files and directories.
+
+ Each object in this file system will become contiguous, so they will be
+ easy to read from memory for the kernel. All objects will also have a
+ fixed size (except for the last one, which can grow), therefore it is
+ difficult to add new files or modify existing ones.
+
+12.3 Inodes and Writable File Systems
+
+ When the need for a writable file system arises, then it is a good idea
+ to look into the concept of an inode. See the section [188]"Further
+ Reading" for recommended reading.
+
+12.4 A Virtual File System
+
+ What abstraction should be used for reading and writing to devices such
+ as the screen and the keyboard?
+
+ A virtual file system (VFS) creates an abstraction on top of the
+ concrete file systems. A VFS mainly supplies the path system and file
+ hierarchy, it delegates operations on files to the underlying file
+ systems. The original paper on VFS is succinct and well worth a read.
+ See the section [189]"Further Reading" for a reference.
+
+ With a VFS we could mount a special file system on the path /dev. This
+ file system would handle all devices such as keyboards and the console.
+ However, one could also take the traditional UNIX approach, with
+ major/minor device numbers and mknod to create special files for
+ devices. Which approach you think is the most appropriate is up to you,
+ there is no right or wrong when building abstraction layers (although
+ some abstractions turn out way more useful than others).
+
+12.5 Further Reading
+
+ * The ideas behind the Plan 9 operating systems is worth taking a
+ look at: [190]http://plan9.bell-labs.com/plan9/index.html
+ * Wikipedia's page on inodes: [191]http://en.wikipedia.org/wiki/Inode
+ and the inode pointer structure:
+ [192]http://en.wikipedia.org/wiki/Inode_pointer_structure.
+ * The original paper on the concept of vnodes and a virtual file
+ system is quite interesting:
+ [193]http://www.arl.wustl.edu/~fredk/Courses/cs523/fall01/Papers/kl
+ eiman86vnodes.pdf
+ * Poul-Henning Kamp discusses the idea of a special file system for
+ /dev in
+ [194]http://static.usenix.org/publications/library/proceedings/bsdc
+ on02/full_papers/kamp/kamp_html/index.html
+
+13 System Calls
+
+ System calls is the way user-mode applications interact with the kernel
+ - to ask for resources, request operations to be performed, etc. The
+ system call API is the part of the kernel that is most exposed to the
+ users, therefore its design requires some thought.
+
+13.1 Designing System Calls
+
+ It is up to us, the kernel developers, to design the system calls that
+ application developers can use. We can draw inspiration from the POSIX
+ standards or, if they seem like too much work, just look at the ones
+ for Linux, and pick and choose. See the section [195]"Further Reading"
+ at the end of the chapter for references.
+
+13.2 Implementing System Calls
+
+ System calls are traditionally invoked with software interrupts. The
+ user applications put the appropriate values in registers or on the
+ stack and then initiates a pre-defined interrupt which transfers
+ execution to the kernel. The interrupt number used is dependent on the
+ kernel, Linux uses the number 0x80 to identify that an interrupt is
+ intended as a system call.
+
+ When system calls are executed, the current privilege level is
+ typically changed from PL3 to PL0 (if the application is running in
+ user mode). To allow this, the DPL of the entry in the IDT for the
+ system call interrupt needs to allow PL3 access.
+
+ Whenever inter-privilege level interrupts occur, the processor pushes a
+ few important registers onto the stack - the same ones we used to enter
+ user mode [196]before, see figure 6-4, section 6.12.1, in the Intel
+ manual [33]. What stack is used? The same section in [33] specifies
+ that if an interrupt leads to code executing at a numerically lower
+ privilege level, a stack switch occurs. The new values for the
+ registers ss and esp is loaded from the current Task State Segment
+ (TSS). The TSS structure is specified in figure 7-2, section 7.2.1 of
+ the Intel manual [33].
+
+ To enable system calls we need to setup a TSS before entering user
+ mode. Setting it up can be done in C by setting the ss0 and esp0 fields
+ of a "packed struct" that represents a TSS. Before loading the "packed
+ struct" into the processor, a TSS descriptor has to be added to the
+ GDT. The structure of the TSS descriptor is described in section 7.2.2
+ in [33].
+
+ You specify the current TSS segment selector by loading it into the tr
+ register with the ltr assembly code instruction. If the TSS segment
+ descriptor has index 5, and thus offset 5 * 8 = 40 = 0x28, this is the
+ value that should be loaded into the register tr.
+
+ When we entered user mode before in the chapter [197]"Entering User
+ Mode" we disabled interrupts when executing in PL3. Since system calls
+ are implemented using interrupts, interrupts must be enabled in user
+ mode. Setting the IF flag bit in the eflags value on the stack will
+ make iret enable interrupts (since the eflags value on the stack will
+ be loaded into the eflags register by the assembly code instruction
+ iret).
+
+13.3 Further Reading
+
+ * The Wikipedia page on POSIX, with links to the specifications:
+ [198]http://en.wikipedia.org/wiki/POSIX
+ * A list of system calls used in Linux:
+ [199]http://bluemaster.iu.hio.no/edu/dark/lin-asm/syscalls.html
+ * The Wikipedia page on system calls:
+ [200]http://en.wikipedia.org/wiki/System_call
+ * The Intel manual [33] sections on interrupts (chapter 6) and TSS
+ (chapter 7) are where you get all the details you need.
+
+14 Multitasking
+
+ How do you make multiple processes appear to run at the same time?
+ Today, this question has two answers:
+ * With the availability of multi-core processors, or on system with
+ multiple processors, two processes can actually run at the same
+ time by running two processes on different cores or processors.
+ * Fake it. That is, switch rapidly (faster than a human can notice)
+ between the processes. At any given moment there is only one
+ process executing, but the rapid switching gives the impression
+ that they are running "at the same time".
+
+ Since the operating system created in this book does not support
+ multi-core processors or multiple processors the only option is to fake
+ it. The part of the operating system responsible for rapidly switching
+ between the processes is called the scheduling algorithm.
+
+14.1 Creating New Processes
+
+ Creating new processes is usually done with two different system calls:
+ fork and exec. fork creates an exact copy of the currently running
+ process, while exec replaces the current process with one that is
+ specified by a path to the location of a program in the file system. Of
+ these two we recommend that you start implementing exec, since this
+ system call will do almost exactly the same steps as described in the
+ section [201]"Setting up for user mode" in the chapter [202]"User
+ Mode".
+
+14.2 Cooperative Scheduling with Yielding
+
+ The easiest way to achieve rapid switching between processes is if the
+ processes themselves are responsible for the switching. The processes
+ run for a while and then tell the OS (via a system call) that it can
+ now switch to another process. Giving up the control of CPU to another
+ process is called yielding and when the processes themselves are
+ responsible for the scheduling it's called cooperative scheduling,
+ since all the processes must cooperate with each other.
+
+ When a process yields the process' entire state must be saved (all the
+ registers), preferably on the kernel heap in a structure that
+ represents a process. When changing to a new process all the registers
+ must be restored from the saved values.
+
+ Scheduling can be implemented by keeping a list of which processes are
+ running. The system call yield should then run the next process in the
+ list and put the current one last (other schemes are possible, but this
+ is a simple one).
+
+ The transfer of control to the new process is done via the iret
+ assembly code instruction in exactly the same way as explained in the
+ section [203]"Entering user mode" in the chapter [204]"User Mode".
+
+ We strongly recommend that you start to implement support for multiple
+ processes by implementing cooperative scheduling. We further recommend
+ that you have a working solution for both exec, fork and yield before
+ implementing preemptive scheduling. Since cooperative scheduling is
+ deterministic, it is much easier to debug than preemptive scheduling.
+
+14.3 Preemptive Scheduling with Interrupts
+
+ Instead of letting the processes themselves manage when to change to
+ another process the OS can switch processes automatically after a short
+ period of time. The OS can set up the programmable interval timer (PIT)
+ to raise an interrupt after a short period of time, for example 20 ms.
+ In the interrupt handler for the PIT interrupt the OS will change the
+ running process to a new one. This way the processes themselves don't
+ need to worry about scheduling. This kind of scheduling is called
+ preemptive scheduling.
+
+14.3.1 Programmable Interval Timer
+
+ To be able to do preemptive scheduling the PIT must first be configured
+ to raise interrupts every x milliseconds, where x should be
+ configurable.
+
+ The configuration of the PIT is very similar to the configuration of
+ other hardware devices: a byte is sent to an I/O port. The command port
+ of the PIT is 0x43. To read about all the configuration options, see
+ the article about the PIT on OSDev [39]. We use the following options:
+ * Raise interrupts (use channel 0)
+ * Send the divider as low byte then high byte (see next section for
+ an explanation)
+ * Use a square wave
+ * Use binary mode
+
+ This results in the configuration byte 00110110.
+
+ Setting the interval for how often interrupts are to be raised is done
+ via a divider, the same way as for the serial port. Instead of sending
+ the PIT a value (e.g. in milliseconds) that says how often an interrupt
+ should be raised you send the divider. The PIT operates at 1193182 Hz
+ as default. Sending the divider 10 results in the PIT running at
+ 1193182 / 10 = 119318 Hz. The divider can only be 16 bits, so it is
+ only possible to configure the timer's frequency between 1193182 Hz and
+ 1193182 / 65535 = 18.2 Hz. We recommend that you create a function that
+ takes an interval in milliseconds and converts it to the correct
+ divider.
+
+ The divider is sent to the channel 0 data I/O port of the PIT, but
+ since only one byte can be sent at at a time, the lowest 8 bits of the
+ divider has to sent first, then the highest 8 bits of the divider can
+ be sent. The channel 0 data I/O port is located at 0x40. Again, see the
+ article on OSDev [39] for more details.
+
+14.3.2 Separate Kernel Stacks for Processes
+
+ If all processes uses the same kernel stack (the stack exposed by the
+ TSS) there will be trouble if a process is interrupted while still in
+ kernel mode. The process that is being switched to will now use the
+ same kernel stack and will overwrite what the previous process have
+ written on the stack (remember that TSS data structure points to the
+ beginning of the stack).
+
+ To solve this problem every process should have it's own kernel stack,
+ the same way that each process have their own user mode stack. When
+ switching process the TSS must be updated to point to the new process'
+ kernel stack.
+
+14.3.3 Difficulties with Preemptive Scheduling
+
+ When using preemptive scheduling one problem arises that doesn't exist
+ with cooperative scheduling. With cooperative scheduling every time a
+ process yields, it must be in user mode (privilege level 3), since
+ yield is a system call. With preemptive scheduling, the processes can
+ be interrupted in either user mode or kernel mode (privilege level 0),
+ since the process itself does not control when it gets interrupted.
+
+ Interrupting a process in kernel mode is a little bit different than
+ interrupting a process in user mode, due to the way the CPU sets up the
+ stack at interrupts. If a privilege level change occurred (the process
+ was interrupted in user mode) the CPU will push the value of the
+ process ss and esp register on the stack. If no privilege level change
+ occurs (the process was interrupted in kernel mode) the CPU won't push
+ the esp register on the stack. Furthermore, if there was no privilege
+ level change, the CPU won't change stack to the one defined it the TSS.
+
+ This problem is solved by calculating what the value of esp was before
+ the interrupt. Since you know that the CPU pushes 3 things on the stack
+ when no privilege change happens and you know how much you have pushed
+ on the stack, you can calculate what the value of esp was at the time
+ of the interrupt. This is possible since the CPU won't change stacks if
+ there is no privilege level change, so the content of esp will be the
+ same as at the time of the interrupt.
+
+ To further complicate things, one must think of how to handle case when
+ switching to a new process that should be running in kernel mode. Since
+ iret is being used without a privilege level change the CPU won't
+ update the value of esp with the one placed on the stack - you must
+ update esp yourself.
+
+14.4 Further Reading
+
+ * For more information about different scheduling algorithms, see
+ [205]http://wiki.osdev.org/Scheduling_Algorithms
+
+14.4 References
+
+ [1] Andrew Tanenbaum, 2007. Modern operating systems, 3rd edition.
+ Prentice Hall, Inc.,
+
+ [2] The royal institute of technology, [206]http://www.kth.se,
+
+ [3] Wikipedia, Hexadecimal,
+ [207]http://en.wikipedia.org/wiki/Hexadecimal,
+
+ [4] OSDev, OSDev, [208]http://wiki.osdev.org/Main_Page,
+
+ [5] James Molloy, James m's kernel development tutorial,
+ [209]http://www.jamesmolloy.co.uk/tutorial_html/,
+
+ [6] Canonical Ltd, Ubuntu, [210]http://www.ubuntu.com/,
+
+ [7] Oracle, Oracle vM virtualBox, [211]http://www.virtualbox.org/,
+
+ [8] Dennis M. Ritchie Brian W. Kernighan, 1988. The c programming
+ language, second edition. Prentice Hall, Inc.,
+
+ [9] Wikipedia, C (programming language),
+ [212]http://en.wikipedia.org/wiki/C_(programming_language),
+
+ [10] Free Software Foundation, GCC, the gNU compiler collection,
+ [213]http://gcc.gnu.org/,
+
+ [11] NASM, NASM: The netwide assembler, [214]http://www.nasm.us/,
+
+ [12] Wikipedia, Bash,
+ [215]http://en.wikipedia.org/wiki/Bash_%28Unix_shell%29,
+
+ [13] Free Software Foundation, GNU make,
+ [216]http://www.gnu.org/software/make/,
+
+ [14] Volker Ruppert, bochs: The open souce iA-32 emulation project,
+ [217]http://bochs.sourceforge.net/,
+
+ [15] QEMU, QEMU, [218]http://wiki.qemu.org/Main_Page,
+
+ [16] Wikipedia, BIOS, [219]https://en.wikipedia.org/wiki/BIOS,
+
+ [17] Free Software Foundation, GNU gRUB,
+ [220]http://www.gnu.org/software/grub/,
+
+ [18] Wikipedia, Executable and linkable format,
+ [221]http://en.wikipedia.org/wiki/Executable_and_Linkable_Format,
+
+ [19] Free Software Foundation, Multiboot specification version 0.6.96,
+ [222]http://www.gnu.org/software/ grub/manual/multiboot/multiboot.html,
+
+ [20] GNU, GNU binutils, [223]http://www.gnu.org/software/binutils/,
+
+ [21] Lars Nodeen, Bug #426419: configure: error: GRUB requires a
+ working absolute objcopy,
+ [224]https://bugs.launchpad.net/ubuntu/+source/grub/+bug/426419,
+
+ [22] Wikipedia, ISO image, [225]http://en.wikipedia.org/wiki/ISO_image,
+
+ [23] Bochs, bochsrc,
+ [226]http://bochs.sourceforge.net/doc/docbook/user/bochsrc.html,
+
+ [24] NASM, RESB and friends: Declaring uninitialized data,
+ [227]http://www.nasm.us/doc/nasmdoc3.htm,
+
+ [25] Wikipedia, x86 calling conventions,
+ [228]http://en.wikipedia.org/wiki/X86_calling_conventions,
+
+ [26] Wikipedia, Framebuffer,
+ [229]http://en.wikipedia.org/wiki/Framebuffer,
+
+ [27] Wikipedia, VGA-compatible text mode,
+ [230]http://en.wikipedia.org/wiki/VGA-compatible_text_mode,
+
+ [28] Wikipedia, ASCII, [231]https://en.wikipedia.org/wiki/Ascii,
+
+ [29] OSDev, VGA hardware, [232]http://wiki.osdev.org/VGA_Hardware,
+
+ [30] Wikipedia, Serial port,
+ [233]http://en.wikipedia.org/wiki/Serial_port,
+
+ [31] OSDev, Serial ports, [234]http://wiki.osdev.org/Serial_ports,
+
+ [32] WikiBooks, Serial programming/8250 uART programming,
+ [235]http://en.wikibooks.org/wiki/Serial_Programming/
+ 8250_UART_Programming,
+
+ [33] Intel, Intel 64 and iA-32 architectures software developer's
+ manual vol. 3A, [236]http://www.intel.com/content/
+ www/us/en/architecture-and-technology/64-ia-32-architectures-software-d
+ eveloper-vol-3a-part-1-manual.html/,
+
+ [34] NASM, Multi-line macros,
+ [237]http://www.nasm.us/doc/nasmdoc4.html#section-4.3,
+
+ [35] SIGOPS, i386 interrupt handling,
+ [238]http://www.acm.uiuc.edu/sigops/roll_your_own/i386/irq.html,
+
+ [36] Andries Brouwer, Keyboard scancodes, [239]http://www.win.tue.nl/,
+
+ [37] Steve Chamberlain, Using ld, the gNU linker,
+ [240]http://www.math.utah.edu/docs/info/ld_toc.html,
+
+ [38] OSDev, Page frame allocation,
+ [241]http://wiki.osdev.org/Page_Frame_Allocation,
+
+ [39] OSDev, Programmable interval timer,
+ [242]http://wiki.osdev.org/Programmable_Interval_Timer,
+ __________________________________________________________________
+
+ 1. The bootloader must fit into the master boot record (MBR) boot
+ sector of a hard drive, which is only 512 bytes large.[243]©
+
+References
+
+ 1. https://github.com/littleosbook/littleosbook/
+ 2. https://littleosbook.github.io/book.pdf
+ 3. https://littleosbook.github.io/#introduction
+ 4. https://littleosbook.github.io/#about-the-book
+ 5. https://littleosbook.github.io/#the-reader
+ 6. https://littleosbook.github.io/#credits-thanks-and-acknowledgements
+ 7. https://littleosbook.github.io/#contributors
+ 8. https://littleosbook.github.io/#changes-and-corrections
+ 9. https://littleosbook.github.io/#issues-and-where-to-get-help
+ 10. https://littleosbook.github.io/#license
+ 11. https://littleosbook.github.io/#first-steps
+ 12. https://littleosbook.github.io/#tools
+ 13. https://littleosbook.github.io/#quick-setup
+ 14. https://littleosbook.github.io/#programming-languages
+ 15. https://littleosbook.github.io/#host-operating-system
+ 16. https://littleosbook.github.io/#build-system
+ 17. https://littleosbook.github.io/#virtual-machine
+ 18. https://littleosbook.github.io/#booting
+ 19. https://littleosbook.github.io/#bios
+ 20. https://littleosbook.github.io/#the-bootloader
+ 21. https://littleosbook.github.io/#the-operating-system
+ 22. https://littleosbook.github.io/#hello-cafebabe
+ 23. https://littleosbook.github.io/#compiling-the-operating-system
+ 24. https://littleosbook.github.io/#linking-the-kernel
+ 25. https://littleosbook.github.io/#obtaining-grub
+ 26. https://littleosbook.github.io/#building-an-iso-image
+ 27. https://littleosbook.github.io/#running-bochs
+ 28. https://littleosbook.github.io/#further-reading
+ 29. https://littleosbook.github.io/#getting-to-c
+ 30. https://littleosbook.github.io/#setting-up-a-stack
+ 31. https://littleosbook.github.io/#calling-c-code-from-assembly
+ 32. https://littleosbook.github.io/#packing-structs
+ 33. https://littleosbook.github.io/#compiling-c-code
+ 34. https://littleosbook.github.io/#build-tools
+ 35. https://littleosbook.github.io/#further-reading-1
+ 36. https://littleosbook.github.io/#output
+ 37. https://littleosbook.github.io/#interacting-with-the-hardware
+ 38. https://littleosbook.github.io/#the-framebuffer
+ 39. https://littleosbook.github.io/#writing-text
+ 40. https://littleosbook.github.io/#moving-the-cursor
+ 41. https://littleosbook.github.io/#the-driver
+ 42. https://littleosbook.github.io/#the-serial-ports
+ 43. https://littleosbook.github.io/#configuring-the-serial-port
+ 44. https://littleosbook.github.io/#configuring-the-line
+ 45. https://littleosbook.github.io/#configuring-the-buffers
+ 46. https://littleosbook.github.io/#configuring-the-modem
+ 47. https://littleosbook.github.io/#writing-data-to-the-serial-port
+ 48. https://littleosbook.github.io/#configuring-bochs
+ 49. https://littleosbook.github.io/#the-driver-1
+ 50. https://littleosbook.github.io/#further-reading-2
+ 51. https://littleosbook.github.io/#segmentation
+ 52. https://littleosbook.github.io/#accessing-memory
+ 53. https://littleosbook.github.io/#the-global-descriptor-table-gdt
+ 54. https://littleosbook.github.io/#loading-the-gdt
+ 55. https://littleosbook.github.io/#further-reading-3
+ 56. https://littleosbook.github.io/#interrupts-and-input
+ 57. https://littleosbook.github.io/#interrupts-handlers
+ 58. https://littleosbook.github.io/#creating-an-entry-in-the-idt
+ 59. https://littleosbook.github.io/#handling-an-interrupt
+ 60. https://littleosbook.github.io/#creating-a-generic-interrupt-handler
+ 61. https://littleosbook.github.io/#loading-the-idt
+ 62. https://littleosbook.github.io/#programmable-interrupt-controller-pic
+ 63. https://littleosbook.github.io/#reading-input-from-the-keyboard
+ 64. https://littleosbook.github.io/#further-reading-4
+ 65. https://littleosbook.github.io/#the-road-to-user-mode
+ 66. https://littleosbook.github.io/#loading-an-external-program
+ 67. https://littleosbook.github.io/#grub-modules
+ 68. https://littleosbook.github.io/#executing-a-program
+ 69. https://littleosbook.github.io/#a-very-simple-program
+ 70. https://littleosbook.github.io/#compiling
+ 71. https://littleosbook.github.io/#finding-the-program-in-memory
+ 72. https://littleosbook.github.io/#jumping-to-the-code
+ 73. https://littleosbook.github.io/#the-beginning-of-user-mode
+ 74. https://littleosbook.github.io/#a-short-introduction-to-virtual-memory
+ 75. https://littleosbook.github.io/#virtual-memory-through-segmentation
+ 76. https://littleosbook.github.io/#further-reading-5
+ 77. https://littleosbook.github.io/#paging
+ 78. https://littleosbook.github.io/#why-paging
+ 79. https://littleosbook.github.io/#paging-in-x86
+ 80. https://littleosbook.github.io/#identity-paging
+ 81. https://littleosbook.github.io/#enabling-paging
+ 82. https://littleosbook.github.io/#a-few-details
+ 83. https://littleosbook.github.io/#paging-and-the-kernel
+ 84. https://littleosbook.github.io/#reasons-to-not-identity-map-the-kernel
+ 85. https://littleosbook.github.io/#the-virtual-address-for-the-kernel
+ 86. https://littleosbook.github.io/#placing-the-kernel-at-0xc0000000
+ 87. https://littleosbook.github.io/#higher-half-linker-script
+ 88. https://littleosbook.github.io/#entering-the-higher-half
+ 89. https://littleosbook.github.io/#running-in-the-higher-half
+ 90. https://littleosbook.github.io/#virtual-memory-through-paging
+ 91. https://littleosbook.github.io/#further-reading-6
+ 92. https://littleosbook.github.io/#page-frame-allocation
+ 93. https://littleosbook.github.io/#managing-available-memory
+ 94. https://littleosbook.github.io/#how-much-memory-is-there
+ 95. https://littleosbook.github.io/#managing-available-memory-1
+ 96. https://littleosbook.github.io/#how-can-we-access-a-page-frame
+ 97. https://littleosbook.github.io/#a-kernel-heap
+ 98. https://littleosbook.github.io/#further-reading-7
+ 99. https://littleosbook.github.io/#user-mode
+ 100. https://littleosbook.github.io/#segments-for-user-mode
+ 101. https://littleosbook.github.io/#setting-up-for-user-mode
+ 102. https://littleosbook.github.io/#entering-user-mode
+ 103. https://littleosbook.github.io/#using-c-for-user-mode-programs
+ 104. https://littleosbook.github.io/#a-c-library
+ 105. https://littleosbook.github.io/#further-reading-8
+ 106. https://littleosbook.github.io/#file-systems
+ 107. https://littleosbook.github.io/#why-a-file-system
+ 108. https://littleosbook.github.io/#a-simple-read-only-file-system
+ 109. https://littleosbook.github.io/#inodes-and-writable-file-systems
+ 110. https://littleosbook.github.io/#a-virtual-file-system
+ 111. https://littleosbook.github.io/#further-reading-9
+ 112. https://littleosbook.github.io/#system-calls
+ 113. https://littleosbook.github.io/#designing-system-calls
+ 114. https://littleosbook.github.io/#implementing-system-calls
+ 115. https://littleosbook.github.io/#further-reading-10
+ 116. https://littleosbook.github.io/#multitasking
+ 117. https://littleosbook.github.io/#creating-new-processes
+ 118. https://littleosbook.github.io/#cooperative-scheduling-with-yielding
+ 119. https://littleosbook.github.io/#preemptive-scheduling-with-interrupts
+ 120. https://littleosbook.github.io/#programmable-interval-timer
+ 121. https://littleosbook.github.io/#separate-kernel-stacks-for-processes
+ 122. https://littleosbook.github.io/#difficulties-with-preemptive-scheduling
+ 123. https://littleosbook.github.io/#further-reading-11
+ 124. https://littleosbook.github.io/#first-steps
+ 125. https://littleosbook.github.io/#getting-to-c
+ 126. https://littleosbook.github.io/#output
+ 127. https://littleosbook.github.io/#segmentation
+ 128. https://littleosbook.github.io/#interrupts-and-input
+ 129. https://littleosbook.github.io/#the-road-to-user-mode
+ 130. https://littleosbook.github.io/#a-short-introduction-to-virtual-memory
+ 131. https://littleosbook.github.io/#paging
+ 132. https://littleosbook.github.io/#page-frame-allocation
+ 133. https://littleosbook.github.io/#user-mode
+ 134. https://littleosbook.github.io/#file-systems
+ 135. https://littleosbook.github.io/#system-calls
+ 136. https://littleosbook.github.io/#multitasking
+ 137. http://progit.org/
+ 138. https://github.com/alexschneider
+ 139. https://github.com/Avidanborisov
+ 140. https://github.com/nirs
+ 141. https://github.com/kedarmhaswade
+ 142. https://github.com/vamanea
+ 143. https://github.com/ansjob
+ 144. https://github.com/littleosbook/littleosbook/issues
+ 145. http://creativecommons.org/licenses/by-nc-sa/3.0/us/
+ 146. https://littleosbook.github.io/#fn1
+ 147. https://littleosbook.github.io/#getting-to-c
+ 148. ftp://alpha.gnu.org/gnu/grub/grub-0.97.tar.gz
+ 149. http://littleosbook.github.com/files/stage2_eltorito
+ 150. http://duartes.org/gustavo/blog/post/how-computers-boot-up
+ 151. http://duartes.org/gustavo/blog/post/kernel-boot-process
+ 152. http://wiki.osdev.org/Boot_Sequence
+ 153. https://littleosbook.github.io/#the-framebuffer
+ 154. http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming#UART_Registers
+ 155. http://wiki.osdev.org/Serial_ports
+ 156. https://littleosbook.github.io/#paging
+ 157. https://littleosbook.github.io/#packing-structs
+ 158. http://wiki.osdev.org/Segmentation
+ 159. http://en.wikipedia.org/wiki/X86_memory_segmentation
+ 160. https://littleosbook.github.io/#getting-to-c
+ 161. http://wiki.osdev.org/Interrupts
+ 162. https://littleosbook.github.io/#scheduling
+ 163. https://littleosbook.github.io/#paging
+ 164. http://www.gnu.org/software/grub/manual/multiboot/html_node/multiboot.h.html
+ 165. https://littleosbook.github.io/#segmentation
+ 166. https://littleosbook.github.io/#paging
+ 167. https://littleosbook.github.io/#page-frame-allocation
+ 168. https://littleosbook.github.io/#paging
+ 169. https://littleosbook.github.io/#page-frame-allocation
+ 170. http://lwn.net/Articles/253361/
+ 171. http://duartes.org/gustavo/blog/post/memory-translation-and-segmentation
+ 172. https://littleosbook.github.io/#user-mode
+ 173. https://littleosbook.github.io/#linking-the-kernel
+ 174. https://littleosbook.github.io/#linking-the-kernel
+ 175. https://littleosbook.github.io/#page-frame-allocation
+ 176. http://en.wikipedia.org/wiki/Paging
+ 177. http://wiki.osdev.org/Paging
+ 178. http://wiki.osdev.org/Higher_Half_bare_bones
+ 179. http://duartes.org/gustavo/blog/post/anatomy-of-a-program-in-memory
+ 180. http://flint.cs.yale.edu/cs422/doc/ELF_Format.pdf
+ 181. http://wiki.osdev.org/Page_Frame_Allocation
+ 182. https://littleosbook.github.io/#the-global-descriptor-table-gdt
+ 183. https://littleosbook.github.io/#segmentation
+ 184. https://littleosbook.github.io/#system-calls
+ 185. https://littleosbook.github.io/#creating-and-loading-the-gdt
+ 186. https://littleosbook.github.io/#system-calls
+ 187. http://duartes.org/gustavo/blog/post/cpu-rings-privilege-and-protection
+ 188. https://littleosbook.github.io/#further-reading-6
+ 189. https://littleosbook.github.io/#further-reading-6
+ 190. http://plan9.bell-labs.com/plan9/index.html
+ 191. http://en.wikipedia.org/wiki/Inode
+ 192. http://en.wikipedia.org/wiki/Inode_pointer_structure
+ 193. http://www.arl.wustl.edu/~fredk/Courses/cs523/fall01/Papers/kleiman86vnodes.pdf
+ 194. http://static.usenix.org/publications/library/proceedings/bsdcon02/full_papers/kamp/kamp_html/index.html
+ 195. https://littleosbook.github.io/#further-reading-7
+ 196. https://littleosbook.github.io/#user-mode
+ 197. https://littleosbook.github.io/#entering-user-mode
+ 198. http://en.wikipedia.org/wiki/POSIX
+ 199. http://bluemaster.iu.hio.no/edu/dark/lin-asm/syscalls.html
+ 200. http://en.wikipedia.org/wiki/System_call
+ 201. https://littleosbook.github.io/#setting-up-for-user-mode
+ 202. https://littleosbook.github.io/#user-mode
+ 203. https://littleosbook.github.io/#entering-user-mode
+ 204. https://littleosbook.github.io/#user-mode
+ 205. http://wiki.osdev.org/Scheduling_Algorithms
+ 206. http://www.kth.se/
+ 207. http://en.wikipedia.org/wiki/Hexadecimal
+ 208. http://wiki.osdev.org/Main_Page
+ 209. http://www.jamesmolloy.co.uk/tutorial_html/
+ 210. http://www.ubuntu.com/
+ 211. http://www.virtualbox.org/
+ 212. http://en.wikipedia.org/wiki/C_(programming_language)
+ 213. http://gcc.gnu.org/
+ 214. http://www.nasm.us/
+ 215. http://en.wikipedia.org/wiki/Bash_%28Unix_shell%29
+ 216. http://www.gnu.org/software/make/
+ 217. http://bochs.sourceforge.net/
+ 218. http://wiki.qemu.org/Main_Page
+ 219. https://en.wikipedia.org/wiki/BIOS
+ 220. http://www.gnu.org/software/grub/
+ 221. http://en.wikipedia.org/wiki/Executable_and_Linkable_Format
+ 222. http://www.gnu.org/software/grub/manual/multiboot/multiboot.html
+ 223. http://www.gnu.org/software/binutils/
+ 224. https://bugs.launchpad.net/ubuntu/+source/grub/+bug/426419
+ 225. http://en.wikipedia.org/wiki/ISO_image
+ 226. http://bochs.sourceforge.net/doc/docbook/user/bochsrc.html
+ 227. http://www.nasm.us/doc/nasmdoc3.htm
+ 228. http://en.wikipedia.org/wiki/X86_calling_conventions
+ 229. http://en.wikipedia.org/wiki/Framebuffer
+ 230. http://en.wikipedia.org/wiki/VGA-compatible_text_mode
+ 231. https://en.wikipedia.org/wiki/Ascii
+ 232. http://wiki.osdev.org/VGA_Hardware
+ 233. http://en.wikipedia.org/wiki/Serial_port
+ 234. http://wiki.osdev.org/Serial_ports
+ 235. http://en.wikibooks.org/wiki/Serial_Programming/8250_UART_Programming
+ 236. http://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-3a-part-1-manual.html/
+ 237. http://www.nasm.us/doc/nasmdoc4.html#section-4.3
+ 238. http://www.acm.uiuc.edu/sigops/roll_your_own/i386/irq.html
+ 239. http://www.win.tue.nl/
+ 240. http://www.math.utah.edu/docs/info/ld_toc.html
+ 241. http://wiki.osdev.org/Page_Frame_Allocation
+ 242. http://wiki.osdev.org/Programmable_Interval_Timer
+ 243. https://littleosbook.github.io/#fnref1