Merge remote-tracking branch 'LCTT/master'

This commit is contained in:
wxy 2018-03-31 23:46:23 +08:00
commit cb6d034cae
98 changed files with 867 additions and 881 deletions

View File

@ -1,29 +1,28 @@
为初学者准备的 Linux ln 命令教程5 个示例)
为初学者准备的 ln 命令教程5 个示例)
======
当我们在命令行上工作时,您可能需要在文件之间创建链接。这时,您可以可以借助一个专用命令,**ln**。本教程中,我们将通过一些简单易理解的例子来讨论此工具的基础知识。在此之前,值得一提的是,本教程所有例子都已在 Ubuntu 16.04 上测试通过。
当我们在命令行上工作时,您可能需要在文件之间创建链接。这时,您可以可以借助一个专用命令,`ln`。本教程中,我们将通过一些简单易理解的例子来讨论此工具的基础知识。在此之前,值得一提的是,本教程所有例子都已在 Ubuntu 16.04 上测试通过。
### Linux ln 命令
正如你现在所了解的ln 命令能够让您在文件之间创建链接。下面就是 ln 工具的语法(或者使用其他一些可行的语法)。
正如你现在所了解的,`ln` 命令能够让您在文件之间创建链接。下面就是 `ln` 工具的语法(或者使用其他一些可行的语法)。
```
ln [OPTION]... [-T] TARGET LINK_NAME (1st form)
ln [OPTION]... TARGET (2nd form)
ln [OPTION]... TARGET... DIRECTORY (3rd form)
ln [OPTION]... -t DIRECTORY TARGET... (4th form)
ln [OPTION]... [-T] TARGET LINK_NAME (第一种形式)
ln [OPTION]... TARGET (第二种形式)
ln [OPTION]... TARGET... DIRECTORY (第三种形式)
ln [OPTION]... -t DIRECTORY TARGET... (第四种形式)
```
下面是 ln 工具 man 文档描述的内容:
```
在第一种形式下,为 TARGET 创建一个叫 LINK_NAME 的链接。在第二种形式下,为 TARGET 在当前目录下创建一个链接( LCTT 译注:创建的为同名链接)。在第三和第四中形式中,在 DIRECTORY 目录下为每一个 TARGET 创建链接。默认创建硬链接,符号链接需要 --symbolic 选项。默认创建的每一个目标新链接的名字都不能已经存在。当创建硬链接时TARGET 文件必须存在。符号链接可以保存任意文本,如果之后解析,相对链接的解析与其父目录有关。
```
下面是 `ln` 工具 man 文档描述的内容:
> 在第一种形式下为目标位置TARGET创建一个叫 LINK_NAME 的链接。在第二种形式下为目标位置TARGET在当前目录下创建一个链接LCTT 译注:创建的为同名链接)。在第三和第四种形式中,在 DIRECTORY 目录下为每一个目标位置TARGET创建链接。默认创建硬链接符号链接需要 `--symbolic` 选项。默认创建的每一个创建的链接新链接的名字都不能已经存在。当创建硬链接时目标位置TARGET文件必须存在符号链接可以保存任意文本如果之后解析相对链接的解析与其父目录有关。
通过下面问答风格的例子,可能会给你更好的理解。但是在此之前,建议您先了解 [硬链接和软链接的区别][1].
### Q1. 如何使用 ln 命令创建硬链接?
这很简单,你只需要像下面使用 ln 命令:
这很简单,你只需要像下面使用 `ln` 命令:
```
ln [file] [hard-link-to-file]
@ -37,11 +36,11 @@ ln test.txt test_hard_link.txt
[![如何使用 ln 命令创建硬链接][2]][3]
如此,您便可以看见一个已经创建好的,名为 test_hard_link.txt 的硬链接。
如此,您便可以看见一个已经创建好的,名为 `test_hard_link.txt` 的硬链接。
### Q2. 如何使用 ln 命令创建软/符号链接?
使用 -s 命令行选项
使用 `-s` 命令行选项
```
ln -s [file] [soft-link-to-file]
@ -55,39 +54,39 @@ ln -s test.txt test_soft_link.txt
[![如何使用 ln 命令创建软/符号链接][4]][5]
test_soft_link.txt 文件就是一个软/符号链接,被天蓝色文本 [标识][6]。
`test_soft_link.txt` 文件就是一个软/符号链接,以天蓝色文本 [标识][6]。
### Q3. 如何使用 ln 命令删除既存的同名目标文件?
默认情况下ln 不允许您在目标目录下创建已存在的链接。
默认情况下,`ln` 不允许您在目标目录下创建已存在的链接。
[![ln 命令示例][7]][8]
然而,如果一定要这么做,您可以使用 **-f** 命令行选项覆盖此行为。
然而,如果一定要这么做,您可以使用 `-f` 命令行选项覆盖此行为。
[![如何使用 ln 命令创建软/符号链接][9]][10]
**贴士** : 如果您想在此删除过程中有所交互,您可以使用 **-i** 选项。
提示:如果您想在此删除过程中有所交互,您可以使用 `-i` 选项。
### Q4. 如何使用 ln 命令创建现有文件的同名备份?
如果您不想 ln 删除同名的现有文件,您可以为这些文件创建备份。使用 **-b** 即可实现此效果,以这种方式创建的备份文件,会在其文件名结尾处包含一个波浪号(~)。
如果您不想 `ln` 删除同名的现有文件,您可以为这些文件创建备份。使用 `-b` 即可实现此效果,以这种方式创建的备份文件,会在其文件名结尾处包含一个波浪号(`~`)。
[![如何使用 ln 命令创建现有文件的同名备份][11]][12]
### Q5. 如何在当前目录以外的其它目录创建链接?
使用 **-t** 选项指定一个文件目录(除了当前目录)。比如:
使用 `-t` 选项指定一个文件目录(除了当前目录)。比如:
```
ls test* | xargs ln -s -t /home/himanshu/Desktop/
```
上述命令会为所有 test* 文件(当前目录下的 test* 文件创建链接,并放到桌面目录下。
上述命令会为(当前目录下的)所有 `test*` 文件创建链接,并放到桌面目录下。
### 总结
当然,尤其对于新手来说,**ln** 并不是日常必备命令。但是,这是一个有用的命令,因为你永远不知道它什么时候能够节省你一天的时间。对于这个命令,我们已经讨论了一些实用的选项,如果你已经完成了这些,可以查询 [man 文档][13] 来了解更多详情。
当然,尤其对于新手来说,`ln` 并不是日常必备命令。但是,这是一个有用的命令,因为你永远不知道它什么时候能够节省你一天的时间。对于这个命令,我们已经讨论了一些实用的选项,如果你已经完成了这些,可以查询 [man 文档][13] 来了解更多详情。
--------------------------------------------------------------------------------

View File

@ -0,0 +1,109 @@
计算机系统进化论
======
纵观现代计算机的历史,从与系统的交互方式方面,可以划分为数个进化阶段。而我更倾向于将之归类为以下几个阶段:
1. 数字系统
2. 专用应用系统
3. 应用中心系统
4. 信息中心系统
5. 无应用系统
下面我们详细聊聊这几种分类。
### 数字系统
在我看来,[早期计算机][1],只被设计用来处理数字。它们能够加、减、乘、除。在它们中有一些能够运行像是微分和积分之类的更复杂的数学操作。
当然,如果你把字符映射成数字,它们也可以计算字符串。但这多少有点“数字的创造性使用”的意思,而不是直接处理各种信息。
### 专用应用系统
对于更高层级的问题,纯粹的数字系统是不够的。专用应用系统被开发用来处理单一任务。它们和数字系统十分相似,但是,它们拥有足够的复杂数字计算能力。这些系统能够完成十分明确的高层级任务,像调度问题的相关计算或者其他优化问题。
这类系统为单一目的而搭建,它们解决的是单一明确的问题。
### 应用中心系统
应用中心系统是第一个真正的通用系统。它们的主要使用风格很像专用应用系统,但是它们拥有以时间片模式(一个接一个)或以多任务模式(多应用同时)运行的多个应用程序。
上世纪 70 年代的 [早期的个人电脑][3]是第一种受人们欢迎的应用中心系统。
如今的现在操作系统 —— Windows 、macOS 、大多数 GNU/Linux 桌面环境 —— 一直遵循相同的法则。
当然,应用中心系统还可以再细分为两种子类:
1. 紧密型应用中心系统
2. 松散型应用中心系统
紧密型应用中心系统像是 [Windows 3.1][4] (拥有程序管理器和文件管理器)或者甚至 [Windows 95][5] 的最初版本都没有预定义的文件夹层次。用户启动文本处理程序(像 [ WinWord ][6])并且把文件保存在 WinWord 的程序文件夹中。在使用表格处理程序的时候,又把文件保存在表格处理工具的程序文件夹中。诸如此类。用户几乎不创建自己的文件层次结构,可能由于此举的不方便、用户单方面的懒惰,或者他们认为根本没有必要。那时,每个用户拥有几十个至多几百个文件。
为了访问文件中的信息,用户常常先打开一个应用程序,然后通过程序中的“文件/打开”功能来获取处理过的数据文件。
在 Windows 平台的 [Windows 95][5] SP2 中,“[我的文档][7]”首次被使用。有了这样一个文件层次结构的样板,应用设计者开始把 “[我的文档][7]” 作为程序的默认的保存 / 打开目录,抛弃了原来将软件产品安装目录作为默认目录的做法。这样一来,用户渐渐适应了这种模式,并且开始自己维护文件夹层次。
松散型应用中心系统(通过文件管理器来提取文件)应运而生。在这种系统下,当打开一个文件的时候,操作系统会自动启动与之相关的应用程序。这是一次小而精妙的用法转变。这种应用中心系统的用法模式一直是个人电脑的主要用法模式。
然而,这种模式有很多的缺点。例如,为了防止数据提取出现问题,需要维护一个包含给定项目的所有相关文件的严格文件夹层次结构。不幸的是,人们并不总能这样做。更进一步说,[这种模式不能很好的扩展][8]。 桌面搜索引擎和高级数据组织工具(像 [tagstore][9])可以起到一点改善作用。正如研究显示的那样,只有一少部分人正在使用那些高级文件提取工具。大多数的用户不使用替代提取工具或者辅助提取技术在文件系统中寻找文件。
### 信息中心系统
解决上述需要将所有文件都放到一个文件夹的问题的可行办法之一就是从应用中心系统转换到信息中心系统。
信息中心系统将项目的所有信息联合起来,放在一个地方,放在同一个应用程序里。因此,我们再也不需要计算项目预算时,打开表格处理程序;写工程报告时,打开文本处理程序;处理图片文件时,又打开另一个工具。
上个月的预算情况在客户会议笔记的右下方,客户会议笔记又在画板的右下方,而画板又在另一些要去完成的任务的右下方。在各个层之间没有文件或者应用程序来回切换的麻烦。
早期IBM [OS/2][10]、 Microsoft [OLE][11] 和 [NeXT][12] 都做过类似的尝试。但都由于种种原因没有取得重大成功。从 [Plan 9][14] 发展而来的 [ACme][13] 是一个非常有趣的信息中心环境。它在一个应用程序中包含了[多种应用程序][15]。但是即时是它移植到了 Windows 和 GNU/Linux也从来没有成为一个引起关注的软件。
信息中心系统的现代形式是高级 [个人维基][16](像 [TheBrain][17] 和 [Microsoft OneNote][18])。
我选择的个人工具是带 [Org 模式][19] 扩展的 [GNU/Emacs][20] 平台。在用电脑的时候,我几乎不能没有 Org 模式 。为了访问外部数据资源,我创建了一个可以将多种数据导入 Org 模式的插件 —— [Memacs][20] 。我喜欢将表格数据计算放到日程任务的右下方,然后是行内图片,内部和外部链接,等等。它是一个真正的用户不用必须操心程序或者严格的层次文件系统文件夹的信息中心系统。同时,用简单的或高级的标签也可以进行多分类。一个命令可以派生多种视图。比如,一个视图有日历,待办事项。另一个视图是租借事宜列表。等等。它对 Org 模式的用户没有限制。只有你想不到,没有它做不到。
进化结束了吗? 当然没有。
### 无应用系统
我能想到这样一类操作系统,我称之为无应用系统。在下一步的发展中,系统将不需要单一领域的应用程序,即使它们能和 Org 模式一样出色。计算机直接提供一个处理信息和使用功能的友好用户接口,而不通过文件和程序。甚至连传统的操作系统也不需要。
无应用系统也可能和 [人工智能][21] 联系起来。把它想象成 [2001 太空漫游][23] 中的 [HAL 9000][22] 和星际迷航中的 [LCARS][24] 一类的东西就可以了。
从基于应用的、基于供应商的软件文化到无应用系统的转化让人很难相信。 或许,缓慢但却不断发展的开源环境,可以使一个由各种各样组织和人们贡献的真正无应用环境成型。
信息和提取、操作信息的功能,这是系统应该具有的,同时也是我们所需要的。其他的东西仅仅是为了使我们不至于分散注意力。
--------------------------------------------------------------------------------
via: http://karl-voit.at/2017/02/10/evolution-of-systems/
作者:[Karl Voit][a]
译者:[lontow](https://github.com/lontow)
校对:[wxy](https://github.com/wxy)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:http://karl-voit.at
[1]:https://en.wikipedia.org/wiki/History_of_computing_hardware
[2]:https://en.wikipedia.org/wiki/String_%2528computer_science%2529
[3]:https://en.wikipedia.org/wiki/Xerox_Alto
[4]:https://en.wikipedia.org/wiki/Windows_3.1x
[5]:https://en.wikipedia.org/wiki/Windows_95
[6]:https://en.wikipedia.org/wiki/Microsoft_Word
[7]:https://en.wikipedia.org/wiki/My_Documents
[8]:http://karl-voit.at/tagstore/downloads/Voit2012b.pdf
[9]:http://karl-voit.at/tagstore/
[10]:https://en.wikipedia.org/wiki/OS/2
[11]:https://en.wikipedia.org/wiki/Object_Linking_and_Embedding
[12]:https://en.wikipedia.org/wiki/NeXT
[13]:https://en.wikipedia.org/wiki/Acme_%2528text_editor%2529
[14]:https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs
[15]:https://en.wikipedia.org/wiki/List_of_Plan_9_applications
[16]:https://en.wikipedia.org/wiki/Personal_wiki
[17]:https://en.wikipedia.org/wiki/TheBrain
[18]:https://en.wikipedia.org/wiki/Microsoft_OneNote
[19]:../../../../tags/emacs
[20]:https://github.com/novoid/Memacs
[21]:https://en.wikipedia.org/wiki/Artificial_intelligence
[22]:https://en.wikipedia.org/wiki/HAL_9000
[23]:https://en.wikipedia.org/wiki/2001:_A_Space_Odyssey
[24]:https://en.wikipedia.org/wiki/LCARS

View File

@ -1,625 +0,0 @@
Translating by qhwdw
Caffeinated 6.828: Lab 1: Booting a PC
======
### Introduction
This lab is split into three parts. The first part concentrates on getting familiarized with x86 assembly language, the QEMU x86 emulator, and the PC's power-on bootstrap procedure. The second part examines the boot loader for our 6.828 kernel, which resides in the `boot` directory of the `lab` tree. Finally, the third part delves into the initial template for our 6.828 kernel itself, named JOS, which resides in the `kernel` directory.
#### Software Setup
The files you will need for this and subsequent lab assignments in this course are distributed using the [Git][1] version control system. To learn more about Git, take a look at the [Git user's manual][2], or, if you are already familiar with other version control systems, you may find this [CS-oriented overview of Git][3] useful.
The URL for the course Git repository is `https://exokernel.scripts.mit.edu/joslab.git`. To install the files in your Athena account, you need to clone the course repository, by running the commands below. You can log into a public Athena host with `ssh -X athena.dialup.mit.edu`.
```
athena% mkdir ~/6.828
athena% cd ~/6.828
athena% add git
athena% git clone https://exokernel.scripts.mit.edu/joslab.git lab
Cloning into lab...
athena% cd lab
athena%
```
Git allows you to keep track of the changes you make to the code. For example, if you are finished with one of the exercises, and want to checkpoint your progress, you can commit your changes by running:
```
athena% git commit -am 'my solution for lab1 exercise 9'
Created commit 60d2135: my solution for lab1 exercise 9
1 files changed, 1 insertions(+), 0 deletions(-)
athena%
```
You can keep track of your changes by using the `git diff` command. Running `git diff` will display the changes to your code since your last commit, and `git diff origin/lab1` will display the changes relative to the initial code supplied for this lab. Here, `origin/lab1` is the name of the git branch with the initial code you downloaded from our server for this assignment.
We have set up the appropriate compilers and simulators for you on Athena. To use them, run `add exokernel`. You must run this command every time you log in (or add it to your `~/.environment` file). If you get obscure errors while compiling or running `qemu`, double check that you added the course locker.
If you are working on a non-Athena machine, you'll need to install `qemu` and possibly `gcc` following the directions on the [tools page][4]. We've made several useful debugging changes to `qemu` and some of the later labs depend on these patches, so you must build your own. If your machine uses a native ELF toolchain (such as Linux and most BSD's, but notably not OS X), you can simply install `gcc` from your package manager. Otherwise, follow the directions on the tools page.
#### Hand-In Procedure
We use different Git repositories for you to hand in your lab. The hand-in repositories reside behind an SSH server. You will get your own hand-in repository, which is inaccessible by any other students. To authenticate yourself with the SSH server, you should have an RSA key pair, and let the server know your public key.
The lab code comes with a script that helps you to set up access to your hand-in repository. Before running the script, you must have an account at our [submission web interface][5]. On the login page, type in your Athena user name and click on "Mail me my password". You will receive your `6.828` password in your mailbox shortly. Note that every time you click the button, the system will assign you a new random password.
Now that you have your `6.828` password, in the `lab` directory, set up the hand-in repository by running:
```
athena% make handin-prep
Using public key from ~/.ssh/id_rsa:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0lnnkoHSi4JDFA ...
Continue? [Y/n] Y
Login to 6.828 submission website.
If you do not have an account yet, sign up at https://exokernel.scripts.mit.edu/submit/
before continuing.
Username: <your Athena username>
Password: <your 6.828 password>
Your public key has been successfully updated.
Setting up hand-in Git repository...
Adding remote repository ssh://josgit@exokernel.mit.edu/joslab.git as 'handin'.
Done! Use 'make handin' to submit your lab code.
athena%
```
The script may also ask you to generate a new key pair if you did not have one:
```
athena% make handin-prep
SSH key file ~/.ssh/id_rsa does not exists, generate one? [Y/n] Y
Generating public/private rsa key pair.
Your identification has been saved in ~/.ssh/id_rsa.
Your public key has been saved in ~/.ssh/id_rsa.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
The keyʼs randomart image is:
+--[ RSA 2048]----+
| ........ |
| ........ |
+-----------------+
Using public key from ~/.ssh/id_rsa:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0lnnkoHSi4JDFA ...
Continue? [Y/n] Y
.....
athena%
```
When you are ready to hand in your lab, first commit your changes with git commit, and then type make handin in the `lab` directory. The latter will run git push handin HEAD, which pushes the current branch to the same name on the remote `handin` repository.
```
athena% git commit -am "ready to submit my lab"
[lab1 c2e3c8b] ready to submit my lab
2 files changed, 18 insertions(+), 2 deletions(-)
athena% make handin
Handin to remote repository using 'git push handin HEAD' ...
Counting objects: 59, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (55/55), done.
Writing objects: 100% (59/59), 49.75 KiB, done.
Total 59 (delta 3), reused 0 (delta 0)
To ssh://josgit@am.csail.mit.edu/joslab.git
* [new branch] HEAD -> lab1
athena%
```
If you have made changes to your hand-in repository, an email receipt will be sent to you to confirm the submission. You can run make handin (or git push handin) as many times as you want. The late hours of your submission for a specific lab is based on the latest hand-in (push) time of the corresponding branch.
In the case that make handin does not work properly, try fixing the problem with Git commands. Or you can run make tarball. This will make a tar file for you, which you can then upload via our [web interface][5]. `make handin` provides more specific directions.
For Lab 1, you do not need to turn in answers to any of the questions below. (Do answer them for yourself though! They will help with the rest of the lab.)
We will be grading your solutions with a grading program. You can run make grade to test your solutions with the grading program.
### Part 1: PC Bootstrap
The purpose of the first exercise is to introduce you to x86 assembly language and the PC bootstrap process, and to get you started with QEMU and QEMU/GDB debugging. You will not have to write any code for this part of the lab, but you should go through it anyway for your own understanding and be prepared to answer the questions posed below.
#### Getting Started with x86 assembly
If you are not already familiar with x86 assembly language, you will quickly become familiar with it during this course! The [PC Assembly Language Book][6] is an excellent place to start. Hopefully, the book contains mixture of new and old material for you.
Warning: Unfortunately the examples in the book are written for the NASM assembler, whereas we will be using the GNU assembler. NASM uses the so-called Intel syntax while GNU uses the AT&T syntax. While semantically equivalent, an assembly file will differ quite a lot, at least superficially, depending on which syntax is used. Luckily the conversion between the two is pretty simple, and is covered in [Brennan's Guide to Inline Assembly][7].
> **Exercise 1**
>
> Familiarize yourself with the assembly language materials available on [the 6.828 reference page][8]. You don't have to read them now, but you'll almost certainly want to refer to some of this material when reading and writing x86 assembly.
We do recommend reading the section "The Syntax" in [Brennan's Guide to Inline Assembly][7]. It gives a good (and quite brief) description of the AT&T assembly syntax we'll be using with the GNU assembler in JOS.
Certainly the definitive reference for x86 assembly language programming is Intel's instruction set architecture reference, which you can find on [the 6.828 reference page][8] in two flavors: an HTML edition of the old [80386 Programmer's Reference Manual][9], which is much shorter and easier to navigate than more recent manuals but describes all of the x86 processor features that we will make use of in 6.828; and the full, latest and greatest [IA-32 Intel Architecture Software Developer's Manuals][10] from Intel, covering all the features of the most recent processors that we won't need in class but you may be interested in learning about. An equivalent (and often friendlier) set of manuals is [available from AMD][11]. Save the Intel/AMD architecture manuals for later or use them for reference when you want to look up the definitive explanation of a particular processor feature or instruction.
#### Simulating the x86
Instead of developing the operating system on a real, physical personal computer (PC), we use a program that faithfully emulates a complete PC: the code you write for the emulator will boot on a real PC too. Using an emulator simplifies debugging; you can, for example, set break points inside of the emulated x86, which is difficult to do with the silicon version of an x86.
In 6.828 we will use the [QEMU Emulator][12], a modern and relatively fast emulator. While QEMU's built-in monitor provides only limited debugging support, QEMU can act as a remote debugging target for the [GNU debugger][13] (GDB), which we'll use in this lab to step through the early boot process.
To get started, extract the Lab 1 files into your own directory on Athena as described above in "Software Setup", then type make (or gmake on BSD systems) in the `lab` directory to build the minimal 6.828 boot loader and kernel you will start with. (It's a little generous to call the code we're running here a "kernel," but we'll flesh it out throughout the semester.)
```
athena% cd lab
athena% make
+ as kern/entry.S
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
+ ld obj/kern/kernel
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 414 bytes (max 510)
+ mk obj/kern/kernel.img
```
(If you get errors like "undefined reference to `__udivdi3'", you probably don't have the 32-bit gcc multilib. If you're running Debian or Ubuntu, try installing the gcc-multilib package.)
Now you're ready to run QEMU, supplying the file `obj/kern/kernel.img`, created above, as the contents of the emulated PC's "virtual hard disk." This hard disk image contains both our boot loader (`obj/boot/boot`) and our kernel (`obj/kernel`).
```
athena% make qemu
```
This executes QEMU with the options required to set the hard disk and direct serial port output to the terminal. Some text should appear in the QEMU window:
```
Booting from Hard Disk...
6828 decimal is XXX octal!
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K>
```
Everything after '`Booting from Hard Disk...`' was printed by our skeletal JOS kernel; the `K>` is the prompt printed by the small monitor, or interactive control program, that we've included in the kernel. These lines printed by the kernel will also appear in the regular shell window from which you ran QEMU. This is because for testing and lab grading purposes we have set up the JOS kernel to write its console output not only to the virtual VGA display (as seen in the QEMU window), but also to the simulated PC's virtual serial port, which QEMU in turn outputs to its own standard output. Likewise, the JOS kernel will take input from both the keyboard and the serial port, so you can give it commands in either the VGA display window or the terminal running QEMU. Alternatively, you can use the serial console without the virtual VGA by running make qemu-nox. This may be convenient if you are SSH'd into an Athena dialup.
There are only two commands you can give to the kernel monitor, `help` and `kerninfo`.
```
K> help
help - display this list of commands
kerninfo - display information about the kernel
K> kerninfo
Special kernel symbols:
entry f010000c (virt) 0010000c (phys)
etext f0101a75 (virt) 00101a75 (phys)
edata f0112300 (virt) 00112300 (phys)
end f0112960 (virt) 00112960 (phys)
Kernel executable memory footprint: 75KB
K>
```
The `help` command is obvious, and we will shortly discuss the meaning of what the `kerninfo` command prints. Although simple, it's important to note that this kernel monitor is running "directly" on the "raw (virtual) hardware" of the simulated PC. This means that you should be able to copy the contents of `obj/kern/kernel.img` onto the first few sectors of a real hard disk, insert that hard disk into a real PC, turn it on, and see exactly the same thing on the PC's real screen as you did above in the QEMU window. (We don't recommend you do this on a real machine with useful information on its hard disk, though, because copying `kernel.img` onto the beginning of its hard disk will trash the master boot record and the beginning of the first partition, effectively causing everything previously on the hard disk to be lost!)
#### The PC's Physical Address Space
We will now dive into a bit more detail about how a PC starts up. A PC's physical address space is hard-wired to have the following general layout:
```
+------------------+ <- 0xFFFFFFFF (4GB)
| 32-bit |
| memory mapped |
| devices |
| |
/\/\/\/\/\/\/\/\/\/\
/\/\/\/\/\/\/\/\/\/\
| |
| Unused |
| |
+------------------+ <- depends on amount of RAM
| |
| |
| Extended Memory |
| |
| |
+------------------+ <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
| |
+------------------+ <- 0x00000000
```
The first PCs, which were based on the 16-bit Intel 8088 processor, were only capable of addressing 1MB of physical memory. The physical address space of an early PC would therefore start at `0x00000000` but end at `0x000FFFFF` instead of `0xFFFFFFFF`. The 640KB area marked "Low Memory" was the only random-access memory (RAM) that an early PC could use; in fact the very earliest PCs only could be configured with 16KB, 32KB, or 64KB of RAM!
The 384KB area from `0x000A0000` through `0x000FFFFF` was reserved by the hardware for special uses such as video display buffers and firmware held in non-volatile memory. The most important part of this reserved area is the Basic Input/Output System (BIOS), which occupies the 64KB region from `0x000F0000` through `0x000FFFFF`. In early PCs the BIOS was held in true read-only memory (ROM), but current PCs store the BIOS in updateable flash memory. The BIOS is responsible for performing basic system initialization such as activating the video card and checking the amount of memory installed. After performing this initialization, the BIOS loads the operating system from some appropriate location such as floppy disk, hard disk, CD-ROM, or the network, and passes control of the machine to the operating system.
When Intel finally "broke the one megabyte barrier" with the 80286 and 80386 processors, which supported 16MB and 4GB physical address spaces respectively, the PC architects nevertheless preserved the original layout for the low 1MB of physical address space in order to ensure backward compatibility with existing software. Modern PCs therefore have a "hole" in physical memory from `0x000A0000` to `0x00100000`, dividing RAM into "low" or "conventional memory" (the first 640KB) and "extended memory" (everything else). In addition, some space at the very top of the PC's 32-bit physical address space, above all physical RAM, is now commonly reserved by the BIOS for use by 32-bit PCI devices.
Recent x86 processors can support more than 4GB of physical RAM, so RAM can extend further above `0xFFFFFFFF`. In this case the BIOS must arrange to leave a second hole in the system's RAM at the top of the 32-bit addressable region, to leave room for these 32-bit devices to be mapped. Because of design limitations JOS will use only the first 256MB of a PC's physical memory anyway, so for now we will pretend that all PCs have "only" a 32-bit physical address space. But dealing with complicated physical address spaces and other aspects of hardware organization that evolved over many years is one of the important practical challenges of OS development.
#### The ROM BIOS
In this portion of the lab, you'll use QEMU's debugging facilities to investigate how an IA-32 compatible computer boots.
Open two terminal windows. In one, enter `make qemu-gdb` (or `make qemu-nox-gdb`). This starts up QEMU, but QEMU stops just before the processor executes the first instruction and waits for a debugging connection from GDB. In the second terminal, from the same directory you ran `make`, run `make gdb`. You should see something like this,
```
athena% make gdb
GNU gdb (GDB) 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
+ target remote localhost:1234
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb)
```
The `make gdb` target runs a script called `.gdbrc`, which sets up GDB to debug the 16-bit code used during early boot and directs it to attach to the listening QEMU.
The following line:
```
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
```
is GDB's disassembly of the first instruction to be executed. From this output you can conclude a few things:
* The IBM PC starts executing at physical address `0x000ffff0`, which is at the very top of the 64KB area reserved for the ROM BIOS.
* The PC starts executing with `CS = 0xf000` and `IP = 0xfff0`.
* The first instruction to be executed is a `jmp` instruction, which jumps to the segmented address `CS = 0xf000` and `IP = 0xe05b`.
Why does QEMU start like this? This is how Intel designed the 8088 processor, which IBM used in their original PC. Because the BIOS in a PC is "hard-wired" to the physical address range `0x000f0000-0x000fffff`, this design ensures that the BIOS always gets control of the machine first after power-up or any system restart - which is crucial because on power-up there is no other software anywhere in the machine's RAM that the processor could execute. The QEMU emulator comes with its own BIOS, which it places at this location in the processor's simulated physical address space. On processor reset, the (simulated) processor enters real mode and sets CS to `0xf000` and the IP to `0xfff0`, so that execution begins at that (CS:IP) segment address. How does the segmented address 0xf000:fff0 turn into a physical address?
To answer that we need to know a bit about real mode addressing. In real mode (the mode that PC starts off in), address translation works according to the formula: physical address = 16 * segment + offset. So, when the PC sets CS to `0xf000` and IP to `0xfff0`, the physical address referenced is:
```
16 * 0xf000 + 0xfff0 # in hex multiplication by 16 is
= 0xf0000 + 0xfff0 # easy--just append a 0.
= 0xffff0
```
`0xffff0` is 16 bytes before the end of the BIOS (`0x100000`). Therefore we shouldn't be surprised that the first thing that the BIOS does is `jmp` backwards to an earlier location in the BIOS; after all how much could it accomplish in just 16 bytes?
> **Exercise 2**
>
> Use GDB's `si` (Step Instruction) command to trace into the ROM BIOS for a few more instructions, and try to guess what it might be doing. You might want to look at [Phil Storrs I/O Ports Description][14], as well as other materials on the [6.828 reference materials page][8]. No need to figure out all the details - just the general idea of what the BIOS is doing first.
When the BIOS runs, it sets up an interrupt descriptor table and initializes various devices such as the VGA display. This is where the "`Starting SeaBIOS`" message you see in the QEMU window comes from.
After initializing the PCI bus and all the important devices the BIOS knows about, it searches for a bootable device such as a floppy, hard drive, or CD-ROM. Eventually, when it finds a bootable disk, the BIOS reads the boot loader from the disk and transfers control to it.
### Part 2: The Boot Loader
Floppy and hard disks for PCs are divided into 512 byte regions called sectors. A sector is the disk's minimum transfer granularity: each read or write operation must be one or more sectors in size and aligned on a sector boundary. If the disk is bootable, the first sector is called the boot sector, since this is where the boot loader code resides. When the BIOS finds a bootable floppy or hard disk, it loads the 512-byte boot sector into memory at physical addresses 0x7c00 through `0x7dff`, and then uses a `jmp` instruction to set the CS:IP to `0000:7c00`, passing control to the boot loader. Like the BIOS load address, these addresses are fairly arbitrary - but they are fixed and standardized for PCs.
The ability to boot from a CD-ROM came much later during the evolution of the PC, and as a result the PC architects took the opportunity to rethink the boot process slightly. As a result, the way a modern BIOS boots from a CD-ROM is a bit more complicated (and more powerful). CD-ROMs use a sector size of 2048 bytes instead of 512, and the BIOS can load a much larger boot image from the disk into memory (not just one sector) before transferring control to it. For more information, see the ["El Torito" Bootable CD-ROM Format Specification][15].
For 6.828, however, we will use the conventional hard drive boot mechanism, which means that our boot loader must fit into a measly 512 bytes. The boot loader consists of one assembly language source file, `boot/boot.S`, and one C source file, `boot/main.c` Look through these source files carefully and make sure you understand what's going on. The boot loader must perform two main functions:
1. First, the boot loader switches the processor from real mode to 32-bit protected mode, because it is only in this mode that software can access all the memory above 1MB in the processor's physical address space. Protected mode is described briefly in sections 1.2.7 and 1.2.8 of [PC Assembly Language][6], and in great detail in the Intel architecture manuals. At this point you only have to understand that translation of segmented addresses (segment:offset pairs) into physical addresses happens differently in protected mode, and that after the transition offsets are 32 bits instead of 16.
2. Second, the boot loader reads the kernel from the hard disk by directly accessing the IDE disk device registers via the x86's special I/O instructions. If you would like to understand better what the particular I/O instructions here mean, check out the "IDE hard drive controller" section on [the 6.828 reference page][8]. You will not need to learn much about programming specific devices in this class: writing device drivers is in practice a very important part of OS development, but from a conceptual or architectural viewpoint it is also one of the least interesting.
After you understand the boot loader source code, look at the file `obj/boot/boot.asm`. This file is a disassembly of the boot loader that our GNUmakefile creates after compiling the boot loader. This disassembly file makes it easy to see exactly where in physical memory all of the boot loader's code resides, and makes it easier to track what's happening while stepping through the boot loader in GDB. Likewise, `obj/kern/kernel.asm` contains a disassembly of the JOS kernel, which can often be useful for debugging.
You can set address breakpoints in GDB with the `b` command. For example, `b *0x7c00` sets a breakpoint at address `0x7C00`. Once at a breakpoint, you can continue execution using the `c` and `si` commands: `c` causes QEMU to continue execution until the next breakpoint (or until you press Ctrl-C in GDB), and `si N` steps through the instructions `N` at a time.
To examine instructions in memory (besides the immediate next one to be executed, which GDB prints automatically), you use the `x/i` command. This command has the syntax `x/Ni ADDR`, where `N` is the number of consecutive instructions to disassemble and `ADDR` is the memory address at which to start disassembling.
> **Exercise 3**
>
> Take a look at the [lab tools guide][16], especially the section on GDB commands. Even if you're familiar with GDB, this includes some esoteric GDB commands that are useful for OS work.
Set a breakpoint at address 0x7c00, which is where the boot sector will be loaded. Continue execution until that breakpoint. Trace through the code in `boot/boot.S`, using the source code and the disassembly file `obj/boot/boot.asm` to keep track of where you are. Also use the `x/i` command in GDB to disassemble sequences of instructions in the boot loader, and compare the original boot loader source code with both the disassembly in `obj/boot/boot.asm` and GDB.
Trace into `bootmain()` in `boot/main.c`, and then into `readsect()`. Identify the exact assembly instructions that correspond to each of the statements in `readsect()`. Trace through the rest of `readsect()` and back out into `bootmain()`, and identify the begin and end of the `for` loop that reads the remaining sectors of the kernel from the disk. Find out what code will run when the loop is finished, set a breakpoint there, and continue to that breakpoint. Then step through the remainder of the boot loader.
Be able to answer the following questions:
* At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?
* What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?
* Where is the first instruction of the kernel?
* How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?
#### Loading the Kernel
We will now look in further detail at the C language portion of the boot loader, in `boot/main.c`. But before doing so, this is a good time to stop and review some of the basics of C programming.
> **Exercise 4**
>
> Download the code for [pointers.c][17], run it, and make sure you understand where all of the printed values come from. In particular, make sure you understand where the pointer addresses in lines 1 and 6 come from, how all the values in lines 2 through 4 get there, and why the values printed in line 5 are seemingly corrupted.
>
> If you're not familiar with pointers, The C Programming Language by Brian Kernighan and Dennis Ritchie (known as 'K&R') is a good reference. Students can purchase this book (here is an [Amazon Link][18]) or find one of [MIT's 7 copies][19]. 3 copies are also available for perusal in the [SIPB Office][20].
>
> [A tutorial by Ted Jensen][21] that cites K&R heavily is available in the course readings.
>
> Warning: Unless you are already thoroughly versed in C, do not skip or even skim this reading exercise. If you do not really understand pointers in C, you will suffer untold pain and misery in subsequent labs, and then eventually come to understand them the hard way. Trust us; you don't want to find out what "the hard way" is.
To make sense out of `boot/main.c` you'll need to know what an ELF binary is. When you compile and link a C program such as the JOS kernel, the compiler transforms each C source ('`.c`') file into an object ('`.o`') file containing assembly language instructions encoded in the binary format expected by the hardware. The linker then combines all of the compiled object files into a single binary image such as `obj/kern/kernel`, which in this case is a binary in the ELF format, which stands for "Executable and Linkable Format".
Full information about this format is available in [the ELF specification][22] on [our reference page][8], but you will not need to delve very deeply into the details of this format in this class. Although as a whole the format is quite powerful and complex, most of the complex parts are for supporting dynamic loading of shared libraries, which we will not do in this class.
For purposes of 6.828, you can consider an ELF executable to be a header with loading information, followed by several program sections, each of which is a contiguous chunk of code or data intended to be loaded into memory at a specified address. The boot loader does not modify the code or data; it loads it into memory and starts executing it.
An ELF binary starts with a fixed-length ELF header, followed by a variable-length program header listing each of the program sections to be loaded. The C definitions for these ELF headers are in `inc/elf.h`. The program sections we're interested in are:
* `.text`: The program's executable instructions.
* `.rodata`: Read-only data, such as ASCII string constants produced by the C compiler. (We will not bother setting up the hardware to prohibit writing, however.)
* `.data`: The data section holds the program's initialized data, such as global variables declared with initializers like `int x = 5;`.
When the linker computes the memory layout of a program, it reserves space for uninitialized global variables, such as `int x;`, in a section called `.bss` that immediately follows `.data` in memory. C requires that "uninitialized" global variables start with a value of zero. Thus there is no need to store contents for `.bss` in the ELF binary; instead, the linker records just the address and size of the `.bss` section. The loader or the program itself must arrange to zero the `.bss` section.
Examine the full list of the names, sizes, and link addresses of all the sections in the kernel executable by typing:
```
athena% i386-jos-elf-objdump -h obj/kern/kernel
```
You can substitute `objdump` for `i386-jos-elf-objdump` if your computer uses an ELF toolchain by default like most modern Linuxen and BSDs.
You will see many more sections than the ones we listed above, but the others are not important for our purposes. Most of the others are to hold debugging information, which is typically included in the program's executable file but not loaded into memory by the program loader.
Take particular note of the "VMA" (or link address) and the "LMA" (or load address) of the `.text` section. The load address of a section is the memory address at which that section should be loaded into memory. In the ELF object, this is stored in the `ph->p_pa` field (in this case, it really is a physical address, though the ELF specification is vague on the actual meaning of this field).
The link address of a section is the memory address from which the section expects to execute. The linker encodes the link address in the binary in various ways, such as when the code needs the address of a global variable, with the result that a binary usually won't work if it is executing from an address that it is not linked for. (It is possible to generate position-independent code that does not contain any such absolute addresses. This is used extensively by modern shared libraries, but it has performance and complexity costs, so we won't be using it in 6.828.)
Typically, the link and load addresses are the same. For example, look at the `.text` section of the boot loader:
```
athena% i386-jos-elf-objdump -h obj/boot/boot.out
```
The BIOS loads the boot sector into memory starting at address 0x7c00, so this is the boot sector's load address. This is also where the boot sector executes from, so this is also its link address. We set the link address by passing `-Ttext 0x7C00` to the linker in `boot/Makefrag`, so the linker will produce the correct memory addresses in the generated code.
> **Exercise 5**
>
> Trace through the first few instructions of the boot loader again and identify the first instruction that would "break" or otherwise do the wrong thing if you were to get the boot loader's link address wrong. Then change the link address in `boot/Makefrag` to something wrong, run make clean, recompile the lab with make, and trace into the boot loader again to see what happens. Don't forget to change the link address back and make clean again afterward!
Look back at the load and link addresses for the kernel. Unlike the boot loader, these two addresses aren't the same: the kernel is telling the boot loader to load it into memory at a low address (1 megabyte), but it expects to execute from a high address. We'll dig in to how we make this work in the next section.
Besides the section information, there is one more field in the ELF header that is important to us, named `e_entry`. This field holds the link address of the entry point in the program: the memory address in the program's text section at which the program should begin executing. You can see the entry point:
```
athena% i386-jos-elf-objdump -f obj/kern/kernel
```
You should now be able to understand the minimal ELF loader in `boot/main.c`. It reads each section of the kernel from disk into memory at the section's load address and then jumps to the kernel's entry point.
> **Exercise 6**
>
> We can examine memory using GDB's x command. The [GDB manual][23] has full details, but for now, it is enough to know that the command `x/Nx ADDR` prints `N` words of memory at `ADDR`. (Note that both `x`s in the command are lowercase.) Warning: The size of a word is not a universal standard. In GNU assembly, a word is two bytes (the 'w' in xorw, which stands for word, means 2 bytes).
Reset the machine (exit QEMU/GDB and start them again). Examine the 8 words of memory at `0x00100000` at the point the BIOS enters the boot loader, and then again at the point the boot loader enters the kernel. Why are they different? What is there at the second breakpoint? (You do not really need to use QEMU to answer this question. Just think.)
### Part 3: The Kernel
We will now start to examine the minimal JOS kernel in a bit more detail. (And you will finally get to write some code!). Like the boot loader, the kernel begins with some assembly language code that sets things up so that C language code can execute properly.
#### Using virtual memory to work around position dependence
When you inspected the boot loader's link and load addresses above, they matched perfectly, but there was a (rather large) disparity between the kernel's link address (as printed by objdump) and its load address. Go back and check both and make sure you can see what we're talking about. (Linking the kernel is more complicated than the boot loader, so the link and load addresses are at the top of `kern/kernel.ld`.)
Operating system kernels often like to be linked and run at very high virtual address, such as `0xf0100000`, in order to leave the lower part of the processor's virtual address space for user programs to use. The reason for this arrangement will become clearer in the next lab.
Many machines don't have any physical memory at address `0xf0100000`, so we can't count on being able to store the kernel there. Instead, we will use the processor's memory management hardware to map virtual address `0xf0100000` (the link address at which the kernel code expects to run) to physical address `0x00100000` (where the boot loader loaded the kernel into physical memory). This way, although the kernel's virtual address is high enough to leave plenty of address space for user processes, it will be loaded in physical memory at the 1MB point in the PC's RAM, just above the BIOS ROM. This approach requires that the PC have at least a few megabytes of physical memory (so that physical address `0x00100000` works), but this is likely to be true of any PC built after about 1990.
In fact, in the next lab, we will map the entire bottom 256MB of the PC's physical address space, from physical addresses `0x00000000` through `0x0fffffff`, to virtual addresses `0xf0000000` through `0xffffffff` respectively. You should now see why JOS can only use the first 256MB of physical memory.
For now, we'll just map the first 4MB of physical memory, which will be enough to get us up and running. We do this using the hand-written, statically-initialized page directory and page table in `kern/entrypgdir.c`. For now, you don't have to understand the details of how this works, just the effect that it accomplishes. Up until `kern/entry.S` sets the `CR0_PG` flag, memory references are treated as physical addresses (strictly speaking, they're linear addresses, but boot/boot.S set up an identity mapping from linear addresses to physical addresses and we're never going to change that). Once `CR0_PG` is set, memory references are virtual addresses that get translated by the virtual memory hardware to physical addresses. `entry_pgdir` translates virtual addresses in the range `0xf0000000` through `0xf0400000` to physical addresses `0x00000000` through `0x00400000`, as well as virtual addresses `0x00000000` through `0x00400000` to physical addresses `0x00000000` through `0x00400000`. Any virtual address that is not in one of these two ranges will cause a hardware exception which, since we haven't set up interrupt handling yet, will cause QEMU to dump the machine state and exit (or endlessly reboot if you aren't using the 6.828-patched version of QEMU).
> **Exercise 7**
>
> Use QEMU and GDB to trace into the JOS kernel and stop at the `movl %eax, %cr0`. Examine memory at `0x00100000` and at `0xf0100000`. Now, single step over that instruction using the `stepi` GDB command. Again, examine memory at `0x00100000` and at `0xf0100000`. Make sure you understand what just happened.
What is the first instruction after the new mapping is established that would fail to work properly if the mapping weren't in place? Comment out the `movl %eax, %cr0` in `kern/entry.S`, trace into it, and see if you were right.
#### Formatted Printing to the Console
Most people take functions like `printf()` for granted, sometimes even thinking of them as "primitives" of the C language. But in an OS kernel, we have to implement all I/O ourselves.
Read through `kern/printf.c`, `lib/printfmt.c`, and `kern/console.c`, and make sure you understand their relationship. It will become clear in later labs why `printfmt.c` is located in the separate `lib` directory.
> **Exercise 8**
>
> We have omitted a small fragment of code - the code necessary to print octal numbers using patterns of the form "%o". Find and fill in this code fragment.
>
> Be able to answer the following questions:
>
> 1. Explain the interface between `printf.c` and `console.c`. Specifically, what function does `console.c` export? How is this function used by `printf.c`?
>
> 2. Explain the following from `console.c`:
[code] > if (crt_pos >= CRT_SIZE) {
> int i;
> memcpy(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
> for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
> crt_buf[i] = 0x0700 | ' ';
> crt_pos -= CRT_COLS;
> }
>
```
>
> 3. For the following questions you might wish to consult the notes for Lecture 1. These notes cover GCC's calling convention on the x86.
>
> Trace the execution of the following code step-by-step:
[code] > int x = 1, y = 3, z = 4;
> cprintf("x %d, y %x, z %d\n", x, y, z);
>
```
>
> 1. In the call to `cprintf()`, to what does `fmt` point? To what does `ap` point?
> 2. List (in order of execution) each call to `cons_putc`, `va_arg`, and `vcprintf`. For `cons_putc`, list its argument as well. For `va_arg`, list what `ap` points to before and after the call. For `vcprintf` list the values of its two arguments.
> 4. Run the following code.
[code] > unsigned int i = 0x00646c72;
> cprintf("H%x Wo%s", 57616, &i);
>
```
>
> What is the output? Explain how this output is arrived at in the step-by-step manner of the previous exercise. [Here's an ASCII table][24] that maps bytes to characters.
>
> The output depends on that fact that the x86 is little-endian. If the x86 were instead big-endian what would you set `i` to in order to yield the same output? Would you need to change `57616` to a different value?
>
> [Here's a description of little- and big-endian][25] and [a more whimsical description][26].
>
> 5. In the following code, what is going to be printed after `y=`? (note: the answer is not a specific value.) Why does this happen?
[code] > cprintf("x=%d y=%d", 3);
>
```
>
> 6. Let's say that GCC changed its calling convention so that it pushed arguments on the stack in declaration order, so that the last argument is pushed last. How would you have to change `cprintf` or its interface so that it would still be possible to pass it a variable number of arguments?
>
>
#### The Stack
In the final exercise of this lab, we will explore in more detail the way the C language uses the stack on the x86, and in the process write a useful new kernel monitor function that prints a backtrace of the stack: a list of the saved Instruction Pointer (IP) values from the nested `call` instructions that led to the current point of execution.
> **Exercise 9**
>
> Determine where the kernel initializes its stack, and exactly where in memory its stack is located. How does the kernel reserve space for its stack? And at which "end" of this reserved area is the stack pointer initialized to point to?
The x86 stack pointer (`esp` register) points to the lowest location on the stack that is currently in use. Everything below that location in the region reserved for the stack is free. Pushing a value onto the stack involves decreasing the stack pointer and then writing the value to the place the stack pointer points to. Popping a value from the stack involves reading the value the stack pointer points to and then increasing the stack pointer. In 32-bit mode, the stack can only hold 32-bit values, and esp is always divisible by four. Various x86 instructions, such as `call`, are "hard-wired" to use the stack pointer register.
The `ebp` (base pointer) register, in contrast, is associated with the stack primarily by software convention. On entry to a C function, the function's prologue code normally saves the previous function's base pointer by pushing it onto the stack, and then copies the current `esp` value into `ebp` for the duration of the function. If all the functions in a program obey this convention, then at any given point during the program's execution, it is possible to trace back through the stack by following the chain of saved `ebp` pointers and determining exactly what nested sequence of function calls caused this particular point in the program to be reached. This capability can be particularly useful, for example, when a particular function causes an `assert` failure or `panic` because bad arguments were passed to it, but you aren't sure who passed the bad arguments. A stack backtrace lets you find the offending function.
> **Exercise 10**
>
> To become familiar with the C calling conventions on the x86, find the address of the `test_backtrace` function in `obj/kern/kernel.asm`, set a breakpoint there, and examine what happens each time it gets called after the kernel starts. How many 32-bit words does each recursive nesting level of `test_backtrace` push on the stack, and what are those words?
The above exercise should give you the information you need to implement a stack backtrace function, which you should call `mon_backtrace()`. A prototype for this function is already waiting for you in `kern/monitor.c`. You can do it entirely in C, but you may find the `read_ebp()` function in `inc/x86.h` useful. You'll also have to hook this new function into the kernel monitor's command list so that it can be invoked interactively by the user.
The backtrace function should display a listing of function call frames in the following format:
```
Stack backtrace:
ebp f0109e58 eip f0100a62 args 00000001 f0109e80 f0109e98 f0100ed2 00000031
ebp f0109ed8 eip f01000d6 args 00000000 00000000 f0100058 f0109f28 00000061
...
```
The first line printed reflects the currently executing function, namely `mon_backtrace` itself, the second line reflects the function that called `mon_backtrace`, the third line reflects the function that called that one, and so on. You should print all the outstanding stack frames. By studying `kern/entry.S` you'll find that there is an easy way to tell when to stop.
Within each line, the `ebp` value indicates the base pointer into the stack used by that function: i.e., the position of the stack pointer just after the function was entered and the function prologue code set up the base pointer. The listed `eip` value is the function's return instruction pointer: the instruction address to which control will return when the function returns. The return instruction pointer typically points to the instruction after the `call` instruction (why?). Finally, the five hex values listed after `args` are the first five arguments to the function in question, which would have been pushed on the stack just before the function was called. If the function was called with fewer than five arguments, of course, then not all five of these values will be useful. (Why can't the backtrace code detect how many arguments there actually are? How could this limitation be fixed?)
Here are a few specific points you read about in K&R Chapter 5 that are worth remembering for the following exercise and for future labs.
* If `int *p = (int*)100`, then `(int)p + 1` and `(int)(p + 1)` are different numbers: the first is `101` but the second is `104`. When adding an integer to a pointer, as in the second case, the integer is implicitly multiplied by the size of the object the pointer points to.
* `p[i]` is defined to be the same as `*(p+i)`, referring to the i'th object in the memory pointed to by p. The above rule for addition helps this definition work when the objects are larger than one byte.
* `&p[i]` is the same as `(p+i)`, yielding the address of the i'th object in the memory pointed to by p.
Although most C programs never need to cast between pointers and integers, operating systems frequently do. Whenever you see an addition involving a memory address, ask yourself whether it is an integer addition or pointer addition and make sure the value being added is appropriately multiplied or not.
> **Exercise 11**
>
> Implement the backtrace function as specified above. Use the same format as in the example, since otherwise the grading script will be confused. When you think you have it working right, run make grade to see if its output conforms to what our grading script expects, and fix it if it doesn't. After you have handed in your Lab 1 code, you are welcome to change the output format of the backtrace function any way you like.
At this point, your backtrace function should give you the addresses of the function callers on the stack that lead to `mon_backtrace()` being executed. However, in practice you often want to know the function names corresponding to those addresses. For instance, you may want to know which functions could contain a bug that's causing your kernel to crash.
To help you implement this functionality, we have provided the function `debuginfo_eip()`, which looks up `eip` in the symbol table and returns the debugging information for that address. This function is defined in `kern/kdebug.c`.
> **Exercise 12**
>
> Modify your stack backtrace function to display, for each `eip`, the function name, source file name, and line number corresponding to that `eip`.
In `debuginfo_eip`, where do `__STAB_*` come from? This question has a long answer; to help you to discover the answer, here are some things you might want to do:
* look in the file `kern/kernel.ld` for `__STAB_*`
* run i386-jos-elf-objdump -h obj/kern/kernel
* run i386-jos-elf-objdump -G obj/kern/kernel
* run i386-jos-elf-gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c, and look at init.s.
* see if the bootloader loads the symbol table in memory as part of loading the kernel binary
Complete the implementation of `debuginfo_eip` by inserting the call to `stab_binsearch` to find the line number for an address.
Add a `backtrace` command to the kernel monitor, and extend your implementation of `mon_backtrace` to call `debuginfo_eip` and print a line for each stack frame of the form:
```
K> backtrace
Stack backtrace:
ebp f010ff78 eip f01008ae args 00000001 f010ff8c 00000000 f0110580 00000000
kern/monitor.c:143: monitor+106
ebp f010ffd8 eip f0100193 args 00000000 00001aac 00000660 00000000 00000000
kern/init.c:49: i386_init+59
ebp f010fff8 eip f010003d args 00000000 00000000 0000ffff 10cf9a00 0000ffff
kern/entry.S:70: <unknown>+0
K>
```
Each line gives the file name and line within that file of the stack frame's `eip`, followed by the name of the function and the offset of the `eip` from the first instruction of the function (e.g., `monitor+106` means the return `eip` is 106 bytes past the beginning of `monitor`).
Be sure to print the file and function names on a separate line, to avoid confusing the grading script.
Tip: printf format strings provide an easy, albeit obscure, way to print non-null-terminated strings like those in STABS tables. `printf("%.*s", length, string)` prints at most `length` characters of `string`. Take a look at the printf man page to find out why this works.
You may find that some functions are missing from the backtrace. For example, you will probably see a call to `monitor()` but not to `runcmd()`. This is because the compiler in-lines some function calls. Other optimizations may cause you to see unexpected line numbers. If you get rid of the `-O2` from `GNUMakefile`, the backtraces may make more sense (but your kernel will run more slowly).
**This completes the lab.** In the `lab` directory, commit your changes with `git commit` and type `make handin` to submit your code.
--------------------------------------------------------------------------------
via: https://sipb.mit.edu/iap/6.828/lab/lab1/
作者:[mit][a]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://sipb.mit.edu
[1]:http://www.git-scm.com/
[2]:http://www.kernel.org/pub/software/scm/git/docs/user-manual.html
[3]:http://eagain.net/articles/git-for-computer-scientists/
[4]:https://sipb.mit.edu/iap/6.828/tools
[5]:https://exokernel.scripts.mit.edu/submit/
[6]:https://sipb.mit.edu/iap/6.828/readings/pcasm-book.pdf
[7]:http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html
[8]:https://sipb.mit.edu/iap/6.828/reference
[9]:https://sipb.mit.edu/iap/6.828/readings/i386/toc.htm
[10]:http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
[11]:http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals
[12]:http://www.qemu.org/
[13]:http://www.gnu.org/software/gdb/
[14]:http://web.archive.org/web/20040404164813/members.iweb.net.au/%7Epstorr/pcbook/book2/book2.htm
[15]:https://sipb.mit.edu/iap/6.828/readings/boot-cdrom.pdf
[16]:https://sipb.mit.edu/iap/6.828/labguide
[17]:https://sipb.mit.edu/iap/6.828/files/pointers.c
[18]:http://www.amazon.com/C-Programming-Language-2nd/dp/0131103628/sr=8-1/qid=1157812738/ref=pd_bbs_1/104-1502762-1803102?ie=UTF8&s=books
[19]:http://library.mit.edu/F/AI9Y4SJ2L5ELEE2TAQUAAR44XV5RTTQHE47P9MKP5GQDLR9A8X-10422?func=item-global&doc_library=MIT01&doc_number=000355242&year=&volume=&sub_library=
[20]:http://sipb.mit.edu/
[21]:https://sipb.mit.edu/iap/6.828/readings/pointers.pdf
[22]:https://sipb.mit.edu/iap/6.828/readings/elf.pdf
[23]:http://sourceware.org/gdb/current/onlinedocs/gdb_9.html#SEC63
[24]:http://web.cs.mun.ca/%7Emichael/c/ascii-table.html
[25]:http://www.webopedia.com/TERM/b/big_endian.html
[26]:http://www.networksorcery.com/enp/ien/ien137.txt

View File

@ -1,121 +0,0 @@
translating by kimii
cTop - A CLI Tool For Container Monitoring
======
Recent days Linux containers are famous, even most of us already working on it and few of us start learning about it.
We have already covered article about the famous GUI (Graphical User Interface) tools such as Portainer & Rancher. This will help us to manage containers through GUI.
This tutorial will help us to understand and monitor Linux containers through cTop command. Its a command-line tool like top command.
### Whats cTop
[ctop][1] provides a concise and condensed overview of real-time metrics for multiple containers. Its Top-like interface for container metrics.
It displays containers metrics such as CPU utilization, Memory utilization, Disk I/O Read & Write, Process ID (PID), and Network Transmit(TX Transmit FROM this server) and receive(RX Receive TO this server).
ctop comes with built-in support for Docker and runC; connectors for other container and cluster systems are planned for future releases.
It doesnt requires any arguments and uses Docker host variables by default.
**Suggested Read :**
**(#)** [Portainer A Simple Docker Management GUI][2]
**(#)** [Rancher A Complete Container Management Platform For Production Environment][3]
### How To Install cTop
Developer offers a simple shell script, which help us to use ctop instantly. What we have to do, just download the ctop shell file at `/bin` directory for global access. Finally assign the execute permission to ctop shell file.
Download the ctop shell file @ `/usr/local/bin` directory.
```
$ sudo wget https://github.com/bcicen/ctop/releases/download/v0.7/ctop-0.7-linux-amd64 -O /usr/local/bin/ctop
```
Set execute permission to ctop shell file.
```
$ sudo chmod +x /usr/local/bin/ctop
```
Alternatively you can install and run ctop through docker. Make sure you should have installed docker as a pre-prerequisites for this. To install docker, refer the following link.
**Suggested Read :**
**(#)** [How to install Docker in Linux][4]
**(#)** [How to play with Docker images on Linux][5]
**(#)** [How to play with Docker containers on Linux][6]
**(#)** [How to Install, Run Applications inside Docker Containers][7]
```
$ docker run --rm -ti \
--name=ctop \
-v /var/run/docker.sock:/var/run/docker.sock \
quay.io/vektorlab/ctop:latest
```
### How To Use cTop
Just launch the ctop utility without any arguments. By default its bind with `a` key which display of all containers (running and non-running).
ctop header shows your system time and total number of containers.
```
$ ctop
```
You might get the output similar to below.
![][9]
### How To Manage Containers
You can able to administrate the containers using ctop. Select a container that you want to manage then hit `Enter` button and choose required options like start, stop, remove, etc,.
![][10]
### How To Sort Containers
By default ctop sort the containers using state field. Hit `s` key to sort the containers in the different aspect.
![][11]
### How To View the Containers Metrics
If you want to view more details & metrics about the container, just select the corresponding which you want to view then hit `o` key.
![][12]
### How To View Container Logs
Select the corresponding container which you want to view the logs then hit `l` key.
![][13]
### Display Only Active Containers
Run ctop command with `-a` option to show active containers only.
![][14]
### Open Help Dialog Box
Run ctop, just hit `h`key to open help section.
![][15]
--------------------------------------------------------------------------------
via: https://www.2daygeek.com/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux/
作者:[2DAYGEEK][a]
译者:[译者ID](https://github.com/译者ID)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://www.2daygeek.com/author/2daygeek/
[1]:https://github.com/bcicen/ctop
[2]:https://www.2daygeek.com/portainer-a-simple-docker-management-gui/
[3]:https://www.2daygeek.com/rancher-a-complete-container-management-platform-for-production-environment/
[4]:https://www.2daygeek.com/install-docker-on-centos-rhel-fedora-ubuntu-debian-oracle-archi-scentific-linux-mint-opensuse/
[5]:https://www.2daygeek.com/list-search-pull-download-remove-docker-images-on-linux/
[6]:https://www.2daygeek.com/create-run-list-start-stop-attach-delete-interactive-daemonized-docker-containers-on-linux/
[7]:https://www.2daygeek.com/install-run-applications-inside-docker-containers/
[8]:data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
[9]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-1.png
[10]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-2.png
[11]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-3.png
[12]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-4a.png
[13]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-7.png
[14]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-5.png
[15]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-6.png

View File

@ -1,3 +1,4 @@
translating by kimii
Do continuous deployment with Github and Python
======

View File

@ -1,113 +0,0 @@
计算机系统的进化论
======
纵观现代计算机的历史,从与系统的交互方式方面,可以划分为数个进化阶段。而我更倾向于将之归类为以下几个阶段:
1. 数字系统
2. 专用应用系统
3. 应用中心系统
4. 信息中心系统
5. 无应用系统
下面我们详细聊聊这几种分类。
### 数字系统
在我看来,[ 早期计算机 ][1],只被设计用来处理数字。它们能够加,减,乘,除。在它们中有一些能够运行像是微分和积分之类的更复杂的数学操作。
当然,如果你把字符映射成数字,它们也可以计算字符串。但这多少有点“数字的创造性使用”的意思,而不是直接处理各种信息。
### 专用应用系统
对于更高层级的问题,纯粹的数字系统是不够的。专用应用系统被开发用来处理单一任务。它们和数字系统十分相似,但是,它们拥有足够的复杂数字计算能力。这些系统能够完成十分明确的高层级任务,像调度问题的相关计算或者其他优化问题。
这类系统为单一目的而搭建,它们解决的是单一明确的问题。
### 应用中心系统
应用中心系统是第一个真正的通用系统。它们的主要使用风格很像专用应用系统,但是它们拥有以时间片模式(一个接一个)或以多任务模式(多应用同时)运行的多个应用程序。
上世纪 70 年代的[ 早期的个人电脑 ][3]是第一种受大量人们欢迎的应用中心系统。
如今的现在操作系统 —— Windows , macOS , 大多数 GNU/Linux 桌面环境 —— 一直遵循相同的法则。
当然,应用中心系统还可以再细分为两种子类:
1. 紧密型应用中心系统
2. 松散型应用中心系统
精密型应用中心系统像是 [Windows 3.1][4] (拥有程序管理器和文件管理器)或者甚至 [ Windows 95 ][5] 的最初版本都没有预定义文件夹层次。用户启动文本处理程序(像 [ WinWord ][6])并且把文件保存在 WinWord 的程序文件夹中。在使用表格处理程序的时候,又把文件保存在表格处理工具的程序文件夹中。诸如此类。用户几乎不创建自己的文件层次结构,可能由于此举的不方便,用户单方面的懒惰,或者他们认为根本没有必要。那时,每个用户拥有几十个至多几百个文件。
为了访问文件中的信息,用户常常先打开一个应用程序,然后通过程序中的“文件/打开”功能来获取处理过的数据文件。
在 Windows 平台的[ Windows 95][5] SP2 中,«[ 我的文档 ][7]»首次被使用。有了这样一个文件层次结构的样板,应用设计者开始把 «[我的文档][7]» 作为程序的默认 保存 / 打开 目录,抛弃了原来将软件产品安装目录作为默认目录的做法。这样一来,用户渐渐适应了这种模式,并且开始自己维护文件夹层次。
松散型应用中心系统(通过文件管理器来提取文件)应用而生。在这种系统下,当打开一个文件的时候,操作系统会自动启动与之相关的应用程序。这是一次小而精妙的用法转变。这种应用中心系统的用法模式一直是个人电脑的主要用法模式。
然而,这种模式有很多的缺点。例如,对于一个给定的项目,为了防止数据提取出现问题,需要维护一个包含所有相关文件的严格文件夹层次结构。不幸的是,人们并不总能这样做。更进一步说,[ 这种模式不能很好的扩展 ][8]。 桌面搜索引擎和高级数据组织工具(像[ tagstore ][9])可以起到一点改善作用。正如研究显示的那样,只有一少部分人正在使用那些高级文件提取工具。大多数的用户不使用替代提取工具或者辅助提取技术在文件系统中寻找文件。
### 信息中心系统
解决上述问题的可行办法之一就是从应用中心系统转换到信息中心系统。
信息中心系统将项目的所有信息联合起来,放在一个地方,放在同一个应用程序里。
因此,我们再也不需要计算项目预算时,打开表格处理程序;写工程报告时,打开文本处理程序;处理图片文件时,又打开另一个工具。
上个月的预算情况在客户会议笔记的右下方,客户会议笔记又在画板的右下方,而画板又在另一些要去完成的任务的右下方。在各个层之间没有文件或者应用程序来回切换的麻烦。
早期IBM [ OS/2 ][10], Microsoft [ OLE ][11] 和 [NeXT][12] 都做过类似的尝试。但都由于各种原因没有取得重大成功。从 [ Plan 9][14] 发展而来的 [ACme][13] 是一个令人兴奋的信息中心环境。它在一个应用程序中包含了多种应用程序。但是相比 Windows 和 GNU/Linux 而言,它从不是一个值得注意的系统发行版(即使在系统接口级别)。
信息中心系统的现代形式是高级 [ 个人 wikis ][16](像 [ TheBrain ][17]和[ Microsoft OneNote ][18])。
我选择的个人工具是带 [Org-mode][19] 扩展的 [GNU/Emacs][20]。在用电脑的时候,我几乎不能没有 Org-mode 。为了访问外部数据资源,我创建了一个可以将多种数据导入 Org-mode 的插件 —— [Memacs][20] 。我喜欢将表格数据计算放到日程任务的右下方,然后是行内图片,内部和外部链接,等等。它是一个真正的用户不用必须切换程序或者切换严格层次文件系统文件夹的信息中心系统。同时,用简单的或高级的标签也可以进行多分类。一个命令可以派生多种视图。比如,一个视图有日历,待办事项。另一个视图是租借事宜。等等。它对 Org-mode 用户没有限制。只有你想不到,没有它做不到。
进化结束了吗? 当然没有。
### 无应用系统
我能想到这样一类操作系统,我称之为无应用系统。在下一步的发展中,系统将不需要单域应用程序,即使它们能和 Org-mode 一样出色。计算机直接提供一个处理信息和使用功能的友好用户接口,而不通过文件和程序。甚至连传统的操作系统也不需要。
无应用系统也可能和 [人工智能][21] 联系起来。把它想象成 [2001太空漫游][23] 中的 [HAL 9000][22] 和星际迷航中的 [LCARS][24]一类的东西就可以了。
从基于应用的,基于供应商的软件文化到无应用系统的转化让人很难相信。 或许,缓慢但却不断发展的开源环境,可以使一个由各种各样组织和人们贡献的真正无应用环境成型。
信息和提取、操作信息的功能,这是系统应该有的,同时也是我们所需要的。其他的东西仅仅是为了使我们不至于分散注意力。
--------------------------------------------------------------------------------
via: http://karl-voit.at/2017/02/10/evolution-of-systems/
作者:[Karl Voit][a]
译者:[lontow](https://github.com/lontow)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:http://karl-voit.at
[1]:https://en.wikipedia.org/wiki/History_of_computing_hardware
[2]:https://en.wikipedia.org/wiki/String_%2528computer_science%2529
[3]:https://en.wikipedia.org/wiki/Xerox_Alto
[4]:https://en.wikipedia.org/wiki/Windows_3.1x
[5]:https://en.wikipedia.org/wiki/Windows_95
[6]:https://en.wikipedia.org/wiki/Microsoft_Word
[7]:https://en.wikipedia.org/wiki/My_Documents
[8]:http://karl-voit.at/tagstore/downloads/Voit2012b.pdf
[9]:http://karl-voit.at/tagstore/
[10]:https://en.wikipedia.org/wiki/OS/2
[11]:https://en.wikipedia.org/wiki/Object_Linking_and_Embedding
[12]:https://en.wikipedia.org/wiki/NeXT
[13]:https://en.wikipedia.org/wiki/Acme_%2528text_editor%2529
[14]:https://en.wikipedia.org/wiki/Plan_9_from_Bell_Labs
[15]:https://en.wikipedia.org/wiki/List_of_Plan_9_applications
[16]:https://en.wikipedia.org/wiki/Personal_wiki
[17]:https://en.wikipedia.org/wiki/TheBrain
[18]:https://en.wikipedia.org/wiki/Microsoft_OneNote
[19]:../../../../tags/emacs
[20]:https://github.com/novoid/Memacs
[21]:https://en.wikipedia.org/wiki/Artificial_intelligence
[22]:https://en.wikipedia.org/wiki/HAL_9000
[23]:https://en.wikipedia.org/wiki/2001:_A_Space_Odyssey
[24]:https://en.wikipedia.org/wiki/LCARS

View File

@ -0,0 +1,616 @@
Caffeinated 6.828:实验 1PC 的引导过程
======
### 简介
这个实验分为三个部分。第一部分主要是为了熟悉使用 x86 汇编语言、QEMU x86 仿真器、以及 PC 的加电引导过程。第二部分查看我们的 6.828 内核的引导加载器,它位于 `lab` 树的 `boot` 目录中。第三部分深入到我们的名为 JOS 的 6.828 内核模型内部,它在 `kernel` 目录中。
#### 软件安装
本课程中你需要的文件和接下来的实验任务所需要的文件都是通过使用 [Git][1] 版本控制系统来分发的。学习更多关于 Git 的知识,请查看 [Git 用户手册][2],或者,如果你熟悉其它的版本控制系统,这个 [面向 CS 的 Git 概述][3] 可能对你有帮助。
本课程在 Git 仓库中的地址是 `https://exokernel.scripts.mit.edu/joslab.git`。在你的 Athena 帐户中安装文件,你需要运行如下的命令去克隆课程仓库。你也可以使用 `ssh -X athena.dialup.mit.edu` 去登入到一个公共的 Athena 主机。
```
athena% mkdir ~/6.828
athena% cd ~/6.828
athena% add git
athena% git clone https://exokernel.scripts.mit.edu/joslab.git lab
Cloning into lab...
athena% cd lab
athena%
```
Git 可以帮你跟踪代码中的变化。比如,如果你完成了一个练习,想在你的进度中打一个检查点,你可以运行如下的命令去提交你的变更:
```
athena% git commit -am 'my solution for lab1 exercise 9'
Created commit 60d2135: my solution for lab1 exercise 9
1 files changed, 1 insertions(+), 0 deletions(-)
athena%
```
你可以使用 `git diff` 命令跟踪你的变更。运行 `git diff` 将显示你的代码自最后一次提交之后的变更,而 `git diff origin/lab1` 将显示这个实验相对于初始代码的变更。在这里,`origin/lab1` 是为了完成这个作业,从我们的服务器上下载的初始代码在 Git 分支上的名字。
在 Athena 上,我们为你配置了合适的编译器和模拟器。如果你要去使用它们,请运行 `add exokernel` 命令。 每次登入 Athena 主机你都必须要运行这个命令(或者你可以将它添加到你的 `~/.environment` 文件中)。如果你在编译或者运行 `qemu` 时出现晦涩难懂的错误,可以双击 "check" 将它添加到你的课程收藏夹中。
如果你使用的是非 Athena 机器,你需要安装 `qemu``gcc`,它们在 [工具页面][4] 目录中。为了以后的实验需要,我们做了一些 `qemu` 调试方面的变更和补丁,因此,你必须构建你自己的工具。如果你的机器使用原生的 ELF 工具链比如Linux 和大多数 BSD但不包括 OS X你可以简单地从你的包管理器中安装 `gcc`。除此之外,都应该按工具页面的指导去做。
#### 动手过程
我们为了你便于做实验,为你使用了不同的 Git 仓库。做实验用的仓库位于一个 SSH 服务器后面。你可以拥有你自己的实验仓库,其他的任何同学都不可访问你的这个仓库。为了通过 SSH 服务器的认证,你必须有一对 RSA 密钥,并让服务器知道你的公钥。
实验代码同时还带有一个脚本,它可以帮你设置如何访问你的实验仓库。在运行这个脚本之前,你必须在我们的 [submission web 界面][5] 上有一个帐户。在登陆页面上,输入你的 Athena 用户名,然后点击 "Mail me my password"。在你的邮箱中将马上接收到一封包含有你的 `6.828` 课程密码的邮件。注意,每次你点击这个按钮的时候,系统将随机给你分配一个新密码。
现在,你已经有了你的 `6.828` 密码,在 `lab` 目录下,运行如下的命令去配置实践仓库:
```
athena% make handin-prep
Using public key from ~/.ssh/id_rsa:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0lnnkoHSi4JDFA ...
Continue? [Y/n] Y
Login to 6.828 submission website.
If you do not have an account yet, sign up at https://exokernel.scripts.mit.edu/submit/
before continuing.
Username: <your Athena username>
Password: <your 6.828 password>
Your public key has been successfully updated.
Setting up hand-in Git repository...
Adding remote repository ssh://josgit@exokernel.mit.edu/joslab.git as 'handin'.
Done! Use 'make handin' to submit your lab code.
athena%
```
如果你没有 RSA 密钥对,这个脚本可能会询问你是否生成一个新的密钥对:
```
athena% make handin-prep
SSH key file ~/.ssh/id_rsa does not exists, generate one? [Y/n] Y
Generating public/private rsa key pair.
Your identification has been saved in ~/.ssh/id_rsa.
Your public key has been saved in ~/.ssh/id_rsa.pub.
The key fingerprint is:
xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx
The keyʼs randomart image is:
+--[ RSA 2048]----+
| ........ |
| ........ |
+-----------------+
Using public key from ~/.ssh/id_rsa:
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD0lnnkoHSi4JDFA ...
Continue? [Y/n] Y
.....
athena%
```
当你开始动手做实验时,在 `lab` 目录下,输入 `make handin` 去使用 git 做第一次提交。后面将运行 `git push handin HEAD`,它将推送当前分支到远程 `handin` 仓库的同名分支上。
```
athena% git commit -am "ready to submit my lab"
[lab1 c2e3c8b] ready to submit my lab
2 files changed, 18 insertions(+), 2 deletions(-)
athena% make handin
Handin to remote repository using 'git push handin HEAD' ...
Counting objects: 59, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (55/55), done.
Writing objects: 100% (59/59), 49.75 KiB, done.
Total 59 (delta 3), reused 0 (delta 0)
To ssh://josgit@am.csail.mit.edu/joslab.git
* [new branch] HEAD -> lab1
athena%
```
如果在你的实验仓库上产生变化,你将收到一封电子邮件,让你去确认这个提交。以后,你可能会多次去运行 `run make handin`(或者 `git push handin`)。对于一个指定实验的最后提交时间是由相应分支的最新推送(最后一个推送)的时间决定的。
在这个案例中,`make handin` 运行可能并不正确,你可以使用 Git 命令去尝试修复这个问题。或者,你可以去运行 `make tarball`。它将为你生成一个 tar 文件,这个文件可以通过我们的 [web 界面][5] 来上传。`make handin` 提供了很多特殊说明。
对于实验 1你不需要去回答下列的任何一个问题。尽管你不用自己回答但是它们对下面的实验有帮助
我们将使用一个评级程序来分级你的解决方案。你可以使用这个评级程序去测试你的解决方案的分级情况。
### 第一部分PC 引导
第一个练习的目的是向你介绍 x86 汇编语言和 PC 引导过程,你可以使用 QEMU 和 QEMU/GDB 调试开始你的练习。这部分的实验你不需要写任何代码,但是,通过这个实验,你将对 PC 引导过程有了你自己的理解,并且为回答后面的问题做好准备。
#### 从使用 x86 汇编语言开始
如果你对 x86 汇编语言的使用还不熟悉,通过这个课程,你将很快熟悉它!如果你想学习它,[PC 汇编语言][6] 这本书是一个很好的开端。希望这本书中有你所需要的一切内容。
警告:很不幸,这本书中的示例是为 NASM 汇编语言写的,而我们使用的是 GNU 汇编语言。NASM 使用所谓的 Intel 语法,而 GNU 使用 AT&T 语法。虽然在语义上是等价的,但是根据你使用的语法不同,至少从表面上看,汇编文件的差别还是挺大的。幸运的是,这两种语法的转换很简单,在 [Brennan's Guide to Inline Assembly][7] 有详细的介绍。
> **练习 1**
>
> 熟悉在 [6.828 参考页面][8] 上列出的你想去使用的可用汇编语言。你不需要现在就去阅读它们,但是在你阅读和写 x86 汇编程序的时候,你可以去参考相关的内容。
我并不推荐你阅读 [Brennan's Guide to Inline Assembly][7] 上的 “语法” 章节。虽然它对 AT&T 汇编语法描述的很好(并且非常详细),而且我们在 JOS 中使用的 GNU 汇编就是它。
对于 x86 汇编语言程序最终还是需要参考 Intel 的指令集架构,你可以在 [6.828 参考页面][8] 上找到它,它有两个版本:一个是 HTML 版的,是老的 [80386 程序员参考手册][9],它比起最新的手册更简短,更易于查找,但是,它包含了我们的 6.828 上所使用的 x86 处理器的所有特性;而更全面的、更新的、更好的是,来自 Intel 的 [IA-32 Intel 架构软件开发者手册][10],它涵盖了我们在课程中所需要的、(并且可能有些是你不感兴趣的)大多数处理器的全部特性。另一个差不多的(并且经常是很友好的)一套手册是 [来自 AMD][11] 的。当你为了一个特定的处理器特性或者指令,去查找最终的解释时,保存的最新的 Intel/AMD 架构手册或者它们的参考就很有用了。
#### 仿真 x86
与在一台真实的、物理的、个人电脑上引导一个操作系统不同,我们使用程序去如实地仿真一台完整的 PC你在仿真器中写的代码也能够引导一台真实的 PC。使用仿真器可以简化调试工作比如你可以在仿真器中设置断点而这在真实的机器中是做不到的。
在 6.828 中,我们将使用 [QEMU 仿真器][12],它是一个现代化的并且速度非常快的仿真器。虽然 QEMU 内置的监视功能提供了有限的调试支持但是QEMU 也可以做为 [GNU 调试器][13] (GDB) 的远程调试目标,我们在这个实验中将使用它来一步一步完成引导过程。
在开始之前,按照前面 “软件安装“ 中在 Athena 主机上描述的步骤,提取实验 1 的文件到你自己的目录中,然后,在 `lab` 目录中输入 `make`(如果是 BSD 的系统,是输入 `gmake` )来构建最小的 6.828 引导加载器和用于启动的内核。(把在这里我们运行的这些代码称为 ”内核“ 有点夸大,但是,通过这个学期的课程,我们将把这些代码充实起来,成为真正的 ”内核“)
```
athena% cd lab
athena% make
+ as kern/entry.S
+ cc kern/init.c
+ cc kern/console.c
+ cc kern/monitor.c
+ cc kern/printf.c
+ cc lib/printfmt.c
+ cc lib/readline.c
+ cc lib/string.c
+ ld obj/kern/kernel
+ as boot/boot.S
+ cc -Os boot/main.c
+ ld boot/boot
boot block is 414 bytes (max 510)
+ mk obj/kern/kernel.img
```
(如果你看到有类似 ”undefined reference to `__udivdi3'" 这样的错误,可能是因为你的电脑上没有 32 位的 ”gcc multilib“。如果你运行在 Debian 或者 Ubuntu你可以尝试去安装 ”gcc-multilib“ 包。)
现在,你可以去运行 QEMU 了,并将上面创建的 `obj/kern/kernel.img` 文件提供给它,以作为仿真 PC 的 “虚拟硬盘”,这个虚拟硬盘中包含了我们的引导加载器(`obj/boot/boot`) 和我们的内核(`obj/kernel`)。
```
athena% make qemu
```
运行 QEMU 时需要使用选项去设置硬盘,以及指示串行端口输出到终端。在 QEMU 窗口中将出现一些文本内容:
```
Booting from Hard Disk...
6828 decimal is XXX octal!
entering test_backtrace 5
entering test_backtrace 4
entering test_backtrace 3
entering test_backtrace 2
entering test_backtrace 1
entering test_backtrace 0
leaving test_backtrace 0
leaving test_backtrace 1
leaving test_backtrace 2
leaving test_backtrace 3
leaving test_backtrace 4
leaving test_backtrace 5
Welcome to the JOS kernel monitor!
Type 'help' for a list of commands.
K>
```
在 '`Booting from Hard Disk...`' 之后的内容,就是由我们的基本 JOS 内核输出的:`K>` 是包含在我们的内核中的小型监听器或者交互式控制程序的提示符。内核输出的这些行也会出现在你运行 QEMU 的普通 shell 窗口中。这是因为测试和实验分级的原因,我们配置了 JOS 的内核,使它将控制台输出不仅写入到虚拟的 VGA 显示器(就是 QEMU 窗口),也写入到仿真 PC 的虚拟串口上QEMU 会将虚拟串口上的信息转发到它的标准输出上。同样JOS 内核也将接收来自键盘和串口的输入,因此,你既可以从 VGA 显示窗口中输入命令,也可以从运行 QEMU 的终端窗口中输入命令。或者,你可以通过运行 `make qemu-nox` 来取消虚拟 VGA 的输出,只使用串行控制台来输出。如果你是通过 SSH 拨号连接到 Athena 主机,这样可能更方便。
在这里有两个可以用来监视内核的命令,它们是 `help``kerninfo`
```
K> help
help - display this list of commands
kerninfo - display information about the kernel
K> kerninfo
Special kernel symbols:
entry f010000c (virt) 0010000c (phys)
etext f0101a75 (virt) 00101a75 (phys)
edata f0112300 (virt) 00112300 (phys)
end f0112960 (virt) 00112960 (phys)
Kernel executable memory footprint: 75KB
K>
```
`help` 命令的用途很明确,我们将简短地讨论一下 `kerninfo` 命令输出的内容。虽然它很简单,但是,需要重点注意的是,这个内核监视器是 “直接” 运行在仿真 PC 的 “原始(虚拟)硬件” 上的。这意味着你可以去拷贝 `obj/kern/kernel.img` 的内容到一个真实硬盘的前几个扇区,然后将那个硬盘插入到一个真实的 PC 中,打开这个 PC 的电源,你将在一台真实的 PC 屏幕上看到和上面在 QEMU 窗口完全一样的内容。(我们并不推荐你在一台真实机器上这样做,因为拷贝 `kernel.img` 到硬盘的前几个扇区将覆盖掉那个硬盘上原来的主引导记录,这将导致这个硬盘上以前的内容丢失!)
#### PC 的物理地址空间
我们现在将更深入去了解 ”关于 PC 是如何启动“ 的更多细节。一台 PC 的物理地址空间是硬编码为如下的布局:
```
+------------------+ <- 0xFFFFFFFF (4GB)
| 32-bit |
| memory mapped |
| devices |
| |
/\/\/\/\/\/\/\/\/\/\
/\/\/\/\/\/\/\/\/\/\
| |
| Unused |
| |
+------------------+ <- depends on amount of RAM
| |
| |
| Extended Memory |
| |
| |
+------------------+ <- 0x00100000 (1MB)
| BIOS ROM |
+------------------+ <- 0x000F0000 (960KB)
| 16-bit devices, |
| expansion ROMs |
+------------------+ <- 0x000C0000 (768KB)
| VGA Display |
+------------------+ <- 0x000A0000 (640KB)
| |
| Low Memory |
| |
+------------------+ <- 0x00000000
```
首先,这台 PC 是基于 16 位的 Intel 8088 处理器,它仅能处理 1 MB 的物理地址。所以,早期 PC 的物理地址空间开始于 `0x00000000`,结束于 `0x000FFFFF` 而不是 `0xFFFFFFFF`。被标记为 “低位内存” 的区域是早期 PC 唯一可以使用的随机访问内存RAM事实上更早期的 PC 仅可以配置 16KB、32KB、或者 64KB 的内存!
`0x000A0000``0x000FFFFF` 的 384 KB 的区域是为特定硬件保留的区域,比如,视频显示缓冲和保存在非易失存储中的固件。这个保留区域中最重要的部分是基本输入/输出系统BIOS它位于从 `0x000F0000``0x000FFFFF` 之间的 64KB 大小的区域。在早期的 PC 中BIOS 在真正的只读存储ROM但是现在的 PC 的 BIOS 都保存在可更新的 FLASH 存储中。BIOS 负责执行基本系统初始化工作比如激活视频卡和检查已安装的内存数量。这个初始化工作完成之后BIOS 从相关位置加载操作系统比如从软盘、硬盘、CD-ROM、或者网络然后将机器的控制权传递给操作系统。
当 Intel 最终在 80286 和 80386 处理器上 “打破了 1MB 限制” 之后,这两个处理器各自支持 16MB 和 4GB 物理地址空间尽管如此为了确保向下兼容现存软件PC 架构还是保留着 1 MB 以内物理地址空间的原始布局。因此,现代 PC 的物理内存,在 `0x000A0000``0x00100000` 之间有一个 "黑洞区域“,将内存分割为 ”低位“ 或者 ”传统内存“ 区域(前 640 KB和 ”扩展内存“(其它的部分)。除此之外,在 PC 的 32 位物理地址空间顶部之上的一些空间,在全部的物理内存上面,现在一般都由 BIOS 保留给 32 位的 PCI 设备使用。
最新的 x86 处理器可以支持超过 4GB 的物理地址空间因此RAM 可以进一步扩展到 `0xFFFFFFFF` 之上。在这种情况下BIOS 必须在 32 位可寻址空间顶部之上的系统 RAM 上,设置第二个 ”黑洞区域“,以便于为这些 32 位的设备映射留下空间。因为 JOS 设计的限制,它仅可以使用 PC 物理内存的前 256 MB因此我们将假设所有的 PC ”仅仅“ 拥有 32 位物理地址空间。但是处理复杂的物理地址空间和其它部分的硬件系统,将涉及到许多年前操作系统开发所遇到的实际挑战之一。
#### ROM BIOS
在实验的这一部分中,你将使用 QEMU 的调试功能去研究 IA-32 相关的计算机是如何引导的。
打开两个终端窗口,在其中一个中,输入 `make qemu-gdb`(或者 `make qemu-nox-gdb`),这将启动 QEMU但是处理器在运行第一个指令之前将停止 QEMU以等待来自 GDB 的调试连接。在第二个终端窗口中,从相同的目录中运行 `make`,以及运行 `make gdb`。你将看到如下的输出。
```
athena% make gdb
GNU gdb (GDB) 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
+ target remote localhost:1234
The target architecture is assumed to be i8086
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
0x0000fff0 in ?? ()
+ symbol-file obj/kern/kernel
(gdb)
```
`make gdb` 的运行目标是一个称为 `.gdbrc` 的脚本,它设置了 GDB 在早期引导期间调试所用到的 16 位代码,并且将它指向到正在监听的 QEMU 上。
下列行:
```
[f000:fff0] 0xffff0: ljmp $0xf000,$0xe05b
```
是 GDB 运行的第一个指令的反汇编。这个输出包含如下的信息:
* IBM PC 从物理地址 `0x000ffff0` 开始运行,这个地址位于为 ROM BIOS 保留的 64 KB 区域的顶部。
* PC 使用 `CS = 0xf000``IP = 0xfff0` 开始运行。
* 运行的第一个指令是一个 `jmp` 指令,它跳转段地址 `CS = 0xf000``IP = 0xe05b`
为什么 QEMU 是这样开始的呢?这是因为 Intel 设计的 8088 处理器是这样做的,这个处理器是 IBM 最早用在他们的 PC 上的处理器。因为在一台 PC 中BIOS 是硬编码在物理地址范围 `0x000f0000-0x000fffff` 中的这样的设计确保了在机器接通电源或者任何系统重启之后BIOS 总是能够首先控制机器 —— 这是至关重要的因为机器接通电源之后在机器的内存中没有处理器可以运行的任何软件。QEMU 仿真器有它自己的 BIOS它的位置在处理器的模拟地址空间中。在处理器复位之后模拟的处理器进入了实模式然后设置 CS 为 `0xf000` 、IP 为 `0xfff0`所以运行开始于那个CS:IP段地址。那么段地址 0xf000:fff0 是如何转到物理地址的呢?
在回答这个问题之前我们需要了解有关实模式地址的知识。在实模式PC 启动之后就处于实模式)中,物理地址是根据这个公式去转换的:物理地址 = 16 * 段地址 + 偏移。因此,当 PC 设置 CS 为 `0xf000` 、IP 为 `0xfff0` 之后,物理地址指向到:
```
16 * 0xf000 + 0xfff0 # in hex multiplication by 16 is
= 0xf0000 + 0xfff0 # easy--just append a 0.
= 0xffff0
```
`0xffff0` 是 BIOS (`0x100000`) 结束之前的 16 字节。因此BIOS 所做的第一件事情是向后 `jmp` 到 BIOS 中的早期位置就一点也不奇怪了;毕竟只有 16 字节,还能指望它做些什么呢?
> **练习 2**
>
> 使用 GDB 的 `si`(步进指令)指令去跟踪进入到 ROM BIOS 的更多指令,然后尝试猜测它可能会做什么。你可能需要去查看 [Phil Storrs I/O 端口描述][14],以及在 [6.828 参考资料页面][8] 上的其它资料。不需要了解所有的细节 —— 只要搞明白 BIOS 首先要做什么就可以了。
当 BIOS 运行后,它将设置一个中断描述符表和初始化各种设备,比如, VGA 显示。在这时,你在 QEMU 窗口中将出现 "`Starting SeaBIOS`" 的信息。
在初始化 PCI 产品线和 BIOS 知道的所有重要设备之后,它将搜索可引导设备,比如,一个软盘、硬盘、或者 CD-ROM。最后当它找到可引导磁盘之后BIOS 从可引导硬盘上读取引导加载器,然后将控制权交给它。
### 第二部分:引导加载器
在 PC 的软盘和硬盘中,将它们分割成 512 字节大小的区域,每个区域称为一个扇区。一个扇区就是磁盘的最小转存单元:每个读或写操作都必须是一个或多个扇区大小,并且按扇区边界进行对齐。如果磁盘是可引导盘,第一个扇区则为引导扇区,因为,第一个扇区中驻留有引导加载器的代码。当 BIOS 找到一个可引导软盘或者硬盘时,它将 512 字节的引导扇区加载进物理地址为 0x7c00 到 `0x7dff` 的内存中,然后使用一个 `jmp` 指令设置 CS:IP 为 `0000:7c00`,并传递控制权到引导加载器。与 BIOS 加载地址一样,这些地址是任意的 —— 但是它们对于 PC 来说是固定的,并且是标准化的。
后来,随着 PC 的技术进步,它们可以从 CD-ROM 中引导因此PC 架构师趁机对引导过程进行轻微的调整。最后的结果使现代的 BIOS 从 CD-ROM 中引导的过程更复杂并且功能更强大。CD-ROM 使用 2048 字节大小的扇区,而不是 512 字节的扇区并且BIOS 在传递控制权之前,可以从磁盘上加载更大的(不止是一个扇区)引导镜像到内存中。更多内容,请查看 ["El Torito" 可引导 CD-ROM 格式规范][15]。
不过对于 6.828,我们将使用传统的硬盘引导机制,意味着我们的引导加载器必须小于 512 字节。引导加载器是由一个汇编源文件 `boot/boot.S` 和一个 C 源文件 `boot/main.c` 构成,仔细研究这些源文件可以让你彻底理解引导加载器都做了些什么。引导加载器必须要做两件主要的事情:
1. 第一,引导加载器将处理器从实模式切换到 32 位保护模式,因为只有在 32 位保护模式中,软件才能够访问处理器中 1 MB 以上的物理地址空间。关于保护模式将在 [PC 汇编语言][6] 的 1.2.7 和 1.2.8 节中详细描述,更详细的内容请参阅 Intel 架构手册。在这里,你只要理解在保护模式中段地址(段基地址:偏移量)与物理地址转换的差别就可以了,并且转换后的偏移是 32 位而不是 16 位。
2. 第二,引导加载器通过 x86 的专用 I/O 指令直接访问 IDE 磁盘设备寄存器,从硬盘中读取内核。如果你想去更好地了解在这里说的专用 I/O 指令,请查看 [6.828 参考页面][8] 上的 ”IDE 硬盘控制器“ 章节。你不用学习太多的专用设备编程方面的内容:在实践中,写设备驱动程序是操作系统开发中的非常重要的部分,但是,从概念或者架构的角度看,它也是最让人乏味的部分。
理解了引导加载器源代码之后,我们来看一下 `obj/boot/boot.asm` 文件。这个文件是在引导加载器编译过程中,由我们的 GNUmakefile 创建的引导加载器的反汇编文件。这个反汇编文件让我们可以更容易地看到引导加载器代码所处的物理内存位置,并且也可以更容易地跟踪在 GDB 中步进的引导加载器发生了什么事情。同样的,`obj/kern/kernel.asm` 文件中包含了 JOS 内核的一个反汇编,它也经常被用于内核调试。
你可以使用 `b` 命令在 GDB 中设置中断点地址。比如,`b *0x7c00` 命令在地址 `0x7C00` 处设置了一个断点。当处于一个断点中时,你可以使用 `c``si` 命令去继续运行:`c` 命令让 QEMU 继续运行,直到下一个断点为止(或者是你在 GDB 中按下了 Ctrl - C`si N` 命令是每次步进 `N` 个指令。
要检查内存中的指令(除了要立即运行的下一个指令之外,因为它是由 GDB 自动输出的),你可以使用 `x/i` 命令。这个命令的语法是 `x/Ni ADDR`,其中 `N` 是连接的指令个数,`ADDR` 是开始反汇编的内存地址。
> **练习 3**
>
> 查看 [实验工具指南][16],特别是 GDB 命令的相关章节。即便你熟悉使用 GDB 也要好好看一看GDB 的一些命令比较难理解,但是它对操作系统的工作很有帮助。
在地址 0x7c00 处设置断点,它是加载后的引导扇区的位置。继续运行,直到那个断点。在 `boot/boot.S` 中跟踪代码,使用源代码和反汇编文件 `obj/boot/boot.asm` 去保持跟踪。你也可以使用 GDB 中的 `x/i` 命令去反汇编引导加载器接下来的指令,比较引导加载器源代码与在 `obj/boot/boot.asm` 和 GDB 中的反汇编文件。
`boot/main.c` 文件中跟踪进入 `bootmain()` ,然后进入 `readsect()`。识别 `readsect()` 中相关的每一个语句的准确汇编指令。跟踪 `readsect()` 中剩余的指令,然后返回到 `bootmain()` 中,识别 `for` 循环的开始和结束位置,这个循环从磁盘上读取内核的剩余扇区。找出循环结束后运行了什么代码,在这里设置一个断点,然后继续。接下来再走完引导加载器的剩余工作。
完成之后,就能够回答下列的问题了:
* 处理器开始运行 32 代码时指向到什么地方?从 16 位模式切换到 32 位模式的真实原因是什么?
* 引导加载器执行的最后一个指令是什么,内核加载之后的第一个指令是什么?
* 内核的第一个指令在哪里?
* 为从硬盘上获取完整的内核,引导加载器如何决定有多少扇区必须被读入?在哪里能找到这些信息?
#### 加载内核
我们现在来进一步查看引导加载器在 `boot/main.c` 中的 C 语言部分的详细细节。在继续之前,我们先停下来回顾一下 C 语言编程的基础知识。
> **练习 4**
>
> 下载 [pointers.c][17] 的源代码,运行它,然后确保你理解了输出值的来源的所有内容。尤其是,确保你理解了第 1 行和第 6 行的指针地址的来源、第 2 行到第 4 行的值是如何得到的、以及为什么第 5 行指向的值表面上看像是错误的。
>
> 如果你对指针的使用不熟悉Brian Kernighan 和 Dennis Ritchie就是大家知道的 'K&R'写的《C Programming Language》是一个非常好的参考书。同学们可以去买这本书这里是 [Amazon 购买链接][18]),或者在 [MIT 的图书馆有 7 个拷贝][19] 中找到其中一个。在 [SIPB Office][20] 也有三个拷贝可以细读。
>
> 在课程阅读中,[Ted Jensen 写的教程][21] 可以使用,它大量引用了 K&R 的内容。
>
> 警告:除非你特别精通 C 语言,否则不要跳过这个阅读练习。如果你没有真正理解了 C 语言中的指针,在接下来的实验中你将非常痛苦,最终你将很难理解它们。相信我们;你将不会遇到什么是 ”最困难的方式“。
要了解 `boot/main.c`,你需要了解一个 ELF 二进制格式的内容。当你编译和链接一个 C 程序时比如JOS 内核,编译器将每个 C 源文件('`.c`')转换为一个包含预期硬件平台的汇编指令编码的二进制格式的对象文件('`.o`'),然后链接器将所有编译过的对象文件组合成一个单个的二进制镜像,比如,`obj/kern/kernel`,在本案例中,它就是 ELF 格式的二进制文件,它表示是一个 ”可运行和可链接格式“。
关于这个格式的全部信息可以在 [我们的参考页面][8] 上的 [ELF 规范][22] 中找到,但是,你并不需要深入地研究这个格式 的细节。虽然完整的格式是非常强大和复杂的,但是,大多数复杂的部分是为了支持共享库的动态加载,在我们的课程中,并不需要做这些。
鉴于 6.828 的目的,你可以认为一个 ELF 可运行文件是一个用于加载信息的头文件,接下来的几个程序节,根据加载到内存中的特定地址的不同,每个都是连续的代码块或数据块。引导加载器并不修改代码或者数据;它加载它们到内存,然后开始运行它。
一个 ELF 二进制文件使用一个固定长度的 ELF 头开始紧接着是一个可变长度的程序头列出了每个加载的程序节。C 语言在 `inc/elf.h` 中定义了这些 ELF 头。在程序节中我们感兴趣的部分有:
* `.text`:程序的可运行指令。
* `.rodata`:只读数据,比如,由 C 编译器生成的 ASCII 字符串常量。(然而我们并不需要操心设置硬件去禁止写入它)
* `.data`:保持在程序的初始化数据中的数据节,比如,初始化声明所需要的全局变量,比如,像 `int x = 5;`
当链接器计算程序的内存布局的时候,它为未初始化的全局变量保留一些空间,比如,`int x;`,在内存中的被称为 `.bss` 的节后面会马上跟着一个 `.data`。C 规定 "未初始化的" 全局变量以一个 0 值开始。因此,在 ELF 二进制中 `.bss` 中并不存储内容;而是,链接器只记录地址和`.bss` 节的大小。加载器或者程序自身必须在 `.bss` 节中写入 0。
通过输入如下的命令来检查在内核中可运行的所有节的名字、大小、以及链接地址的列表:
```
athena% i386-jos-elf-objdump -h obj/kern/kernel
```
如果在你的计算机上默认使用的是一个 ELF 工具链,比如像大多数现代的 Linux 和 BSD你可以使用 `objdump` 来代替 `i386-jos-elf-objdump`
你将看到更多的节,而不仅是上面列出的那几个,但是,其它的那些节对于我们的实验目标来说并不重要。其它的那些节中大多数都是为了保留调试信息,它们一般包含在程序的可执行文件中,但是,这些节并不会被程序加载器加载到内存中。
我们需要特别注意 `.text` 节中的 "VMA"(或者链接地址)和 "LMA"(或者加载地址)。一个节的加载地址是那个节加载到内存中的地址。在 ELF 对象中,它保存在 `ph->p_pa` 域(在本案例中,它实际上是物理地址,不过 ELF 规范在这个域的意义方面规定的很模糊)。
一个节的链接地址是这个节打算在内存中运行时的地址。链接器在二进制代码中以变量的方式去编码这个链接地址,比如,当代码需要全局变量的地址时,如果二进制代码从一个未链接的地址去运行,结果将是无法运行。(它一般是去生成一个不包含任何一个绝对地址的、与位置无关的代码。现在的共享库大量使用的就是这种方法,但这是以性能和复杂性为代价的,所以,我们在 6.828 中不使用这种方法。)
一般情况下,链接和加载地址是一样的。比如,通过如下的命令去查看引导加载器的 `.text` 节:
```
athena% i386-jos-elf-objdump -h obj/boot/boot.out
```
BIOS 加载引导扇区到内存中的 0x7c00 地址,因此,这就是引导扇区的加载地址。这也是引导扇区的运行地址,因此,它也是链接地址。我们在`boot/Makefrag` 中通过传递 `-Ttext 0x7C00` 给链接器来设置链接地址,因此,链接器将在生成的代码中产生正确的内存地址。
> **练习 5**
>
> 如果你得到一个错误的引导加载器链接地址,通过再次跟踪引导加载器的前几个指令,你将会发现第一个指令会 ”中断“ 或者出错。然后在 `boot/Makefrag` 修改链接地址来修复错误,运行 `make clean`,使用 `make` 重新编译,然后再次跟踪引导加载器去查看会发生什么事情。不要忘了改回正确的链接地址,然后再次 `make clean`
我们继续来看内核的加载和链接地址。与引导加载器不同,这里有两个不同的地址:内核告诉引导加载器加载它到内存的低位地址(小于 1 MB 的地址),但是它期望在一个高位地址来运行。我们将在下一节中深入研究它是如何实现的。
除了节的信息之外,在 ELF 头中还有一个对我们很重要的域,它叫做 `e_entry`。这个域保留着程序入口的链接地址:程序的 `.text` 节中的内存地址就是将要被执行的程序的地址。你可以用如下的命令来查看程序入口链接地址:
```
athena% i386-jos-elf-objdump -f obj/kern/kernel
```
你现在应该能够理解在 `boot/main.c` 中的最小的 ELF 加载器了。它从硬盘中读取内核的每个节,并将它们节的加载地址读入到内存中,然后跳转到内核的入口点。
> **练习 6**
>
> 我们可以使用 GDB 的 `x` 命令去检查内存。[GDB 手册][23] 上讲的非常详细,但是现在,我们知道命令 `x/Nx ADDR` 是输出地址 `ADDR``N` 个词word~~致校对word 有“单词”、“命令”、“消息”、“结论” 等意思,到底哪个更适合,我个人认为根据上下文,”单词“ 更适合一些,这里推送的 ”词“ 可能是指令,也可能是数据~~)就够了。(注意在命令中所有的 `x` 都是小写。警告word的多少并没有一个普遍的标准。在 GNU 汇编中一个词word是两个字节在 xorw 中的 'w',它在这个词中就是 2 个字节)。
重置机器(退出 QEMU/GDB 然后再次启动它们)。检查内存中在 `0x00100000` 地址上的 8 个词word输出 BIOS 上的引导加载器入口,然后再次找出引导载器上的内核的入口。为什么它们不一样?在第二个断点上有什么内容?(你并不用真的在 QEMU 上去回答这个问题,只需要思考就可以。)
### 第三部分:内核
我们现在开始去更详细地研究最小的 JOS 内核。(最后你还将写一些代码!)就像引导加载器一样,内核也是从一些汇编语言代码设置一些东西开始的,以便于 C 语言代码可以正确运行。
#### 使用虚拟内存去解决位置依赖问题
前面在你检查引导加载器的链接和加载地址时,它们是完全一样的,但是内核的链接地址(可以通过 `objdump` 来输出)和它的加载地址之间差别很大。可以回到前面去看一下,以确保你明白我们所讨论的内容。(链接内核比引导加载器更复杂,因此,链接和加载地址都在 `kern/kernel.ld` 的顶部。)
操作系统内核经常链接和运行在高位的虚拟地址,比如,`0xf0100000`,为的是给让用户程序去使用处理器的虚拟地址空间的低位部分。至于为什么要这么安排,在下一个实验中我们将会知道。
许多机器在 `0xf0100000` 处并没有物理地址,因此,我们不能指望在那个位置可以存储内核。相反,我们使用处理器的内存管理硬件去映射虚拟地址 `0xf0100000`(内核代码打算运行的链接地址)到物理地址 `0x00100000`(引导加载器将内核加载到内存的物理地址的位置)。通过这种方法,虽然内核的虚拟地址是高位的,离用户程序的地址空间足够远,它将被加载到 PC 的物理内存的 1MB 的位置,只处于 BIOS ROM 之上。这种方法要求 PC 至少要多于 1 MB 的物理内存(以便于物理地址 `0x00100000` 可以工作这在上世纪九十年代以后生产的PC 上应该是没有问题的。
实际上,在下一个实验中,我们将映射整个 256 MB 的 PC 的物理地址空间,从物理地址 `0x00000000``0x0fffffff`,映射到虚拟地址 `0xf0000000``0xffffffff`。你现在就应该明白了为什么 JOS 只能使用物理内存的前 256 MB 的原因了。
现在,我们只映射前 4 MB 的物理内存,它足够我们的内核启动并运行。我们通过在 `kern/entrypgdir.c` 中手工写入静态初始化的页面目录和页面表就可以实现。现在,你不需要理解它们是如何工作的详细细节,只需要达到目的就行了。将上面的 `kern/entry.S` 文件中设置 `CR0_PG` 标志,内存引用就被视为物理地址(严格来说,它们是线性地址,但是,在 `boot/boot.S` 中设置了一个从线性地址到物理地址的映射标识,我们绝对不能改变它)。一旦 `CR0_PG` 被设置,内存引用的就是虚拟地址,这个虚拟地址是通过虚拟地址硬件将物理地址转换得到的。`entry_pgdir` 将把从 `0x00000000``0x00400000` 的物理地址范围转换在 `0xf0000000``0xf0400000` 的范围内的虚拟地址。任何不在这两个范围之一中的地址都将导致硬件异常,因为,我们还没有设置中断去处理这种情况,这种异常将导致 QEMU 去转储机器状态然后退出。(或者如果你没有在 QEMU 中应用 6.828 专用补丁,将导致 QEMU 无限重启。)
> **练习 7**
>
> 使用 QEMU 和 GDB 去跟踪进入到 JOS 内核,然后停止在 `movl %eax, %cr0` 指令处。检查 `0x00100000``0xf0100000` 处的内存。现在使用GDB 的 `stepi` 命令去单步执行那个指令。再次检查 `0x00100000``0xf0100000` 处的内存。确保你能理解这时发生的事情。
新映射建立之后的第一个指令是什么?如果没有映射到位,它将不能正常工作。在 `kern/entry.S` 中注释掉 `movl %eax, %cr0`。然后跟踪它,看看你的猜测是否正确。
#### 格式化控制台的输出
大多数人认为像 `printf()` 这样的函数是天生就有的,有时甚至认为这是 C 语言的 ”原语“。但是在操作系统的内核中,我们需要自己去实现所有的 I/O。
通过阅读 `kern/printf.c`、`lib/printfmt.c`、以及 `kern/console.c`,确保你理解了它们之间的关系。在后面的实验中,你将会明白为什么 `printfmt.c` 是位于单独的 `lib` 目录中。
> **练习 8**
>
> 我们将省略掉一小部分代码片断 —— 这部分代码片断是使用 ”%o" 模式输出八进制数字所需要的。找到它并填充到这个代码片断中。
>
> 然后你就能够回答下列的问题:
>
> 1. 解释 `printf.c``console.c` 之间的接口。尤其是,`console.c` 出口的函数是什么?这个函数是如何被 `printf.c` 使用的?
>
> 2. 在 `console.c` 中解释下列的代码:
>
> ```
> if (crt_pos >= CRT_SIZE) {
> int i;
> memcpy(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
> for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
> crt_buf[i] = 0x0700 | ' ';
> crt_pos -= CRT_COLS;
> }
> ```
>
> 3. 下列的问题你可能需要参考第一节课中的笔记。这些笔记涵盖了 GCC 在 x86 上的调用规则。
>
> 一步一步跟踪下列代码的运行:
>
> ```
> int x = 1, y = 3, z = 4;
> cprintf("x %d, y %x, z %d\n", x, y, z);
> ```
>
> 1. 在调用 `cprintf()` 时,`fmt` 做了些什么?`ap` 做了些什么?
> 2. (按运行顺序)列出 `cons_putc`、`va_arg`、以及 `vcprintf` 的调用列表。对于 `cons_putc`,同时列出它的参数。对于`va_arg`,列出调用之前和之后的 `ap` 内容?对于 `vcprintf`,列出它的两个参数值。
>
> 4. 运行下列代码:
>
> ```
> unsigned int i = 0x00646c72;
> cprintf("H%x Wo%s", 57616, &i);
> ```
> 输出是什么?解释如何在前面的练习中一步一步实现这个输出。这是一个 [ASCII 表][24],它是一个字节到字符串的映射表。
>
> 这个输出取决于 x86 是小端法这一事实。如果这个 x86 采用大端法格式,你怎么去设置 `i`,以产生相同的输出?你需要将 `57616` 改变为一个不同值吗?
>
> [这是小端法和大端法的描述][25] 和 [一个更古怪的描述][26]。
>
> 5. 在下列代码中,`y=` 会输出什么?(注意:这个问题没有确切值)为什么会发生这种情况?
> `cprintf("x=%d y=%d", 3);`
>
>
> 6. 假设修改了 GCC 的调用规则,以便于按声明的次序在栈上推送参数,这样最后的参数就是最后一个推送进去的。那你如何去改变 `cprintf` 或者它的接口,以便它仍然可以传递数量可变的参数?
#### 栈
在本实验的最后一个练习中,我们将理详细地解释在 x86 中 C 语言是如何使用栈的以及在这个过程中我们将写一个新的内核监视函数这个函数将输出栈的回溯信息一个保存了指令指针IP值的列表这个列表中有嵌套的 `call` 指令运行在当前运行点的指针值。
> **练习 9**
>
> 搞清楚内核在什么地方初始化栈,以及栈在内存中的准确位置。内核如何为栈保留空间?以及这个保留区域的 “结束” 位置是指向初始化结束后的指针吗?
x86 栈指针(`esp` 寄存器)指向当前使用的栈的最低位置。在这个区域中那个位置以下的所有部分都是空闲的。给一个栈推送一个值涉及下移栈指针和栈指针指向的位置中写入值。从栈中弹出一个值涉及到从栈指针指向的位置读取值和上移栈指针。在 32 位模式中,栈中仅能保存 32 位值,并且 `esp` 通常分为四部分。各种 x86 指令,比如,`call`,是 “硬编码” 去使用栈指针寄存器的。
相比之下,`ebp`(基指针)寄存器,按软件惯例主要是由栈关联的。在进入一个 C 函数时,函数的前序代码在函数运行期间,通常会通过推送它到栈中来保存前一个函数的基指针,然后拷贝当前的 `esp` 值到 `ebp` 中。如果一个程序中的所有函数都遵守这个规则,那么,在程序运行过程中的任何一个给定时间点,通过在 `ebp` 中保存的指针链和精确确定的函数嵌套调用顺序是如何到达程序中的这个特定的点,就可以通过栈来跟踪回溯。这种跟踪回溯的函数在实践中非常有用,比如,由于给某个函数传递了一个错误的参数,导致一个 `assert` 失败或者 `panic`,但是,你并不能确定是谁传递了错误的参数。栈的回溯跟踪可以让你找到这个惹麻烦的函数。
> **练习 10**
>
> 要熟悉 x86 上的 C 调用规则,可以在 `obj/kern/kernel.asm` 文件中找到函数 `test_backtrace` 的地址,设置一个断点,然后检查在内核启动后,每次调用它时发生了什么。每个递归嵌套的 `test_backtrace` 函数在栈上推送了多少个词word这些词word是什么
上面的练习可以给你提供关于实现栈跟踪回溯函数的一些信息,为实现这个函数,你应该去调用 `mon_backtrace()`。在 `kern/monitor.c` 中已经给你提供了这个函数的一个原型。你完全可以在 C 中去使用它,但是,你可能需要在 `inc/x86.h` 中使用到 `read_ebp()` 函数。你应该在这个新函数中实现一个到内核监视命令的钩子,以便于用户可以与它交互。
这个跟踪回溯函数将以下面的格式显示一个函数调用列表:
```
Stack backtrace:
ebp f0109e58 eip f0100a62 args 00000001 f0109e80 f0109e98 f0100ed2 00000031
ebp f0109ed8 eip f01000d6 args 00000000 00000000 f0100058 f0109f28 00000061
...
```
输出的第一行列出了当前运行的函数,名字为 `mon_backtrace`,就是它自己,第二行列出了被 `mon_backtrace` 调用的函数,第三行列出了另一个被调用的函数,依次类推。你可以输出所有未完成的栈帧。通过研究 `kern/entry.S`,你可以发现,有一个很容易的方法告诉你何时停止。
在每一行中,`ebp` 表示了那个函数进入栈的基指针:即,栈指针的位置,它就是函数进入之后,函数的前序代码设置的基指针。`eip` 值列出的是函数的返回指令指针:当函数返回时,指令地址将控制返回。返回指令指针一般指向 `call` 指令之后的指令(想一想为什么?)。在 `args` 之后列出的五个十六进制值是在问题中传递给函数的前五个参数。当然,如果函数调用时传递的参数少于五个,那么,在这里就不会列出全部五个值了。(为什么跟踪回溯代码不能检测到调用时实际上传递了多少个参数?如何去修复这个 ”缺陷“?)
下面是在阅读 K&R 的书中的第 5 章中的一些关键点,为了接下来的练习和将来的实验,你应该记住它们。
* 如果 `int *p = (int*)100`,那么 `(int)p + 1``(int)(p + 1)` 是不同的数字:前一个是 `101`,但是第二个是 `104`。当在一个指针上加一个整数时,就像第二种情况,这个整数将隐式地与指针所指向的对象相乘。
* `p[i]` 的定义与 `*(p+i)` 定义是相同的,都反映了在内存中由 `p` 指向的第 `i` 个对象。当对象大于一个字节时,上面的加法规则可以使这个定义正常工作。
* `&p[i]``(p+i)` 是相同的,获取在内存中由 p 指向的第 `i` 个对象的地址。
虽然大多数 C 程序不需要在指针和整数之间转换,但是操作系统经常做这种转换。不论何时,当你看到一个涉及内存地址的加法时,你要问你自己,你到底是要做一个整数加法还是一个指针加法,以确保做完加法后的值是正确的,而不是相乘后的结果。
> **练 11**
>
> 实现一个像上面详细描述的那样的跟踪回溯函数。一定使用与示例中相同的输出格式,否则,将会引发评级脚本的识别混乱。在你认为你做的很好的时候,运行 `make grade` 这个评级脚本去查看它的输出是否是我们的脚本所期望的结果,如果不是去修改它。你提交了你的实验 1 代码后,我们非常欢迎你将你的跟踪回溯函数的输出格式修改成任何一种你喜欢的格式。
在这时,你的跟踪回溯函数将能够给你提供导致 `mon_backtrace()` 被运行的,在栈上调用它的函数的地址。但是,在实践中,你经常希望能够知道这个地址相关的函数名字。比如,你可能希望知道是哪个有 Bug 的函数导致了你的内核崩溃。
为帮助你实现这个功能,我们提供了 `debuginfo_eip()` 函数,它在符号表中查找 `eip`,然后返回那个地址的调试信息。这个函数定义在 `kern/kdebug.c` 文件中。
> **练习 12**
>
> 修改你的栈跟踪回溯函数,对于每个 `eip`,显示相关的函数名字、源文件名、以及那个 `eip` 的行号。
`debuginfo_eip` 中,`__STAB_*` 来自哪里?这个问题的答案很长;为帮助你找到答案,下面是你需要做的一些事情:
* 在 `kern/kernel.ld` 文件中查找 `__STAB_*`
* 运行 i386-jos-elf-objdump -h obj/kern/kernel
* 运行 i386-jos-elf-objdump -G obj/kern/kernel
* 运行 i386-jos-elf-gcc -pipe -nostdinc -O2 -fno-builtin -I. -MD -Wall -Wno-format -DJOS_KERNEL -gstabs -c -S kern/init.c, and look at init.s。
* 如果引导加载器在加载二进制内核时,将符号表作为内核的一部分加载进内存中,那么,去查看它。
通过在 `stab_binsearch` 中插入调用,可以完成在 `debuginfo_eip` 中通过地址找到行号的功能。
在内核监视中添加一个 `backtrace` 命令,扩展你实现的 `mon_backtrace` 的功能,通过调用 `debuginfo_eip`,然后以下面的格式来输出每个栈帧行:
```
K> backtrace
Stack backtrace:
ebp f010ff78 eip f01008ae args 00000001 f010ff8c 00000000 f0110580 00000000
kern/monitor.c:143: monitor+106
ebp f010ffd8 eip f0100193 args 00000000 00001aac 00000660 00000000 00000000
kern/init.c:49: i386_init+59
ebp f010fff8 eip f010003d args 00000000 00000000 0000ffff 10cf9a00 0000ffff
kern/entry.S:70: <unknown>+0
K>
```
每行都给出了文件名和在那个文件中栈帧的 `eip` 所在的行,紧接着是函数的名字和那个函数的第一个指令到 `eip` 的偏移量(比如,`monitor+106` 意味着返回 `eip` 是从 `monitor` 开始之后的 106 个字节)。
为防止评级脚本引起混乱,应该将文件和函数名输出在单独的行上。
提示:`printf` 格式的字符串提供一个易用尽管有些难理解的方式去输出非空终止non-null-terminated字符串就像在 STABS 表中的这些一样。`printf("%.*s", length, string)` 输出 `string` 中的最多 `length` 个字符。查阅 `printf` 的 man 页面去搞清楚为什么这样工作。
你可以从 `backtrace` 中找到那些没有的功能。比如,你或者可能看到一个到 `monitor()` 的调用,但是没有到 `runcmd()` 中。这是因为编译器的行内in-lines函数调用。其它的优化可能导致你看到一些意外的行号。如果你从 `GNUMakefile` 删除 `-O2` 参数,`backtraces` 可能会更有意义(但是你的内核将运行的更慢)。
**到此为止, 在 `lab` 目录中的实验全部完成**,使用 `git commit` 提交你的改变,然后输入 `make handin` 去提交你的代码。
--------------------------------------------------------------------------------
via: https://sipb.mit.edu/iap/6.828/lab/lab1/
作者:[mit][a]
译者:[qhwdw](https://github.com/qhwdw)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://sipb.mit.edu
[1]:http://www.git-scm.com/
[2]:http://www.kernel.org/pub/software/scm/git/docs/user-manual.html
[3]:http://eagain.net/articles/git-for-computer-scientists/
[4]:https://sipb.mit.edu/iap/6.828/tools
[5]:https://exokernel.scripts.mit.edu/submit/
[6]:https://sipb.mit.edu/iap/6.828/readings/pcasm-book.pdf
[7]:http://www.delorie.com/djgpp/doc/brennan/brennan_att_inline_djgpp.html
[8]:https://sipb.mit.edu/iap/6.828/reference
[9]:https://sipb.mit.edu/iap/6.828/readings/i386/toc.htm
[10]:http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html
[11]:http://developer.amd.com/documentation/guides/Pages/default.aspx#manuals
[12]:http://www.qemu.org/
[13]:http://www.gnu.org/software/gdb/
[14]:http://web.archive.org/web/20040404164813/members.iweb.net.au/%7Epstorr/pcbook/book2/book2.htm
[15]:https://sipb.mit.edu/iap/6.828/readings/boot-cdrom.pdf
[16]:https://sipb.mit.edu/iap/6.828/labguide
[17]:https://sipb.mit.edu/iap/6.828/files/pointers.c
[18]:http://www.amazon.com/C-Programming-Language-2nd/dp/0131103628/sr=8-1/qid=1157812738/ref=pd_bbs_1/104-1502762-1803102?ie=UTF8&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;s=books
[19]:http://library.mit.edu/F/AI9Y4SJ2L5ELEE2TAQUAAR44XV5RTTQHE47P9MKP5GQDLR9A8X-10422?func=item-global&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;doc_library=MIT01&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;doc_number=000355242&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;year=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;volume=&amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;sub_library=
[20]:http://sipb.mit.edu/
[21]:https://sipb.mit.edu/iap/6.828/readings/pointers.pdf
[22]:https://sipb.mit.edu/iap/6.828/readings/elf.pdf
[23]:http://sourceware.org/gdb/current/onlinedocs/gdb_9.html#SEC63
[24]:http://web.cs.mun.ca/%7Emichael/c/ascii-table.html
[25]:http://www.webopedia.com/TERM/b/big_endian.html
[26]:http://www.networksorcery.com/enp/ien/ien137.txt

View File

@ -0,0 +1,120 @@
cTop - 用于容器监控的 CLI 工具
======
最近 Linux 容器很火,我们中的大多数人甚至已经在使用它,同时一些人也开始学习它。
我们已经介绍了有名的 GUI用户图形界面 工具如 Portainer 和 Rancher 。这将会有助于我们通过 GUI 管理容器。
这篇指南将会通过 cTop 命令帮助我们理解和监控 Linux 容器。它是一个类似 top 命令的命令行工具。
### 什么是 cTop
[ctop][1] 为多个容器提供了一个简洁凝练的实时指标概览。它是一个类 Top 的针对容器指标的界面。
它展示了容器指标比如 CPU 利用率,内存利用率,磁盘 I/O 读写,进程 ID(PID)和网络发送TX - 从此服务器发送以及接受RX - 此服务器接受)。
ctop 伴随着对 Docker 和 runc 的内建支持;对其他容器和集群系统的连接计划在未来版本中推出。
它不需要任何参数并且默认使用 Docker 主机变量。
**建议阅读:**
**(#)** [Portainer 一个简单的 Docker 管理 GUI][2]
**(#)** [Rancher 一个完整的生产环境容器管理平台][3]
### 如何安装 cTop
开发者提供了一个简单的 shell 脚本来帮助我们直接使用 ctop。我们要做的只是在“/bin”目录下下载 ctop shell 文件来保证全局访问。最后给予 ctop 脚本文件执行权限。
在“/usr/local/bin”目录下下载 ctop shell 脚本。
```
$ sudo wget https://github.com/bcicen/ctop/releases/download/v0.7/ctop-0.7-linux-amd64 -O /usr/local/bin/ctop
```
对 ctop shell 脚本设置执行权限。
```
$ sudo chmod +x /usr/local/bin/ctop
```
另外你可以通过 docker 来安装和运行 ctop。在此之前先确保你已经安装过 docker。为了安装 docker参考以下链接。
**建议阅读:**
**(#)** [如何在 Linux 上安装 Docker][4]
**(#)** [如何在 Linux 上玩转 Docker 镜像][5]
**(#)** [如何在 Linux 上玩转 Docker 容器][6]
**(#)** [如何在 Docker 容器中安装,运行应用][7]
```
$ docker run --rm -ti \
--name=ctop \
-v /var/run/docker.sock:/var/run/docker.sock \
quay.io/vektorlab/ctop:latest
```
### 如何使用 cTop
直接启动 ctop 程序而不用任何参数。默认它与“a”键绑定来展示所有容器运行的和没运行的
ctop 头部显示你的系统时间和容器的总数。
```
$ ctop
```
你可能得到以下类似输出。
![][9]
### 如何管理容器
你可以使用 ctop 来管理容器。选择一个你想要管理的容器然后按下“Enter”键选择所需选项如 start,stop,remove 等。
![][10]
### 如何给容器排序
默认 ctop 使用 state 字段来给容器排序。按下“s”键来用不同的方面给容器排序。
![][11]
### 如何查看容器指标
如何你想要查看关于容器的更多细节和指标只用选择你想要查看的相应容器然后按“o”键。
![][12]
### 如何查看容器日志
选择你想要查看日志的相应容器然后按“l”键。
![][13]
### 仅显示活动容器
使用“-a”选项运行 ctop 命令来仅显示活动容器
![][14]
### 打开帮助对话框
运行 ctop,只需按“h”键来打开帮助部分。
![][15]
--------------------------------------------------------------------------------
via: https://www.2daygeek.com/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux/
作者:[2DAYGEEK][a]
译者:[kimii](https://github.com/kimii)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]:https://www.2daygeek.com/author/2daygeek/
[1]:https://github.com/bcicen/ctop
[2]:https://www.2daygeek.com/portainer-a-simple-docker-management-gui/
[3]:https://www.2daygeek.com/rancher-a-complete-container-management-platform-for-production-environment/
[4]:https://www.2daygeek.com/install-docker-on-centos-rhel-fedora-ubuntu-debian-oracle-archi-scentific-linux-mint-opensuse/
[5]:https://www.2daygeek.com/list-search-pull-download-remove-docker-images-on-linux/
[6]:https://www.2daygeek.com/create-run-list-start-stop-attach-delete-interactive-daemonized-docker-containers-on-linux/
[7]:https://www.2daygeek.com/install-run-applications-inside-docker-containers/
[8]:data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7
[9]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-1.png
[10]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-2.png
[11]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-3.png
[12]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-4a.png
[13]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-7.png
[14]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-5.png
[15]:https://www.2daygeek.com/wp-content/uploads/2018/02/ctop-a-command-line-tool-for-container-monitoring-and-management-in-linux-6.png