蚊章汇 - Robert Mullins
type: channel
link: https://linuxcn.bwsl.wang/author/1169/
description: Robert Mullins
items: 计算机实验室之树莓派：课程 5 OK05
items: 计算机实验室之树莓派：课程 4 OK04
items: 计算机实验室之树莓派：课程 3 OK03
items: 计算机实验室之树莓派：课程 2 OK02
items: 计算机实验室之树莓派：课程 1 OK01
items: 计算机实验室之树莓派：课程 0 简介

计算机实验室之树莓派：课程 5 OK05
type: item
date: 2019-02-12T20:30:11+08:00
description: OK05 课程构建于课程 OK04 的基础，使用它来闪烁摩尔斯电码的 SOS 序列（...---...）。这里假设你已经有了 课程 4：OK04 操作系统的代码作为基础。1、数据到目前为止，我们与操作系统有关的所有内容提供的都是指令。然而有时候，指令只是完成了一半的工作。我们的操作系统可能还需要数据。一些早期的操作系统确实只允许特定文件中的特定类型的数据，但是这通常被认为限制太多了。现代方法确实可以使程序变得复杂的多。通常，数据就是些很重要的值。你可能接受过培训，认为数据就是某种类型的，比如，文本文件包含文本，图像文件包含图片，等等。说实话，这只是你的想法而已。计算机上的全部数据都是二进制数字，重要的是我们选择用什么来解释这些数据。在这个例子中，我们会用一个闪灯序列作为数据保存下来。在 main.s 结束处复制下面的代码：.section .data %定义 .data 段 .align 2 %对齐 pattern: %定义整形变量 .int 0b11111111101010100010001000101010.align num 确保下一行代码的地址是 2 num 的整数倍。.int val 输出数值 val。要区分数据和代码，我们将数据都放在 .data 区域。我已经将该区域包含在操作系统的内存布局图。我选择将数据放到代码后面。将我们的指令和数据分开保存的原因是，如果最后我们在自己的操作系统上实现一些安全措施，我们就需要知道代码的那些部分是可以执行的，而那些部分是不行的。我在这里使用了两个新命令 .align 和 .int。.align 保证接下来的数据是按照 2 的乘方对齐的。在这个里，我使用 .align 2 ，意味着数据最终存放的地址是 2 2=4 的整数倍。这个操作是很重要的，因为我们用来读取内存的指令 ldr 要求内存地址是 4 的倍数。命令 .int 直接复制它后面的常量到输出。这意味着 11111111101010100010001000101010 2 将会被存放到输出，所以该标签模式实际是将这段数据标识为模式。关于数据的一个挑战是寻找一个高效和有用的展示形式。这种保存一个开、关的时间单元的序列的方式，运行起来很容易，但是将很难编辑，因为摩尔斯电码的 - 或 . 样式丢失了。如我提到的，数据可以代表你想要的所有东西。在这里我编码了摩尔斯电码的 SOS 序列，对于不熟悉的人，就是 ...---...。我使用 0 表示一个时间单元的 LED 灭灯，而 1 表示一个时间单元的 LED 亮。这样，我们可以像这样编写一些代码在数据中显示一个序列，然后要显示不同序列，我们所有需要做的就是修改这段数据。下面是一个非常简单的例子，操作系统必须一直执行这段程序，解释和展示数据。复制下面几行到 main.s 中的标记 loop$ 之前。ptrn .req r4 %重命名 r4 为 ptrn ldr ptrn,=pattern %加载 pattern 的地址到 ptrn ldr ptrn,[ptrn] %加载地址 ptrn 所在内存的值 seq .req r5 %重命名 r5 为 seq mov seq,#0 %seq 赋值为 0这段代码加载 pattrern 到寄存器 r4，并加载 0 到寄存器 r5。r5 将是我们的序列位置，所以我们可以追踪我们已经展示了多少个 pattern。如果 pattern 的当前位置是 1 且仅有一个 1，下面的代码将非零值放入 r1。mov r1,#1 %加载1到 r1 lsl r1,seq %对r1 的值逻辑左移 seq 次 and r1,ptrn %按位与这段代码对你调用 SetGpio 很有用，它必须有一个非零值来关掉 LED，而一个 0 值会打开 LED。现在修改 main.s 中你的全部代码，这样代码中每次循环会根据当前的序列数设置 LED，等待 250000 毫秒（或者其他合适的延时），然后增加序列数。当这个序列数到达 32 就需要返回 0。看看你是否能实现这个功能，作为额外的挑战，也可以试着只使用一条指令。2、当你玩得开心时，时间过得很快你现在准备好在树莓派上实验。应该闪烁一串包含 3 个短脉冲，3 个长脉冲，然后 3 个短脉冲的序列。在一次延时之后，这种模式应该重复。如果这不工作，请查看我们的问题页。一旦它工作，祝贺你已经抵达 OK 系列教程的结束点。在这个系列我们学习了汇编代码，GPIO 控制器和系统定时器。我们已经学习了函数和 ABI，以及几个基础的操作系统原理，已经关于数据的知识。你现在已经可以准备学习下面几个更高级的课程的某一个。Screen 系列是接下来的，会教你如何通过汇编代码使用屏幕。Input 系列教授你如何使用键盘和鼠标。到现在，你已经有了足够的信息来制作操作系…

计算机实验室之树莓派：课程 4 OK04
type: item
date: 2019-02-11T07:42:00+08:00
description: OK04 课程在 OK03 的基础上进行构建，它教你如何使用定时器让 OK 或 ACT LED 灯按精确的时间间隔来闪烁。假设你已经有了 课程 3：OK03 的操作系统，我们将以它为基础来构建。1、一个新设备定时器是树莓派保持时间的唯一方法。大多数计算机都有一个电池供电的时钟，这样当计算机关机后仍然能保持时间。到目前为止，我们仅看了树莓派硬件的一小部分，即 GPIO 控制器。我只是简单地告诉你做什么，然后它会发生什么事情。现在，我们继续看定时器，并继续带你去了解它的工作原理。和 GPIO 控制器一样，定时器也有地址。在本案例中，定时器的基地址在 20003000 16。阅读手册我们可以找到下面的表：表 1.1 GPIO 控制器寄存器地址大小 / 字节名字描述读或写200030004Control / Status用于控制和清除定时器通道比较器匹配的寄存器RW200030048Counter按 1 MHz 的频率递增的计数器R2000300C4Compare 00 号比较器寄存器RW200030104Compare 11 号比较器寄存器RW200030144Compare 22 号比较器寄存器RW200030184Compare 33 号比较器寄存器RW这个表只告诉我们一部分内容，在手册中描述了更多的字段。手册上解释说，定时器本质上是按每微秒将计数器递增 1 的方式来运行。每次它是这样做的，它将计数器的低 32 位（4 字节）与 4 个比较器寄存器进行比较，如果匹配它们中的任何一个，它更新 Control/Status 以反映出其中有一个是匹配的。关于 位 bit 、 字节 byte 、 位字段 bit field 、以及数据大小的更多内容如下：一个位是一个单个的二进制数的名称。你可能还记得，一个单个的二进制数既可能是一个 1，也可能是一个 0。一个字节是一个 8 位集合的名称。由于每个位可能是 1 或 0 这两个值的其中之一，因此，一个字节有 2 8 = 256 个不同的可能值。我们一般解释一个字节为一个介于 0 到 255（含）之间的二进制数。一个位字段是解释二进制的另一种方式。二进制可以解释为许多不同的东西，而不仅仅是一个数字。一个位字段可以将二进制看做为一系列的 1（开） 或 0（关）的开关。对于每个小开关，我们都有一个意义，我们可以使用它们去控制一些东西。我们已经遇到了 GPIO 控制器使用的位字段，使用它设置一个针脚的开或关。位为 1 时 GPIO 针脚将准确地打开或关闭。有时我们需要更多的选项，而不仅仅是开或关，因此我们将几个开关组合到一起，比如 GPIO 控制器的函数设置（如上图），每 3 位为一组控制一个 GPIO 针脚的函数。我们的目标是实现一个函数，这个函数能够以一个时间数量为输入来调用它，这个输入的时间数量将作为等待的时间，然后返回。想一想如何去做，想想我们都拥有什么。我认为这将有两个选择：从计数器中读取一个值，然后保持分支返回到相同的代码，直到计数器的等待时间数量大于它。从计数器中读取一个值，加上要等待的时间数量，将它保存到比较器寄存器，然后保持分支返回到相同的代码处，直到 Control / Status 寄存器更新。这两种策略都工作的很好，但在本教程中，我们将只实现第一个。原因是比较器寄存器更容易出错，因为在增加等待时间并保存它到比较器的寄存器期间，计数器可能已经增加了，并因此可能会不匹配。如果请求的是 1 微秒（或更糟糕的情况是 0 微秒）的等待，这样可能导致非常长的意外延迟。像这样存在被称为“并发问题”的问题，并且几乎无法解决。2、实现我将把这个创建完美的等待方法的挑战基本留给你。我建议你将所有与定时器相关的代码都放在一个名为 systemTimer.s 的文件中（理由很明显）。关于这个方法的复杂部分是，计数器是一个 8 字节值，而每个寄存器仅能保存 4 字节。所以，计数器值将分到 2 个寄存器中。大型的操作系统通常使用等待函数来抓住机会在后台执行任务。下列的代码块是一个示例。ldrd r0,r1,[r2,#4]ldrd regLow,regHigh,[src,#val] 从 src 中的数加上 val 之和的地址加载 8 字节到寄存器 regLow 和 regHigh 中。上面的代码中你可以发现一个很有用的指令是 ldrd。它加载 8 字节的内存到两个寄存器中。在本案例中，这 8 字节内存从寄存器 r2 中的地址 + 4 开始，将被复制进寄存器 r0 和 r1。这种安排的稍微复杂之处在于 r1 实际上只持有了高位 4 字节。换句话说就是，如果如果计数器的值是 999,999,999,999 10 = 1110100011010100101001010000111111111111 2 ，那么寄…

计算机实验室之树莓派：课程 3 OK03
type: item
date: 2019-02-09T06:59:55+08:00
description: OK03 课程基于 OK02 课程来构建，它教你在汇编中如何使用函数让代码可复用和可读性更好。假设你已经有了 课程 2：OK02 的操作系统，我们将以它为基础。1、可复用的代码到目前为止，我们所写的代码都是以我们希望发生的事为顺序来输入的。对于非常小的程序来说，这种做法很好，但是如果我们以这种方式去写一个完整的系统，所写的代码可读性将非常差。我们应该去使用函数。一个函数是一段可复用的代码片断，可以用于去计算某些答案，或执行某些动作。你也可以称它们为 过程 procedure 、 例程 routine 或 子例程 subroutine 。虽然它们都是不同的，但人们几乎都没有正确地使用这个术语。你应该在数学上遇到了函数的概念。例如，余弦函数应用于一个给定的数时，会得到介于 -1 到 1 之间的另一个数，这个数就是角的余弦。一般我们写成 cos(x) 来表示应用到一个值 x 上的余弦函数。在代码中，函数可以有多个输入（也可以没有输入），然后函数给出多个输出（也可以没有输出），并可能导致副作用。例如一个函数可以在一个文件系统上创建一个文件，第一个输入是它的名字，第二个输入是文件的长度。函数可以认为是一个“黑匣子”。我们给它输入，然后它给我们输出，而我们不需要知道它是如何工作的。在像 C 或 C++ 这样的高级代码中，函数是语言的组成部分。在汇编代码中，函数只是我们的创意。理想情况下，我们希望能够在我们的寄存器中设置一些输入值，然后分支切换到某个地址，然后预期在某个时刻分支返回到我们代码，并通过代码来设置输出值到寄存器。这就是我们所设想的汇编代码中的函数。困难之处在于我们用什么样的方式去设置寄存器。如果我们只是使用平时所接触到的某种方法去设置寄存器，每个程序员可能使用不同的方法，这样你将会发现你很难理解其他程序员所写的代码。另外，编译器也不能像使用汇编代码那样轻松地工作，因为它们压根不知道如何去使用函数。为避免这种困惑，为每个汇编语言设计了一个称为 应用程序二进制接口 Application Binary Interface （ABI）的标准，由它来规范函数如何去运行。如果每个人都使用相同的方法去写函数，这样每个人都可以去使用其他人写的函数。在这里，我将教你们这个标准，而从现在开始，我所写的函数将全部遵循这个标准。该标准规定，寄存器 r0、r1、r2 和 r3 将被依次用于函数的输入。如果函数没有输入，那么它不会在意值是什么。如果只需要一个输入，那么它应该总是在寄存器 r0 中，如果它需要两个输入，那么第一个输入在寄存器 r0 中，而第二个输入在寄存器 r1 中，依此类推。输出值也总是在寄存器 r0 中。如果函数没有输出，那么 r0 中是什么值就不重要了。另外，该标准要求当一个函数运行之后，寄存器 r4 到 r12 的值必须与函数启动时的值相同。这意味着当你调用一个函数时，你可以确保寄存器 r4 到 r12 中的值没有发生变化，但是不能确保寄存器 r0 到 r3 中的值也没有发生变化。当一个函数运行完成后，它将返回到启动它的代码分支处。这意味着它必须知道启动它的代码的地址。为此，需要一个称为 lr（链接寄存器）的专用寄存器，它总是在保存调用这个函数的指令后面指令的地址。表 1.1 ARM ABI 寄存器用法寄存器简介保留规则r0参数和结果否r0 和 r1 用于给函数传递前两个参数，以及函数返回的结果。如果函数返回值不使用它，那么在函数运行之后，它们可以携带任何值。r1参数和结果否 r2参数否r2 和 r3 用去给函数传递后两个参数。在函数运行之后，它们可以携带任何值。r3参数否 r4通用寄存器是r4 到 r12 用于保存函数运行过程中的值，它们的值在函数调用之后必须与调用之前相同。r5通用寄存器是 r6通用寄存器是 r7通用寄存器是 r8通用寄存器是 r9通用寄存器是 r10通用寄存器是 r11通用寄存器是 r12通用寄存器是 lr返回地址否当函数运行完成后，lr 中保存了分支的返回地址，但在函数运行完成之后，它将保存相同的地址。sp栈指针是sp 是栈指针，在下面有详细描述。它的值在函数运行完成后，必须是相同的。通常，函数需要使用很多的寄存器，而不仅是 r0 到 r3。但是，由于 r4 到 r12 必须在函数完成之后值必须保持相同，因此它们需要被保存到某个地方。我们将它们保存到称为栈的地方。一个 栈 stack 就是我们在计算中用来保存值的一个很形象的方法。就像是摞起来的一堆盘子，你可以从上到下来移除它们，而添加它们时，你只能从下到上来添加。在函数运行时，使用栈来保存寄存器值是个非常好的创意。例如，如果我有一个函数需要去使用寄存器 r4 和 r5，它将在一个栈上存放这些寄存器的值。最后用这种方式，它可以再次将它拿回来。更高明的是，如果为…

计算机实验室之树莓派：课程 2 OK02
type: item
date: 2019-01-26T20:01:05+08:00
description: OK02 课程构建于 OK01 课程的基础上，通过不停地打开和关闭 OK 或 ACT LED 指示灯来实现闪烁。假设你已经有了 课程 1：OK01 操作系统的代码，它将是这一节课的基础。1、等待等待是操作系统开发中非常有用的部分。操作系统经常发现自己无事可做，以及必须要延迟。在这个例子中，我们希望通过等待，让 LED 灯打开、关闭的闪烁可以看到。如果你只是打开和关闭它，你将看到这个视觉效果，因为计算机每秒种可以打开和关闭它好几千次（LCTT 译注：视觉暂留效应会使你难以发觉它的闪烁）。在后面的课程中，我们将看到精确的等待，但是现在，我们只要简单地去消耗时间就足够了。mov r2,#0x3F0000 wait1$: sub r2,#1 cmp r2,#0 bne wait1$sub reg,#val 从寄存器 reg 中的值上减去数字 valcmp reg,#val 将寄存器中的值与数字 val 进行比较。如果最后的比较结果是不相等，那么执行后缀了 ne 的 b 命令。上面是一个很常见的产生延迟的代码片段，由于每个树莓派基本上是相同的，所以产生的延迟大致也是相同的。它的工作原理是，使用一个 mov 命令将值 3F0000 16 推入到寄存器 r2 中，然后将这个值减 1，直到这个值减到 0 为止。在这里使用了三个新命令 sub、 cmp 和 bne。sub 是减法命令，它只是简单地从第一个参数中的值减去第二个参数中的值。cmp 是个很有趣的命令。它将第一个参数与第二个参数进行比较，然后将比较结果记录到一个称为当前处理器状态寄存器的专用寄存器中。你其实不用担心它，它记住的只是两个数谁大或谁小，或是相等而已。 1 bne 其实是一个伪装的分支命令。在 ARM 汇编语言家族中，任何指令都可以有条件地运行。这意味着如果上一个比较结果是某个确定的结果，那个指令才会运行。这是个非常有意思的技巧，我们在后面将大量使用到它，但在本案例中，我们在 b 命令后面的 ne 后缀意思是 “只有在上一个比较的结果是值不相等，才去运行该分支”。ne 后缀可以使用在任何命令上，其它几个（总共 16 个）条件也是如此，比如 eq 表示等于，而 lt 表示小于。2、组合到一起上一节讲我提到过，通过将 GPIO 地址偏移量设置为 28（即：str r1,[r0,#28]）而不是 40 即可实现 LED 的关闭。因此，你需要去修改课程 OK01 的代码，在打开 LED 后，运行等待代码，然后再关闭 LED，再次运行等待代码，并包含一个回到开始位置的分支。注意，不需要重新启用 GPIO 的 16 号针脚的输出功能，这个操作只需要做一次就可以了。如果你想更高效，我建议你复用 r1 寄存器的值。所有课程都一样，你可以在 下载页面 找到所有的解决方案。需要注意的是，必须保证你的所有标签都是唯一的。当你写了 wait1$: 你其它行上的标签就不能再使用 wait1$ 了。在我的树莓派上，它大约是每秒闪两次。通过改变我们所设置的 r2 寄存器中的值，可以很轻松地修改它。但是，不幸的是，我不能够精确地预测它的运行速度。如果你的树莓派未按预期正常工作，请查看我们的故障排除页面，如果它正常工作，恭喜你。在这个课程中，我们学习了另外两个汇编命令：sub 和 cmp，同时学习了 ARM 中如何实现有条件运行。在下一个课程，课程 3：OK03 中我们将学习如何编写代码，以及建立一些代码复用的标准，并且如果需要的话，可能会使用 C 或 C++ 来写代码。如果你点了这个链接，说明你一定想知道它的具体内容。CPSR 是一个由许多独立的比特位组成的 32 比特寄存器。它有一个位用于表示正数、零和负数。当一个 cmp 指令运行后，它从第一个参数上减去第二个参数，然后用这个位记下它的结果是正数、零还是负数。如果是零意味着它们相等（a-b=0 暗示着 a=b）如果为正数意味着 a 大于 b（a-b>0 暗示着 a>b），如果为负数意味着小于。还有其它比较指令，但 cmp 指令最直观。 ↩via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok02.html作者：Robert Mullins 选题：lujun9972 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出

计算机实验室之树莓派：课程 1 OK01
type: item
date: 2019-01-20T00:36:00+08:00
description: OK01 课程讲解了树莓派如何入门，以及在树莓派上如何启用靠近 RCA 和 USB 端口的 OK 或 ACT 的 LED 指示灯。这个指示灯最初是为了指示 OK 状态的，但它在第二版的树莓派上被改名为 ACT。1、入门我们假设你已经访问了下载页面，并且已经获得了必需的 GNU 工具链。也下载了一个称为操作系统模板的文件。请下载这个文件并在一个新目录中解开它。2、开始现在，你已经展开了这个模板文件，在 source 目录中创建一个名为 main.s 的文件。这个文件包含了这个操作系统的代码。具体来看，这个文件夹的结构应该像下面这样：build/ (empty) source/ main.s kernel.ld LICENSE Makefile用文本编辑器打开 main.s 文件，这样我们就可以输入汇编代码了。树莓派使用了称为 ARMv6 的汇编代码变体，这就是我们即将要写的汇编代码类型。扩展名为 .s 的文件一般是汇编代码，需要记住的是，在这里它是 ARMv6 的汇编代码。首先，我们复制下面的这些命令。.section .init .globl _start _start:实际上，上面这些指令并没有在树莓派上做任何事情，它们是提供给汇编器的指令。汇编器是一个转换程序，它将我们能够理解的汇编代码转换成树莓派能够理解的机器代码。在汇编代码中，每个行都是一个新的命令。上面的第一行告诉汇编器 1 在哪里放我们的代码。我们提供的模板中将它放到一个名为 .init 的节中的原因是，它是输出的起始点。这很重要，因为我们希望确保我们能够控制哪个代码首先运行。如果不这样做，首先运行的代码将是按字母顺序排在前面的代码！.section 命令简单地告诉汇编器，哪个节中放置代码，从这个点开始，直到下一个 .section 或文件结束为止。在汇编代码中，你可以跳行、在命令前或后放置空格去提升可读性。接下来两行是停止一个警告消息，它们并不重要。 2 3、第一行代码现在，我们正式开始写代码。计算机执行汇编代码时，是简单地一行一行按顺序执行每个指令，除非明确告诉它不这样做。每个指令都是开始于一个新行。复制下列指令。ldr r0,=0x20200000ldr reg,=val 将数字 val 加载到名为 reg 的寄存器中。那是我们的第一个命令。它告诉处理器将数字 0x20200000 保存到寄存器 r0 中。在这里我需要去回答两个问题， 寄存器 register 是什么？0x20200000 是一个什么样的数字？寄存器在处理器中就是一个极小的内存块，它是处理器保存正在处理的数字的地方。处理器中有很多寄存器，很多都有专门的用途，我们在后面会一一接触到它们。最重要的有十三个（命名为 r0、r1、r2、…、r9、r10、r11、r12），它们被称为通用寄存器，你可以使用它们做任何计算。由于是写我们的第一行代码，我们在示例中使用了 r0，当然你可以使用它们中的任何一个。只要后面始终如一就没有问题。树莓派上的一个单独的寄存器能够保存任何介于 0 到 4,294,967,295（含）之间的任意整数，它可能看起来像一个很大的内存，实际上它仅有 32 个二进制比特。0x20200000 确实是一个数字。只不过它是以十六进制表示的。下面的内容详细解释了十六进制的相关信息：延伸阅读：十六进制解释十六进制是另一种表示数字的方式。你或许只知道十进制的数字表示方法，十进制共有十个数字：0、1、2、3、4、5、6、7、8 和 9。十六进制共有十六个数字：0、1、2、3、4、5、6、7、8、9、a、b、c、d、e 和 f。你可能还记得十进制是如何用位制来表示的。即最右侧的数字是个位，紧接着的左边一位是十位，再接着的左边一位是百位，依此类推。也就是说，它的值是 100 × 百位的数字，再加上 10 × 十位的数字，再加上 1 × 个位的数字。从数学的角度来看，我们可以发现规律，最右侧的数字是 10 0 = 1s，紧接着的左边一位是 10 1 = 10s，再接着是 10 2 = 100s，依此类推。我们设定在系统中，0 是最低位，紧接着是 1，依此类推。但如果我们使用一个不同于 10 的数字为幂底会是什么样呢？我们在系统中使用的十六进制就是这样的一个数字。上面的数学等式表明，十进制的数字 567 等于十六进制的数字 237。通常我们需要在系统中明确它们，我们使用下标 10 表示它是十进制数字，用下标 16 表示它是十六进制数字。由于在汇编代码中写上下标的小数字很困难，因此我们使用 0x 来表示它是一个十六进制的数字，因此 0x237 的意思就是 237 16 。那么，后面的 a、b、c、d、e 和 f 又是什么呢？好问题！在十六进制中为了能够写每个数字，我们就需要额外的东西。例如 9 16 =…

计算机实验室之树莓派：课程 0 简介
type: item
date: 2019-01-10T03:37:00+08:00
description: 这个课程简介不包含实践内容，但它解释了一个操作系统的基本概念、汇编代码、和其它很重要的一些基本原理。如果你想直接进入实践环节，跳过本课程并不会有什么问题。1、操作系统操作系统就是一个非常复杂的程序。它的任务就是组织安排计算机上的其它程序，包括共享计算机的时间、内存、硬件和其它资源。你可能听说过的一些比较大的桌面操作系统家族有 GNU/Linux、Mac OS X 和 Microsoft Windows。其它的设备比如电话，也需要操作系统，它可能使用的操作系统是 Android、iOS 和 Windows Phone。 1 由于操作系统是用来与计算机系统上的硬件进行交互的，所以它必须了解系统上硬件专有的信息。为了能让操作系统适用于各种类型的计算机，发明了 驱动程序 的概念。驱动程序是为了能够让操作系统与特定的硬件进行交互而添加（并可删除）到操作系统上的一小部分代码。在本课程中，我们并不涉及如何创建可删除的驱动程序，而是专注于特定的一个硬件：树莓派。操作系统有各种不同的设计方式，在本课程中，我们只触及操作系统设计的皮毛。本课程中，我们主要专注于操作系统与各种硬件的交互部分，因为这经常是比较棘手的部分，并且也是在网络上文档和帮助最少的部分。2、汇编代码处理器每秒可以执行上百万的指令，但是这些指令必须要简单。本课程几乎要完全靠汇编代码来写。汇编代码非常接近计算机的底层。计算机其实是靠一个叫处理器的设备来工作的，处理器能够执行像加法这样的简单任务，还有一组叫做 RAM 的芯片，它能够用来保存数字。当计算机通电后，处理器执行程序员给定的一系列指令，这将导致内存中的数字发生变化，以及与连接的硬件进行交互。汇编代码只是将这些机器命令转换为人类可读的文本。常规的编程就是，程序员使用编程语言，比如 C++、Java、C#、Basic 等等来写代码，然后一个叫编译器的程序将程序员写的代码转换成汇编代码，然后进一步转换为二进制代码。 2 二进制代码才是计算机真正能够理解的东西，但它是人类无法读取的东西。汇编代码比二进制代码好一点，至少它的命令是人类可读的，但它仍然让人很沮丧。请记住，你用汇编代码写的每个命令都是处理器可以直接认识的，因此这些命令设计的很简单，因为物理电路必须能够处理每个命令。和普通编程一样，也有很多不同的汇编代码编程语言，但与普通编程不一样的是，每个汇编编程语言是面对不同的处理器的，每种处理器设计为去理解不同的语言。因此，用一个针对某种机器设计的汇编语言所写的汇编代码，是不能在其它种类的机器上运行的。很多情况下，这都是一个大灾难，因此每个程序都必须在使用它的不同种类的机器上重写一遍，但对于操作系统，这不是个问题，因为在不同的硬件上它必须得重写。尽管如此，大多数操作系统都是用 C++ 或 C 来写的，这样它们就可以很容易地在不同种类的硬件上使用，只需要重写那些必须用汇编代码来实现的部分即可。现在，你已经准备好进入第一节课了，它是 课程 1 OK01。要查看更完整的操作系统列表，请参照：操作系统列表 - Wikipedia ↩当然，我简化了普通编程的这种解释，实际上它在很大程度上取决于语言和机器。感兴趣的话，参见 编译器 - Wikipedia ↩via: https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/introduction.html作者：Robert Mullins 选题：lujun9972 译者：qhwdw 校对：wxy本文由 LCTT 原创编译，Linux中国 荣誉推出