logo资料库

从零开始写一个简单的操作系统.pdf

第1页 / 共77页
第2页 / 共77页
第3页 / 共77页
第4页 / 共77页
第5页 / 共77页
第6页 / 共77页
第7页 / 共77页
第8页 / 共77页
资料共77页,剩余部分请下载后查看
Contents
Introduction
Computer Architecture and the Boot Process
The Boot Process
BIOS, Boot Blocks, and the Magic Number
CPU Emulation
Bochs: A x86 CPU Emulator
QEmu
The Usefulness of Hexadecimal Notation
Boot Sector Programming (in 16-bit Real Mode)
Boot Sector Re-visited
16-bit Real Mode
Erm, Hello?
Interrupts
CPU Registers
Putting it all Together
Hello, World!
Memory, Addresses, and Labels
'X' Marks the Spot
Question 1
Defining Strings
Using the Stack
Question 2
Control Structures
Question 3
Calling Functions
Include Files
Putting it all Together
Question 4
Summary
Nurse, Fetch me my Steth-o-scope
Question 5 (Advanced)
Reading the Disk
Extended Memory Access Using Segments
How Disk Drives Work
Using BIOS to Read the Disk
Putting it all Together
Entering 32-bit Protected Mode
Adapting to Life Without BIOS
Understanding the Global Descriptor Table
Defining the GDT in Assembly
Making the Switch
Putting it all Together
Writing, Building, and Loading Your Kernel
Understanding C Compilation
Generating Raw Machine Code
Local Variables
Calling Functions
Pointers, Addresses, and Data
Executing our Kernel Code
Writing our Kernel
Creating a Boot Sector to Bootstrap our Kernel
Finding Our Way into the Kernel
Automating Builds with Make
Organising Our Operating System's Code Base
C Primer
The Pre-processor and Directives
Function Declarations and Header Files
Developing Essential Device Drivers and a Filesystem
Hardware Input/Output
I/O Buses
I/O Programming
Direct Memory Access
Screen Driver
Understanding the Display Device
Basic Screen Driver Implementation
Scrolling the Screen
Handling Interrupts
Keyboard Driver
Hard-disk Driver
File System
Implementing Processes
Single Processing
Multi-processing
Summary
Bibliography
Writing a Simple Operating System — from Scratch i by Nick Blundell School of Computer Science, University of Birmingham, UK Draft: December 2, 2010 Copyright c 2009–2010 Nick Blundell
Contents Contents 1 Introduction 2 Computer Architecture and the Boot Process BIOS, Boot Blocks, and the Magic Number 2.1 The Boot Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.2 . . . . . . . . . . . . . . . . 2.3 CPU Emulation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Bochs: A x86 CPU Emulator . . . . . . . . . . . . . . . . . . . QEmu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4 The Usefulness of Hexadecimal Notation . . . . . . . . . . . . . . . . . . 2.3.1 2.3.2 ii 1 3 3 4 5 6 6 6 3 Boot Sector Programming (in 16-bit Real Mode) 3.1 3.2 3.3 3.4 Hello, World! 8 Boot Sector Re-visited . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 16-bit Real Mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Erm, Hello? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Interrupts 3.3.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 3.3.2 CPU Registers . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Putting it all Together . . . . . . . . . . . . . . . . . . . . . . . 11 3.3.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 3.4.1 Memory, Addresses, and Labels . . . . . . . . . . . . . . . . . . 13 3.4.2 ’X’ Marks the Spot . . . . . . . . . . . . . . . . . . . . . . . . . 13 Question 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Defining Strings . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Using the Stack . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Question 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Control Structures . . . . . . . . . . . . . . . . . . . . . . . . . 17 Question 3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Calling Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Include Files . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Putting it all Together . . . . . . . . . . . . . . . . . . . . . . . 21 Question 4 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 3.4.6 3.4.7 3.4.8 3.4.9 3.4.3 3.4.4 3.4.5 ii
CONTENTS iii 3.5.1 3.5 Nurse, Fetch me my Steth-o-scope . . . . . . . . . . . . . . . . . . . . . 22 Question 5 (Advanced) . . . . . . . . . . . . . . . . . . . . . . . 23 3.6 Reading the Disk . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 23 Extended Memory Access Using Segments . . . . . . . . . . . . 23 How Disk Drives Work . . . . . . . . . . . . . . . . . . . . . . . 24 Using BIOS to Read the Disk . . . . . . . . . . . . . . . . . . . 27 Putting it all Together . . . . . . . . . . . . . . . . . . . . . . . 28 3.6.1 3.6.2 3.6.3 3.6.4 4 Entering 32-bit Protected Mode 30 4.1 Adapting to Life Without BIOS . . . . . . . . . . . . . . . . . . . . . . . 31 4.2 Understanding the Global Descriptor Table . . . . . . . . . . . . . . . . 32 4.3 Defining the GDT in Assembly . . . . . . . . . . . . . . . . . . . . . . . 35 4.4 Making the Switch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36 Putting it all Together . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39 4.5 5 Writing, Building, and Loading Your Kernel 5.2 5.1.1 5.1.2 5.1.3 5.1.4 Executing our Kernel Code 5.2.1 Writing our Kernel 5.2.2 5.2.3 41 5.1 Understanding C Compilation . . . . . . . . . . . . . . . . . . . . . . . . 41 . . . . . . . . . . . . . . . . . . 41 Generating Raw Machine Code Local Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Calling Functions . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Pointers, Addresses, and Data . . . . . . . . . . . . . . . . . . . 47 . . . . . . . . . . . . . . . . . . . . . . . . . 49 . . . . . . . . . . . . . . . . . . . . . . . . . 50 Creating a Boot Sector to Bootstrap our Kernel . . . . . . . . . 50 Finding Our Way into the Kernel . . . . . . . . . . . . . . . . . 53 5.3 Automating Builds with Make . . . . . . . . . . . . . . . . . . . . . . . . 54 . . . . . . . . . 57 5.4 C Primer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 59 The Pre-processor and Directives . . . . . . . . . . . . . . . . . 59 Function Declarations and Header Files . . . . . . . . . . . . . . 60 Organising Our Operating System’s Code Base 5.4.1 5.4.2 5.3.1 6.2 6 Developing Essential Device Drivers and a Filesystem 62 6.1 Hardware Input/Output . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 I/O Buses . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 6.1.1 I/O Programming . . . . . . . . . . . . . . . . . . . . . . . . . . 63 6.1.2 6.1.3 Direct Memory Access . . . . . . . . . . . . . . . . . . . . . . . 65 Screen Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65 6.2.1 Understanding the Display Device . . . . . . . . . . . . . . . . . 65 Basic Screen Driver Implementation . . . . . . . . . . . . . . . . 65 6.2.2 6.2.3 Scrolling the Screen . . . . . . . . . . . . . . . . . . . . . . . . . 69 6.3 Handling Interrupts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 6.4 Keyboard Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 6.5 Hard-disk Driver . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 File System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 6.6 7 Implementing Processes 71 7.1 Single Processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71 7.2 Multi-processing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
CONTENTS 8 Summary Bibliography iv 72 73
Chapter 1 Introduction We’ve all used an operating system (OS) before (e.g. Windows XP, Linux, etc.), and perhaps we have even written some programs to run on one; but what is an OS actually there for? how much of what I see when I use a computer is done by hardware and how much is done by software? and how does the computer actually work? The late Prof. Doug Shepherd, a lively teacher of mine at Lancaster University, once reminded me amid my grumbling about some annoying programming problem that, back in the day, before he could even begin to attempt any research, he had to write his own operating system, from scratch. So it seems that, today, we take a lot for granted about how these wonderful machines actually work underneith all those layers of software that commonly come bundled with them and which are required for their day-to-day usefulness. Here, concentrating on the widely used x86 architecture CPU, we will strip bare our computer of all software and follow in Doug’s early footsteps, learning along the way about: • How a computer boots • How to write low-level programs in the barren landscape where no operating system yet exists • How to configure the CPU so that we can begin to use its extended functionality • How to bootstrap code written in a higher-level language, so that we can really start to make some progress towards our own operating system • How to create some fundamental operating system services, such as device drivers, file systems, multi-tasking processing. Note that, in terms of practical operating system functionality, this guide does not aim to be extensive, but instead aims to pool together snippets of information from many sources into a self-contained and coherent document, that will give you a hands-on experience of low-level programming, how operating systems are written, and the kind of problems they must solve. The approach taken by this guide is unique in that the particular languages and tools (e.g. assembly, C, Make, etc.) are not the focus but instead are treated as a means to an end: we will learn what we need to about these things to help us achieve our main goal. 1
CHAPTER 1. INTRODUCTION 2 This work is not intended as a replacement but rather as a stepping stone to excellent work such as the Minix project [?] and to operating system development in general.
Chapter 2 Computer Architecture and the Boot Process 2.1 The Boot Process Now, we begin our journey. When we reboot our computer, it must start up again, initially without any notion of an operating system. Somehow, it must load the operating system --- whatever variant that may be --- from some permanent storage device that is currently attached to the computer (e.g. a floppy disk, a hard disk, a USB dongle, etc.). As we will shortly discover, the pre-OS environment of your computer offers little in the way of rich services: at this stage even a simple file system would be a luxury (e.g. read and write logical files to a disk), but we have none of that. Luckily, what we do have is the Basic Input/Output Software (BIOS), a collection of software routines that are initially loaded from a chip into memory and initialised when the computer is switched on. BIOS provides auto-detection and basic control of your computer’s essential devices, such as the screen, keyboard, and hard disks. After BIOS completes some low-level tests of the hardware, particularly whether or not the installed memory is working correctly, it must boot the operating system stored on one of your devices. Here, we are reminded, though, that BIOS cannot simply load a file that represents your operating system from a disk, since BIOS has no notion of a file- system. BIOS must read specific sectors of data (usually 512 bytes in size) from specific physical locations of the disk devices, such as Cylinder 2, Head 3, Sector 5 (details of disk addressing are described later, in Section XXX). So, the easiest place for BIOS to find our OS is in the first sector of one of the disks (i.e. Cylinder 0, Head 0, Sector 0), known as the boot sector. Since some of our disks may not contain an operating systems (they may simply be connected for additional storage), then it is important that BIOS can determine whether the boot sector of a particular disk is boot code that is intended for execution or simply data. Note that the CPU does not differentiate between code and data: both can be interpreted as CPU instructions, where code is simply instructions that have been crafted by a programmer into some useful algorithm. 3
CHAPTER 2. COMPUTER ARCHITECTURE AND THE BOOT PROCESS 4 Again, an unsophisticated means is adopted here by BIOS, whereby the last two bytes of an intended boot sector must be set to the magic number 0xaa55. So, BIOS loops through each storage device (e.g. floppy drive, hard disk, CD drive, etc.), reads the boot sector into memory, and instructs the CPU to begin executing the first boot sector it finds that ends with the magic number. This is where we seize control of the computer. 2.2 BIOS, Boot Blocks, and the Magic Number If we use a binary editor, such as TextPad [?] or GHex [?], that will let us write raw byte values to a file --- rather than a standard text editor that will convert characters such as ’A’ into ASCII values --- then we can craft ourselves a simple yet valid boot sector. e9 fd ff 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 aa Figure 2.1: hexadecimal. A machine code boot sector, with each byte displayed in Note that, in Figure 2.1, the three important features are: • The initial three bytes, in hexadecimal as 0xe9, 0xfd and 0xff, are actually machine code instructions, as defined by the CPU manufacturer, to perform an endless jump. • The last two bytes, 0x55 and 0xaa, make up the magic number, which tells BIOS that this is indeed a boot block and not just data that happens to be on a drive’s boot sector. • The file is padded with zeros (’*’ indicates zeros omitted for brevity), basically to position the magic BIOS number at the end of the 512 byte disk sector. An important note on endianness. You might be wondering why the magic BIOS number was earlier described as the 16-bit value 0xaa55 but in our boot sector was written as the consecutive bytes 0x55 and 0xaa. This is because the x86 architecture handles multi-byte values in little-endian format, whereby less significant bytes proceed more significant bytes, which is contrary to our familiar numbering system --- though if our system ever switched and I had £0000005 in my bank account, I would be able to retire now, and perhaps donate a couple of quid to the needy Ex-millionaires Foundation. Compilers and assemblers can hide many issues of endianness from us by allowing us to define the types of data, such that, say, a 16-bit value is serialised automatically into machine code with its bytes in the correct order. However, it is sometimes useful,
分享到:
收藏