【操作系统】Oranges学习笔记(六) 第七章 输入/输出系统

14 篇文章 27 订阅
订阅专栏
11 篇文章 3 订阅
订阅专栏

完美主义者常常因试图努力把一件事做好而放弃对新领域的尝试,从而使做事的机会成本增加。有时候回头一看才发现,自己在某件事情上已花费了太多时间。而实际上,暂时的妥协可能并不会影响到最终完美结果的呈现。因为不但知识需要沉淀,事情之间也总是有关联的。
我们刚刚实现了简单的进程,你现在可能很想把它做得更加完善,比如进一步改进调度算法、增加通信机制等。但是这些工作不但做起来没有尽头,而且有些也是难以实现的,因为进程必须与I/O、内存管理等其他模块一起工作。
而且,简单的进程更有利于我们思考和控制。

现在,我们就来实现简单的I/O系统。


7.1 键盘

到目前为止,我们的OS一旦启动就不再受控制了,只能等待结果的出现,我们需要交互。而交互的手段,首先当然是键盘

7.1.1 从中断开始──键盘初体验

说起键盘,你可能想起8259A的IRQ1对应的就是键盘,在第5章中做过一个小小的试验(见代码5.57和图5.16)。那时我们没有为键盘中断指定专门的处理程序,所以当按下键盘时只能打印一行“spurious_irq:0x1”。

现在我们来写一个专门的处理程序。新建一个文件 keyboard.c,添加一个非常简单的键盘中断处理程序(代码7.1)。
在这里插入图片描述

结果是每按一次键,打印一个星号,像在输入密码。为了不受其他进程输出的影响,我们把其他进程的输出都注释掉。然后添加指定中断处理程序的代码并打开键盘中断(代码7.2)。
在这里插入图片描述
不要忘了在 proto.h 中声明 init_keyboard( ) 并调用之(代码7.3)。
在这里插入图片描述
修改Makefile之后,就可以 make 并运行了,如图所示,敲击一下键盘就可以看到出现星号:
在这里插入图片描述
但是出现一个星号之后键盘就不再响应了。事情比我们想像的要复杂一些,我们还是要从头说起。

7.1.2 AT、PS/2键盘

键盘 PS/2 键盘和 USB 键盘是当今最流行的两种键盘。现在,看看你的键盘连接计算机的接口,如果看上去很像图7.2左图的样子,那么你用的应该是个 PS/2 键盘;如果是一个有棱有角的扁口,那么它很可能是个 USB 键盘(如果你居然不熟悉USB口的话)。

再有一种可能,你还在使用一种稍微老一点的 AT 键盘,它看上去就像图7.2右图的形状。有人把AT键盘称为大口键盘,而把 PS/2 称为小口键盘。因为 AT 键盘的接口稍微大一些而 PS/2 的稍微小一些。它们的接口分别叫做 5-pin DIN6-pin Mini-DIN
在这里插入图片描述

在更早时候,IBM还推出过 XT 键盘,它也使用 5-pin DIN ,不过现在早就不再使用了。如今的主流键盘主要是 AT、PS/2、USB 三种。在本书中,我们不讨论 USB 键盘,只介绍 ATPS/2 两种。实际上,PS/2 键盘只是在 AT 键盘的基础上做了一点点的扩展而已,在大多数情况下,尤其是在最初接触它们时,你可以认为它们是一样的

从设备管理器中可以发现,我的电脑使用的是 PS/2 键盘,对应的端口是 60, 64 ,使用的是8255A的IRQ1:
在这里插入图片描述

7.1.3 键盘敲击的过程

在键盘中存在一枚叫做键盘编码器 Keyboard Encoder 的芯片,它通常是 Intel 8048 以及兼容芯片,作用是监视键盘的输入,并把适当的数据传送给计算机。另外,在计算机主板上还有一个键盘控制器 Keyboard Controller ,用来接收和解码来自键盘的数据,并与8259A以及软件等进行通信(如图7.3所示)。
在这里插入图片描述

敲击键盘有两个方面的含义:动作和内容。动作可以分解成三类:按下、保持按住的状态以及放开;内容则是键盘上不同的键,字母键还是数字键,回车键还是箭头键。所以,根据敲击动作产生的编码,8048既要反映哪个按键产生了动作,还要反映产生了什么动作

敲击键盘所产生的编码被称作扫描码 Scan Code ,它分为 Make CodeBreak Code 两类。当一个键被按下或者保持住按下时,将会产生Make Code;当键弹起时,产生Break Code。除了Pause键之外,每一个按键都对应一个 Make Code 和一个 Break Code

扫描码总共有三套,叫做 Scan code set 1、Scan code set 2、Scan code set 3Scan code set 1 是早期的 XT 键盘使用的,现在的键盘默认都支持Scan code set 2,而 Scan code set 3 很少使用。

整个键盘输入的过程如下所示:

  • 当8048检测到一个键的动作后,会把相应的扫描码发送给8042
  • 8042会把它转换成相应的 Scancode set 1 扫描码,并将其放置在输入缓冲区中
  • 然后8042告诉8259A产生中断 IRQ1
  • 如果此时键盘又有新的键被按下,8042将不再接收,一直到缓冲区被清空,8042才会收到更多的扫描码。

现在,你一定明白了为什么图7.1中只打印了一个字符,因为我们的键盘中断处理例程什么都没做。只有我们把扫描码从缓冲区中读出来后,8042才能继续响应新的按键

那么如何从缓冲区中读取扫描码呢?这就需要我们来看一看8042了。8042包含表7.1所示的一些寄存器。
在这里插入图片描述

注意:由于8042处在8048和系统的中间,所以输入和输出是相对的。比如,8042从8048输入数据,然后输出到系统。因此,表7.1中的出和入是相对于系统而言的。

对于(写8042)输入和(读8042)输出缓冲区,可以通过 inout 指令来进行相应的读取操作。也就是说,一个 in al, 0x60 指令就可以读取扫描码了。

修改程序。在 chapter7/a/kernel/keyboard_handler 中添加下面一句:in_byte(0x60); 。运行,结果如图7.4所示:
在这里插入图片描述

按一下键,出现两个星号,再按一下,又出现两个。因为每次按键都产生一个 Make Code 和一个 Break Code

只打印星号显然不够有趣,我们把收到的扫描码打印一下看看,进一步修改keyboard_handler(代码7.4)。
在这里插入图片描述
接下来在运行的时候按两个键:字符 a9
在这里插入图片描述
总共出现4组代码:0x1E, 0x9E, 0xA, 0x8A 。对照表7.2发现,它们恰恰就是字符 a9MakeCodeBreak Code
在这里插入图片描述
在这里插入图片描述

注意:

  • 由于 aA 是同一个键,所以它们的扫描码是一样的,事实上它们就是同一个键)。
  • 如果按下 左Shift+a ,将得到这样的输出:0x2A0x1E0x9E0xAA ,分别是左Shift键的 Make CodeaMake CodeaBreak Code 以及左Shift键的 Break Code
  • 所以,按下 Shift+a 得到 A 是软件的功劳,键盘和8042是不管这些的,在你自己的操作系统中,甚至可以让 Shift+a 去对应 S 或者 T ,只要你习惯就行。
  • 同理,按下任何的键,不管是单键还是组合键,想让屏幕输出什么,或者产生什么反应,都是由软件来控制的。虽然增加了操作系统的复杂性,但这种机制无疑是相当灵活的。

同时也看到,虽然键盘支持的是 Scan code set2 ,最终又转化成了 Scan code set1 。这是基于为 XT 键盘写的程序兼容性而考虑的。又是兼容性问题,你脑海里会飞速闪过A20、内存空洞、被分割得乱七八糟的描述符等一系列令人厌恶的回忆。是啊,有时候,软硬件的设计者对兼容性的考虑的确占去了太多的精力,可是又有什么办法呢?

不过,我们马上就可以根据扫描码来处理键盘输入了,只要根据表格建立一种对应关系就够了。因此,忘掉这些不快吧。


7.1.4 用数组表示扫描码

现在扫描码已被轻松获得,可是该如何将扫描码和相应字符对应起来呢?从表7.2中可以看出,Break CodeMake Code | 0x80 进行按位或操作的结果。可是 Make Code 和相应键的对应关系好像找不到什么规律。

不过还好,扫描码是一些数字,我们可以建立一个数组,以扫描码为下标,对应的元素就是相应的字符。要注意的是,其中以 0xE0 以及 0xE1 开头的扫描码要区别对待。

我们把这个数组写成如下这个样子。其中每3个值一组( MAP_COLS 被定义成3),分别是单独按某键、Shift+某键和有0xE0前缀的扫描码对应的字符。Esc, Enter 等被定义成了宏,宏的具体数值无所谓,只要不会造成冲突和混淆,让操作系统认识就可以。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
举个例子:

  • 如果获得的扫描码是 0x1F,我们应该在代码7.5中很容易看到它对应的是字母s 。在写程序的时候,应该用 keymap[0x1F*MAP_COLS] 来表示 0x1F 对应的字符。
  • 如果获得的扫描码是 0x2A0x1E ,它是左Shift键的 Make Code 和字符 aMake Code 连在一起,说明按下Shift还没有弹起的时候a又被按下,因此应该用 keymap[0x1E*MAP_COLS+1] 表示行为的结果,即大写字母 A
  • 存在 0xE0 的情况也是类似的,如果我们收到的扫描码是 0xE00x47 ,就应该去找 keymap[0x47*MAP_COLS+2] ,它对应的是 Home

问题是,8042的输入缓冲区大小只有一个字节,所以当一个扫描码有不止一个字符时,实际上会产生不止一次中断。也就是说,如果我们按一下 Shift+A ,产生的 0x2A0x1E0x9E0xAA4 次中断接收来的。这就给我们的程序实现带来了困难,因为第一次中断时收到的 0x2A 无法让我们知道用户最终会完成什么,说不定是按下Shift又释放,也可能是 Shift+Z 而不是 Shift+A

于是,当接收到类似 0x2A 这样的值的时候,需要先把它保存起来,随后慢慢解析用户到底做了什么。

保存一个字符可以用全局变量来完成。可是,由于扫描码的值和长度都不一样,这项工作做起来可能并不简单。而且可以想像,键盘操作必将是频繁而且复杂的,如果把得到扫描码之后相应的后续操作都放在键盘中断处理中,最后keyboard_handler会变得很大,这不是一个好主意。

在这里,向前辈学习,建立一个缓冲区,让 keyboard_handler 将每次收到的扫描码放入这个缓冲区,然后建立一个新的任务专门用来解析它们并做相应处理

7.1.5 键盘输入缓冲区

我们先来建立一个缓冲区(这个缓冲区的结构是借鉴来的),用以放置中断例程收到的扫描码(代码7.6)。
在这里插入图片描述

这个缓冲区的用法如下图所示,白色框表示空闲字节,灰色框表示已用字节。在执行写操作的时候要注意,如果已经到达缓冲区末尾,则应将指针移到开头。
在这里插入图片描述
对照上图,我们可以容易地对缓冲区进行添加操作
在这里插入图片描述
代码很简单,但要注意,如果缓冲区已满,这里使用的策略是直接就把收到的字节丢弃。其中的 kb_in ,由于我们只在 keyboard.c 中使用,于是把它声明成一个 PRIVATE 变量( PRIVATE 定义位于 const.h 中,被定义成了 static )。注意,kb_in 的成员需要初始化,初始化的代码放在 init_keyboard( ) 中。
在这里插入图片描述

为了保持 kernel_main 的整洁,我们把时钟中断的设定和开启也放到单独的函数 init_clock 中。
在这里插入图片描述

这样,在 kernel_main 中调用这两个函数就可以了。

7.1.6 用新加的任务处理键盘操作

添加一个任务很简单,在6.4.6节中已经做过总结。下面要添加的这个任务将来不仅会处理键盘操作,还将处理诸如屏幕输出等内容,这些操作共同组成同一个任务:终端任务。现在,可以认为它只处理键盘输入。

为了简化程序,在这个任务中,我们不停地调用 keyboard.c 中的函数 keyboard_read
在这里插入图片描述
暂时先把所有对扫描码的处理都写进 keyboard.c 中。代码7.10中被 tty 使用的 keyboard_read 可以如代码7.11这样来定义。函数首先判断 kb_in.count 是否为 0 ,如不为 0 ,表明缓冲区中有扫描码,就开始读。读缓冲区开始时关闭了中断,到结束时才打开,因为 kb_in 作为一个整体,对其中的成员的操作应该是不受打扰的。读操作相当于写操作的反过程。
在这里插入图片描述

其中,disable_int( )enable_int( ) 的定义很简单(代码7.12)。
在这里插入图片描述

这样简单的两个汇编函数,完全可以用C语句中嵌入汇编的方式来做,而且,由于避免了调用函数的 call 指令和返回时的 ret 指令,因此更加节省时间。

这样我们就完成了通过任务来处理扫描码(其实还仅仅是打印数字)的代码,修改Makefile之后就可以 make 并运行了,运行结果与过去一样,因为我们没有对扫描码进行解析(扫描码是根据键盘布局来确定的):
在这里插入图片描述


7.1.7 解析扫描码

对扫描码的解析工作有一点烦琐,所以还是分步骤来完成它。

1. 让字符显示出来

虽然已经有了一个数组 keymap[ ] ,但是不要低估了解析扫描码的复杂性,因为它不但分为 Make CodeBreak Code ,而且有长有短,功能也很多样,比如 Home 键对应的是一种功能而不是一个ASCII码,所以要区别对待。先挑能打印的打印一下,看代码7.13。
在这里插入图片描述
在代码7.11中,总体思想是:

  • 0xE00xE1 单独处理且暂时不加理会。因为从表7.2中知道,除去以这两个数字开头的扫描码,其余的都是单字节的。
  • 如果遇到不是以 0xE0, 0xE1 开头的,则判断是 Make Code 还是 Break Code ,如果是后者同样不加理会,如果是前者就打印出来。
  • 前文中讲过,Break CodeMakeCode | 0x80 进行按位或操作的结果,代码中的 FLAG_BREAK 被定义成了 0x80
  • keymap[ ] 中取出字符的时候进行了一个与操作(scan_code&0x7F 。一方面,如果当前扫描码是 Break Code ,与操作之后就变成 Make Code 了;另一方面,这样做也是为了避免越界的发生,因为数组 keymap[ ] 的大小是 0x80

接下来就可以 make 并运行了,下图就是初步运行的结果。
在这里插入图片描述

敲入了 abc123456789 共计6个字母,它们被显示在了屏幕上。

2. 处理Shift、Alt、Ctrl

现在可以输入简单的字符和数字,但还有更复杂的输入,比如按个Shift组合。

下面就来添加代码,使其能够响应这些功能键。在代码7.14中,我们不但添加了处理 Shift 的代码,而且也对 AltCtrl 键的状态进行了判断,只是暂时对它们还没有做任何的处理。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

Shift, Alt, Ctrl 键左右各3个,最好不要把左右两个键不加区分,因为有一些软件需要区分对待,最简单而且经典的一个例子是超级玛丽,其中左右Shift功能是不一样的。为了不把左右键混为一谈,我们声明 6 个变量来记录它们的状态。

  • 当其中的某一个键被按下时,相应的变量值变为 true 。比如,当我们按下左 Shift 键,shift_l 就变为 true
  • 如果它立即被释放,则 shift_l 又变回 false
  • 如果当左 Shift 键被按下且未被释放时,又按下 a 键,则 if (shift_l || shift_r) 成立,于是 column 值为 1keymap[column] 的取值就是 keymap[ ] 中第二列中相应的值,即大写字母 A

同时,在这段代码中对以 0xE0 开头的扫描码也做了处理。其实它与按下 Shift 键类似,甚至还要更简单。

  • 当检测到一个扫描码的第一个字节是 0x E0 时,将 code_with_E0 赋值为 true整个函数就返回了
  • 下一个字节马上进入处理过程,由于 code_with_E0 = true ,所以 column 值变成 2 ,于是 key 就变成 keymap[ ] 中第二列的值了。

在整个过程中,虽然开始变量 key 的值是从 keymap[ ] 中得到的,但从整个函数执行的角度来看,遵循这样的原则:

  • 如果一个完整的操作还未结束,比如一个2字节的扫描码还未完全读入,则 key 赋值为 0 ,等到下一次 keyboard_read( )被执行时再继续处理。
  • 目前的情况是,一个完整的操作需要在 keyboard_read( ) 多次调用时完成。

现在运行一下,结果如图所示:
在这里插入图片描述
如今我们的OS已经可以识别大小写字母,以及 !, @ 等字符了。

3. 处理所有按键

现在我们已经可以处理大部分按键了,但是至少存在两个问题:

  1. 如果扫描码更加复杂,比如超过 3 个字符,程序还不足以很好地处理。
  2. 如果按下诸如 F1, F2 这样的功能键,系统会试图把它当做可打印字符来处理,从而打印出一个奇怪的符号。

先来解决第一个问题。目前,一个完整的操作需要在 keyboard_read( ) 多次调用时完成。这不但增加了许多全局变量,如 code_with_E0 ,而且让 keyboard_read( ) 理解起来也更困难。

符合逻辑的方法是,按下一个键会产生一到几字节的扫描码,最好能够在一个过程中把它们全都读出来。这其实并不困难,只需将从 kb_in 中读取字符的代码单独拿出来作为一个函数,用到时调用它就可以了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这段代码除了把函数 get_byte_from_kbuf( ) 单独拿出来之外,只是单独处理了 Pause, PrintScreen 两个按键。不过这样一来,每一次调用 keyboard_read( ) 都可以处理一个相对完整的过程。比如按下右侧的 Alt 键产生的 0xE0, 0x38 ,不必调用两次 keyboard_read( ) ,而且所有的按键都已经在处理范围之内了。不过,组合键的情况还是要多次调用 keyboard_read( )

下面来解决第二个问题——关于非打印字符的处理。keyboard_read( ) 这个函数只是负责读取扫描码就可以了,如何处理不该是它的职责,因为只有更高层次的软件才能根据具体情况做出不同的反应。我们再将代码进行如下修改:
在这里插入图片描述
在这里插入图片描述

这样,无论是 Pause, PrintScreen ,还是其他以 0xE0 开头的扫描码或普通的单字符扫描码,都会交给函数 in_process( ) 来处理。而且,Shift, Alt, Ctrl 键的状态会用设置相应位的方式通过 Key 表现出来。我们马上写一个简单的 in_process( )(代码7.17)。
在这里插入图片描述

注意,这里有一个小技巧。如果打开 keyboard.h ,可以看到如下所示的情形:
在这里插入图片描述

在所有的不可打印字符的定义中,都加了一个 FLAG_EXT ,这就使得在程序中可以非常容易地识别出来。当 (!(key & FLAG_EXT)) 真时,就表明当前字符是一个可打印字符。执行后的效果如图所示:
在这里插入图片描述
我们成功地输出了 26 个字母,包括大写和小写,同时输出了数字,以及其他一些字符。当你按下 F1, F2 等功能键时,程序并不做出反应。这些都表明修改是成功的。

不管是单键还是组合键,都使用一个 32 位整型数 key 来表示。因为可打印字符的ASCII码是 8 位,而我们将特殊的按键定义成了 FLAG_EXT 和一个单字节数的和,也不超过 9 位,这样还剩很多位来表示 Shift, Alt, Ctrl 等键的状态,一个整型记载的信息就足够表示当前的按键情况


7.2 显示器

随着键盘模块的逐渐完善,我们越来越需要考虑它与屏幕输出之间的关系终端进程不仅处理键盘操作,还将处理诸如屏幕输出等内容。所以,在彻底完成键盘驱动之前,我们必须了解终端的概念以及显示器的驱动方式

另外,书中假定系统用的是 VGA 以上的视频子系统,并假定不使用单色模式。

7.2.1 初始TTY

对于终端最简单而形象的认识是,当按 Alt+F1, Alt+F2, Alt+F3 等组合键时,会切换到不同的屏幕。这些不同的屏幕中可以分别有不同的输入和输出,相互之间彼此独立。 在某个终端中,如果键入命令 tty ,执行的结果将是当前的终端号。

终端当然不仅是 Alt+Fn 这么简单,但在目前的操作系统中,我们暂时只实现这样简单的终端。对于不同的 TTY ,可以理解成下图的样式。
在这里插入图片描述
不同的 TTY 对应的输入设备虽是同一个键盘,但输出却像是在不同的显示器上,因为不同的 TTY 对应的屏幕画面可能是不同的。实际上,我们仍在使用同一个显示器,画面的不同只是因为显示了显存的不同位置

3 个CONSOLE公用同一块显存,就必须有一种方式,在切换CONSOLE的瞬间,让屏幕显示显存中某个位置的内容。通过简单的端口操作相应的寄存器就可以做到这一点。


7.2.2 基本概念

"显示器"并不是一个精确的称呼,因为我们操作的对象可能是显卡,或者仅仅是显存。下面暂时使用"视频"这个词。

从一开始写简单的 Boot Sector 的时候,我们就没有离开过对视频的操作——如果不是通过屏幕的反馈,我们怎么知道计算机在做些什么呢?

  • 最初的 Boot Sector 中,打印字符是通过BIOS中断来实现的;
  • 到了保护模式中,BIOS中断不能用,我们就在 GDT 中建立了一个段,它的开始地址是 0xB8000 ,通过段寄存器 gs 对它进行写操作,从而实现数据的显示

目前,我们对于视频模块的操作也仅限于此,显示什么就 mov 而已。但是视频是一个很复杂的部分——显示适配器可以被设置成不同的模式,用来显示更多的色彩、更华丽的图像和动画。 这实现起来绝非易事。

不过我们没必要搞那么复杂,只要认识开机看到的默认模式就够了—— 80×25 文本模式, 这种模式下:

  • 显存大小为 32KB ,占用的范围为 0xB8000~0xBFFFF
  • 2 字节代表一个字符,其中低字节表示字符的ASCII码,高字节表示字符的属性——包括颜色,我们设置过字符的颜色,还写了一个函数 disp_color_str() 显示不同颜色的字符。在默认情况下,屏幕上每一个字符对应的2字节的定义如图所示:
    在这里插入图片描述
  • 一个屏幕总共可以显示 25 行,每行 80 个字符。

总的来说,屏幕字符对应的2个字节中:低字节表示的是字符本身,高字节用来定义字符的颜色

  • 颜色分前景和背景两部分,各占4位,其中低三位意义是相同的,表示颜色。
  • 如果前景最高位1 的话,字符的颜色会比此位为 0 时亮一些;如果背景最高位1 ,则显示出的字符将是闪烁的(是字符闪烁而不是背景闪烁)。
  • 更多细节请参考下表:

现在看第 3 章中代码 3.1 的这几行,就全明白了:

mov ah, 0Ch ; 0000: 黑底; 1100: 高亮,红字
mov al, 'P'
mov [gs:edi], ax

想实际看一下各种颜色的效果,可以通过调用 disp_color_str( ) 并改变其参数去试一下。

同时,我们已经知道一个屏幕可以显示几行几列,又知道了一个字符占用几个字节,易得一个屏幕映射到显存中所占的空间大小:80×25×2=4000 Bytes

而显存有 32KB ,每个屏幕才占 4KB ,所以显存中足以存放 8 个屏幕的数据。如果我们有 3TTY ,可以各占 10KB 的空间还有剩余,甚至在每一个 TTY 内还可以实现简单的滚屏功能

如何能够让系统显示指定位置的内容呢?其实很简单,通过端口操作设置相应的寄存器就可以了。


7.2.3 寄存器

从8259A到8042,再到现在的显示器,都是这样的流程,对端口操作而已。不过每种硬件各不相同,我们要了解其具体细节。

VGA 系统有 6 组寄存器,比较多,而且有些寄存器读和写端口还不一致。如表7.4所示:
在这里插入图片描述
使用英语就可以通过 Register 这个单词是否复数来判断寄存器是否只有一个。如 CRT Controller Registers 这一组,其中的 Data Registers 是复数,说明数据寄存器不止一个,如表7.5:
在这里插入图片描述
这样多的数据寄存器,只有一个端口 0x3D5 ,操作其中某个时要用到 Address Register 。表7.5中每一个寄存器都对应一个索引值,当想要访问其中一个时,只需要先向 Address Register 写对应的索引值(通过端口 0x3D4 ),再通过端口 0x3D5 进行的操作就是针对索引值对应的寄存器了。Data Registers 相当于一个数组,而 Address Register 则相当于数组的下标。

举例,假如想把索引号为 idx 的寄存器的值改为 new_value ,可以用两次端口操作来做:

out_byte(0x3D4, idx); 
out_byte(0x3D5, new_value);

我们试一下。从字面意思可知,Cursor Location High RegisterCursor Location LowRegister 用来设置光标位置,索引号分别是 0Eh, 0Fh

下面的程序中,我们不再让光标停在从Loader中调用BIOS中断显示完第5行的 Ready 之后。而是让它跟随我们敲入的每一个字符(代码7.19)。
在这里插入图片描述
其中,寄存器的端口地址和索引值的定义如代码7.20所示:
在这里插入图片描述
之所以 disp_pos2 除,是因为屏幕上每个字符对应 2 字节。make 运行,效果如图所示:
在这里插入图片描述
可以看到,光标开始跟随字符了。进一步做试验,通过设置 Start Address High RegisterStart Address LowRegister重新设置显示开始地址,从而实现滚屏的功能:
在这里插入图片描述

代码的意义是,检查当前按键是否是 Shift+↑ ,如果是,则卷动屏幕至 80×15 处,即向上卷动 15 行。运行,按Shift+↑,呈现出如下样子:
在这里插入图片描述
Start Address HighRegister, Start Address Low Register 两个寄存器可以用来设置从显存的某个位置开始显示。这个特性允许我们把显存划分成不同的部分,然后只需要简单的寄存器设置就可以显示相应位置的内容。

我们通过改变 VGA 寄存器的值实现了光标的移动和屏幕滚动。不过寄存器太多了!当然,我们暂时用到的没有多少。了解了它们的访问方式,需要某个功能时,查一下手册就可以了。


7.3 TTY任务

了解了键盘和显示器的操作,我们就可以实现多个TTY了,让TTY任务这样运行:

  • 在TTY任务中执行循环,每次都会轮询每个TTY,处理它的事件——包括从键盘缓冲区读取数据、显示字符等;
  • 不是轮询到某个TTY时,箭头对应的全部事件都会发生。只有当某个TTY对应的控制台是当前控制台时,它才可以读取键盘缓冲区
  • TTY可以对输入数据做更多的处理,这里简化为"显示";
  • 键盘和显示器是每个TTY的一部分,是公共的。
    在这里插入图片描述

总的来说,每次轮询到某个TTY时要做的:

  • 处理输入——如果它是当前TTY,就从键盘缓冲区读取数据;
  • 处理输出——如果有要显示的内容,则显示它。

将上面的TTY任务图转换为下面的函数调用图:task_tty() 是一个循环,它不断调用 keyboard_read() ,而 key_board() 从键盘缓冲区得到数据后,会调用 in_process() ,将字符直接显示出来。
在这里插入图片描述
话虽如此,实现起来就不是说的那么简单了:

  • 每一个TTY都应该有自己的读和写的动作。所以在 keyboard_read( ) 内部,函数需要了解自己是被哪一个TTY调用。可以通过为函数传入指向当前TTY的指针来做到这一点。
  • 为了让输入和输出分离,被 keyboard_read( ) 调用的 in_process( ) 不应该再直接回显字符,应该将回显的任务交给TTY来完成,因此,我们就需要为每个TTY建立一块缓冲区,用以放置将被回显的字符
  • 每个TTY回显字符时操作的CONSOLE是不同的,所以它们都应该有一个成员来记载其对应的CONSOLE信息

7.3.1 TTY任务框架的搭建

TTY和Console的结体如下:
在这里插入图片描述

整个程序流程如下:

  • task_tty( ) 中,通过循环来处理每一个TTY的读和写操作
  • 读写操作全都放在 tty_do_read( ), tty_do_write( ) 两个函数中,这样就让 task_tty( ) 很简洁,而且逻辑清晰。
  • 读操作会调用 keyboard_read( ) ,此时已经多了一个参数;
  • 写操作会调用 out_char( ) ,它会将字符写入指定的CONSOLE。
    在这里插入图片描述

32KB的显存可以允许同时存在3个控制台,这里声明3个TTY和对应的3个CONSOLE:
在这里插入图片描述
task_tty( ) 框架实现如下,其中 nr_current_console 是一个定义在 const.h 的全局变量 EXTERN int nr_current_console; ,它可以用来记录当前的控制台是哪一个。只有当某个TTY对应的控制台是当前控制台时,它才可以读取键盘缓冲区
在这里插入图片描述
在这个主循环中,先做了初始化工作。为其中的缓冲区设置初值和指定对应的CONSOLE:
在这里插入图片描述
tty_do_read( ) 要判断 nr_current_console 的值,对控制台进行切换时也要改变 nr_current_console
在这里插入图片描述
tty_do_read( ) 如下:
在这里插入图片描述
不过,key_board( ) 改变了,要对其函数体进行修改。同时 in_process( ) 也要增加参数 p_tty往TTY缓冲区写入数据的代码很简单,只把输出字符写入缓冲区,读操作就结束了;如果遇到 Alt+Fn 切换控制台的操作,也要在 in_process 中处理掉:
在这里插入图片描述
看一下写操作,它从TTY缓冲区中中取出值,类似 get_byte_from_kbuf( ) ,然后用 out_char( ) 显示在CONSOLE中:
在这里插入图片描述
out_char( ) 实现如下:V_MEM_BASE = 0xB8000 定义在 const.h 中, V_MEM_BASE + disp_pos 等同于当前显示位置的地址,我们直接把字符写入特定地址:
在这里插入图片描述
我们所做的工作如下:

  • 当TTY任务开始运行时,初始化所有TTY,全局变量 nr_current_console = 0 ,开始循环并一直进行;
  • 对于每个TTY,首先执行 tty_do_read( ) ,它调用 key_board( ) 并把读入的字符交给 in_process( ) 处理。如果是要输出的字符,in_process( ) 把它放入当前TTY的缓冲区中;
  • 然后执行 tty_do_write( ) ,如果缓冲区中有数据,就送入 out_char( ) 进行显示。
  • 由于 nr_current_console 初始化后没有变过,所以只有初始的TTY在接受处理,其他TTY没有动作。

运行结果如下:
在这里插入图片描述


7.3.2 多控制台

这里来实现多个CONSOLE。前面,我们还根本没有用到CONSOLE的结构体成员。下图是某时刻显存的使用状态:

  • original_addr, v_mem_limit 定义控制台所占显存的总体情况,一经初始就不再改变;
  • current_start_addr 随着屏幕卷动而变化;
  • cursor 每输出一个字符就更新一次;
    在这里插入图片描述

为上述成员设置初始值:

  • CONSOEL结构体中的成员都是 int/WORD 类型,符合对VGA寄存器操作的习惯;
  • init_screen( )init_tty( ) 中调用,同时修改原来的 init_tty( )
  • 第一个CONSOLE使用原来的光标位置;其他控制台光标都在左上角,并且显示控制台号和一个字符 #
    在这里插入图片描述

修改后的 init_tty( ) 如下:
在这里插入图片描述

out_char( ) 考虑到多控制台的情况,改变如下:
在这里插入图片描述
还需要一个切换控制台的函数:
在这里插入图片描述

set_video_start_addr( ) 如下:
在这里插入图片描述

另外,惯例是按下 Alt+Fn 完成切换工作:
在这里插入图片描述
有了 select_console( ) ,我们可以在 task_tty( ) 中这样初始化 nr_current_console
在这里插入图片描述
运行时发现通过 ALT+F1/F2 无法切换,可能是Windows快捷键占用或者是Ubuntu的快捷键设置——Alt + F1 聚焦到桌面左侧任务导航栏,Alt + F2 运行命令导致的。

在这里插入图片描述
上面是通过 Alt + F3 切换到的控制台2,然后输入字符串。如果切换回控制台0,会发现屏幕快满了。为此,添加屏幕滚动的代码。

这里简化了一下,当屏幕滚动到最下端后,再试图向下滚动时按键不再响应,最上端时也一样。
在这里插入图片描述
响应 Shift+↑, Shift+↓ 的代码如下:
在这里插入图片描述
再次运行,在控制台0按 Shift+↑ 多次,如下图:
在这里插入图片描述


7.3.3 完善键盘处理

现在,我们对键盘的支持还是很差的,还不能使用 CapsLock, BackSpace 等等。如今任务框架搭好了,我们需要添加处理其他按键的代码。

1. 回车键和退格键

敲入回车和退格时,要往TTY缓冲区写入 "\n""\b" ,然后在 out_char( ) 做相应处理:
在这里插入图片描述
修改 out_char( )

  • 回车键将光标移动到下一行的开头;如果光标已经移出了屏幕,就会触发屏幕滚动;
  • 退格键将光标移动到上一个字符的位置,并写一个空格清除原来的字符;
  • 输出任何字符,都会做边界检验,防止写到别的控制台或者写到显存之外;
    在这里插入图片描述
    运行效果如下,在控制台0中输出如下按键序列:
    在这里插入图片描述
    然后,通过 ALT+F3 来到控制台2(这其实是第三个控制台 ^-^ ),输入如下序列:在这里插入图片描述

这里我确认,之所以 ALT+F1/F2 无法切换是Ubuntu快捷键设置的问题。于是我想到禁用 ALT+F4 ,然后用 ALT+F3/F4/F5 替代 ALT+F1/F2/F3
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


2. CapsLock, NumLock, ScrollLock

我的键盘没有 ScrollLockNumLock 也被我用来作为一个截图软件的快捷键了。只有 CapsLock ,还像书中说的那样对应一个小灯 (LED) 。我们可以通过敲击键盘或者写入8042的输入缓冲区控制灯的亮灭。

8042的输入缓冲区和控制寄存器都是可写的,不过写入输入缓冲区是向8048发送命令,写入控制寄存器是向8042本身发送命令
在这里插入图片描述
先看看对应的端口 0x60 ,写入这个端口将向8048发送命令,设置LED的命令是 0xED 。当键盘接受到命令时,会回复一个ACK 0xFA 信号,然后等待从端口 0x60 写入的LED参数字节:
在这里插入图片描述

当键盘收到参数字节后,再次回复一个ACK,并设置LED。

注意:向8042输入缓冲区写数据时,要先判断输入缓冲区是否为空——通过端口 0x64 读取状态寄存器,第2位如果是 0 ,表示输入缓冲区是空的,可以写入数据。代码如下:
在这里插入图片描述
其中,LED_CODE, KB_ACK, caps_lock=0, num_lock=1, scroll_lock=0 分别定义如下:
在这里插入图片描述在这里插入图片描述
运行程序,系统启动之后NumLock被点亮。我的键盘这个键不会亮;num_lock=1 时可以使用数字功能,为了让功能起效,需要修改 keyboard_read( ) ——增加了一个 pad 变量;在 key 中设置了一个相应的位以区分普通数字键和小键盘数字键:
在这里插入图片描述
在这里插入图片描述在这里插入图片描述在这里插入图片描述

运行如下,出现问题,可能是因为我的键盘没有 NumLock 的LED灯和 ScrollLock 这个键:
在这里插入图片描述
这里就不多纠结了。


7.3.4 TTY任务总结

到此,书中的TTY任务就暂时结束了。

不过问题是,终端任务运行在 ring1 ,但它与运行在 ring0keyboard_handler 容易混淆。终端任务可以访问所有内存。kb_inring0 下写,在 ring1 下读。另外,Minix也是这样做的。


7.4 区分任务和用户进程

现在,我们有了4个进程——TTY, A, B, C ,后三者可有可无,它们是"用户进程",而TTY是"任务"。具体实现上,让用户进程运行在 ring3 ,任务运行在 ring1ring0 运行的是进程调度:
在这里插入图片描述

现在,增加对 NR_PROCS 的定义,同时修改 NR_TASKS = 1
在这里插入图片描述
然后,在所有用到 NR_TASKS 的地方进行修改。新声明一个数组 user_proc_table[ ]
在这里插入图片描述
然后,修改初始化进程表的代码。这里改变了用户进程的特权级,而且通过 eflags 的改变,取消了用户进程所有的IO权限:
在这里插入图片描述

另外,protect.c 中初始化GDT中LDT描述符的代码也进行了修改:
在这里插入图片描述
区分任务和用户进程后的 proc.c 进程调度代码:
在这里插入图片描述
make 运行如下,结果没有多少区别:
在这里插入图片描述

这次改动标志着Orange’s现在已经运行在了3个特权级上。普通的用户进程和系统任务从此区分开来


7.5 printf

现在,我们有了TTY,还有一个任务和三个用户进程,想看到它们在特定终端运行的情况。为此,我们需要有一个供输出使用的 printf( )

printf( ) 进行屏幕输出,需要用到控制台模块的代码,因此,它需要通过系统调用才能完成。

7.5.1 为进程指定TTY

当某个进程调用 printf( ) 时,操作系统必须知道往哪个控制台输出才行。而系统调用发生,ring3 跳入 ring0 时,系统只能够知道当前系统调用是由哪个进程触发的,所以我们必须为每个进程指定一个与之对应的TTY。通过在进程表中增加一个成员即可:

在这里插入图片描述
用初始化 PROCESStick, priority 成员的方法,为 nr_tty 设置初值——在 for 循环中,所有进程的 nr_tty = 0 ,即默认与第0个TTY绑定。不过后面,将 B, C 两个进程与第1个TTY绑定,它们的输出将同时出现在控制台上1上;而 A 的输出出现在控制台 0
在这里插入图片描述


7.5.2 printf()的实现

从学习C的第一天开始,我们就开始用上 printf( ) 了,但是它的实现并不简单——它的参数个数和类型都可变;而且还有多种多样的表示格式的参数,如 %c, %s 等,这些都需要加以识别。

整个 printf( ) 的调用过程如下:
在这里插入图片描述

开始的 printf( ) 实现一个简单的,它只支持 %x 输出十六进制一种格式:
在这里插入图片描述

vsprintf( ) 的实现如下:
在这里插入图片描述

可变参数的原理:

调用一个函数时,总是先把参数压栈,然后通过call指令转移到被调用者,在完成后清理堆栈。 但这里遇到两个问题:

  • 如果有多个参数,哪个参数先入栈,是前面的还是后面的?
  • 由谁来清理堆栈,调用者还是被调用者?


这两个方面的问题其实被称为“调用约定”(Calling Conventions)……调用约定有若干种,每一种都规定参数入栈的顺序以及谁来清理堆栈。我们已经用汇编语言写过不少的函数,都是后面的参数先入栈(前面的参数就位于栈顶,更容易取出),并且由调用者清理堆栈。这种约定被称做C调用约定。

C调用约定的好处在处理可变参数函数时得到了充分体现,因为只有调用者知道此次调用包含几个参数,于是可以方便地清理堆栈C调用约定让使用可变参数的函数成为可能

可具体怎么做呢?

  • 首先是它的声明,过去我们写的函数,都有确定类型的参数,可现在不同了,参数的个数和类型都不知道,于是,省略号就派上了用场,正如代码7.55所示,一个省略号,表示参数不知道有多少,更不知道是什么。
  • …在每一次调用过程中,printf 必须有一种方法来使用这些参数才行。从代码7.55可以看到,printf 使用了它的第一个参数 fmt 作为基准,得到了后面若干参数的开始地址,这样,其值也就容易得到了。


举一个例子,假设我们调用 printf(fmt, var1, var2, var3) ,则堆栈情况将如图7.25所示:
在这里插入图片描述
&fmt 表示 fmt 地址,(char*)(&fmt) + 4 表示跟随在 fmt 后面的参数的地址。所以,接下来实际上是将 var3 的地址传递给了紧接着调用的 vsprintfva_list 其实就是 char* ,它的定义在 type.h 中。

函数 vsprintf 的实现见代码7.56,虽然它只识别 %x 这一种格式,但其他格式的原理也是一样的,即根据%后的格式字符就能判断下一个参数的类型,从而知道从堆栈中取出什么。


7.5.3 系统调用write()

接下来完成 write( ) 系统调用,它把 vsprintf 输出的字符串打印到屏幕上。

增加一个系统调用 (foo) 的过程如下:
在这里插入图片描述

系统调用是 write( ) ,对应的内核部分是 sys_write( ) ,声明在 proto.h 中,对应第4步和第5步:
在这里插入图片描述
接下来是 write( )sys_write( ) 两个函数体:由于已有的系统调用没有参数,所以还需要修改 sys_call( )
在这里插入图片描述
sys_write( ) 通过简单函数 tty_write( ) 实现字符输出,这里 sys_write( )write( ) 多一个参数,需要在修改的 sys_call( ) 中压栈,然后 sys_call 调用 sys_write
在这里插入图片描述
修改的 sys_call 如下。当前运行的进程是通过设置 p_proc_ready 来恢复执行的,所以当进程切换到未发生之前, p_proc_ready 的值就是指向当前进程的指针。把它压栈就是把当前进程即 write( ) 的调用者指针传递给了 sys_write( )
在这里插入图片描述


7.5.4 使用printf()

在3个用户进程中调用它:
在这里插入图片描述
运行,成功:
① 控制台0的情况:printf 打印出字符和数字;
在这里插入图片描述
② 控制台1的情况:printf 交替打印出两个进程的字母;
在这里插入图片描述

我们有了自己的 printf它运行在用户态,可以被普通的用户进程调用。现在,我们还可以到控制台2的空白屏幕敲击键盘输入字符。这就是一个多任务多控制台的操作系统的特性。

Python学习笔记 - 探索内置字符串函数
Mr数据杨
07-08 215
通过本教程的学习,读者已经掌握了Python中常见的字符串操作方法,包括字符串索引、切片、步幅、变量插入和修改等。每一种操作都对应不同的应用场景,从解析和操作文本,到生成动态内容,再到处理用户输入,这些技巧在编程实践中至关重要。掌握这些字符串操作,不仅可以提高代码的灵活性和效率,还能够帮助开发者在面对复杂的文本处理任务时游刃有余。希望通过这份教程,读者能够更深入地理解和应用Python中的字符串操作,为今后的编程工作打下坚实的基础。
Orange‘s:一个操作系统的实现学习笔记2
超大青花鱼的博客
02-21 1955
第三章第一节代码运行实录前言代码+注释运行效果一些问题1. 段描述符的结构2. 段选择子的结构3. 地址转换4. GDTR的结构5. 打开A20地址线6. CR0寄存器的结构7. "$" 前言 去年就开始学习Orange’s:一个操作系统的实现,今年记录并总结一下学习中遇到的一些问题,由于我汇编基础几乎为0,对计算机也不是很了解,所以写的这些注释和总结难免会有错误和纰漏,敬请原谅。此篇笔记抄的书上的代码已经实际运行过了,有可能后期添加注释的时候出现bug,如果运行不起来,可以删除所有注释再运行。此篇笔记去年
Orange‘s 一个操作系统的实现》第七章
XiaoLing 一直摆烂哇!
09-11 1130
的文本模式,该模式显存大小为 32KB,占用范围 0xB8000 ~ 0xBFFFF,每 2 字节代表一个字符,其中低 8 位为 ASCII 码,高 8 位位字符属性。指的是 TTY 进程,不是说每个 TTY 的任务(要执行的工作),该函数的作用是轮询每个 TTY,对每个 TTY 都进程初始化,以及完成对键盘缓冲区的读取和字符显示,这些才是 TTY 的工作。在键盘中存在一枚叫做键盘编码器的芯片,它通常是 Intel 8048 以及兼容芯片,作用是监视键盘的输入,并把适当的数据传送给计算机。
操作系统Oranges学习笔记(一) 第一章、第二章
memcpy0的博客
07-03 1240
实验目标:搭建环境 & 调试第一个例子 环境搭建: 我们要在 windows 系统下安装 virtualbox 软件; 之后要用 virtualbox 创建一个虚拟机,并为这个虚拟机安装 ubuntu 系统; 接着启动刚装好的 ubuntu 虚拟机,在其中安装bochs(注意编译时的配置选项,书中有说明,不然将无法进行调试); 最终运行书中的第一个示例 软件& Linux Distribution 版本要求 virtualbox:5.0.4 ubuntu 镜像:ubuntu-12.04
操作系统Oranges学习笔记(四) 第五章 内核雏形
memcpy0的博客
07-24 2150
文章目录5.1 在Linux下用汇编写HelloWord5.2 再进一步,汇编和C同步使用5.3 ELF(Executable and Linkable Format)1. ELF Header2. Program header5.4 从Loader到内核 5.1 在Linux下用汇编写HelloWord Loader需要完成从实模式向保护模式的跳转,还是应该把GDT、LDT、8259A等内容都准备完毕? 在Linux下写汇编:(chapter5/a/hello.asm) ; 编译链接方法 ; (ld的'
操作系统Oranges学习笔记(五) 第章 进程
memcpy0的博客
07-24 2569
文章目录6.1 迟到的进程6.2 进程概述6.2.1 进程介绍6.2.2 未雨绸缪——形成进程的必要考虑 进程是操作系统中最重要的概念之一。没有进程,就不能称作操作系统。 这一章的概括: 先实现一个简陋的进程,然后模仿它再写一个; 让两个进程同时运行,并用我们的系统试着调度; 最后,尝试扩展进程的功能。 6.1 迟到的进程 进程的切换、调度等内容和保护模式紧密相连。要深入了解操作系统和进程,至少要接触一种平台上的具体实现。只有拥有了基于具体平台的感性认识之后,才有可能对形而上的理论有更踏实的理解。
学习笔记:《python编程快速上手-让繁琐的工作自动化》(第章:字符串操作)
weixin_43113882的博客
06-24 290
6.1处理字符串 6.1.2双引号 字符串以双引号开始和结束,使用双引号的好处是字符串中可以使用单引号字符。 spam = ''that is Alice 's cat'' 6.1.3转义字符 详细查询:https://www.runoob.com/python3/python3-string.html 在引号前加上r使它成为原始字符串。“原始字符串”完全忽略所有的转义字符 spam = 'dan shen\nde\' kuai le\nni xian xiang bu dao' prin
Orange's动手写操作系统-----------第一篇:nasm,bochs环境配置(学习笔记1)
[每一个你讨厌的现在,都有一个不够努力的曾经_...._]
11-17 3933
找完工作,开始学习orange's自己动手写操作系统,希望能够坚持,现在的目标就是好好学习,加油~~~ 1:安装nasm汇编编译器:网站下载:http://sourceforge.net/projects/nasm,目前最新版本是,nasm-2.06.tar.gz     源码安装:     ~$./configure     ~$make     ~$sudo make install
Orange's动手写操作系统-----------第二篇:保护模式Protect Mode (学习笔记2)
[每一个你讨厌的现在,都有一个不够努力的曾经_...._]
11-21 1471
在进行保护模式的运行环境配置前,先看了了下书上的代码,编译运行结果如下:nasm 3_pmtest1.asm -o pmtest1.bin,可以看到界面出现了红色的 p 字                   保护模式环境配置 1:在网站http://bochs.sourceforge.net/guestos/freedos-img.tar.gz 上下载FreeDos,
orange's学习--第七章:键盘显示器处理程序总结
交换ab:a=a^b;b=a^b;a=a^b;
08-24 178
键盘显示器的处理过程分成了好几个模块,重要的2个缓冲区全局变量解耦了程序。 键盘扫描码缓冲区,让keyboard_handler将每次收到的扫描码放入这个缓冲区,然后建立一个新的任务专门用来解析它们并做相应处理。PUBLIC void keyboard_handler(int irq) 把键盘扫描码不加区分的存储到了键盘扫描码缓冲区,PUBLIC void keyboard_read(T...
最新版本OrangeEdit
07-28
kuka机器人编程环境
KUKA机器人用OrangeEdit偏移机器人位置
最新发布
03-02
该操作可以实现用OrangeEdit对程序点位实现偏移,且不影响BASE、TOOL的坐标值
Scan Codes (Set 2)
01-14
Keyboard Scan Codes: Set 2 101-, 102-, and 104-key keyboards: 符合windows规范的键盘扫描码:Volume Up;Volume Down...
Orange 编程指南.pdf
03-02
MELIS 用户编程手册 Orange 编程指南 @ All winner 平台,是基于 Melis 操作系统之上的一套 GUI 系统,该系统支持多任务和多图层操作, 允许在多个图层上面创建窗口,提供完善的异步和同步窗口消息通讯机制,提供点、线、矩 形、扇形、椭圆、圆等基本形状的绘制和填充,支持 TTF 矢量字体、SFT 点阵字体及字体 的加边框和加阴影特效,支持文本的显示输出和 BMP 位图的绘制,支持 button、static、 listmenu、slider…等十几种控件,提供 alphablending、colorkey 等特效,支持 alpha、copy、 fill 的 2D 加速,支持多国语言和内存设备等。在 Orange 中,窗口是 Orange 管理的基本单位, Orange 采用事件驱动编程,窗口是接收事件并分发处理事件的最小单元,在 Orange 中所有 的消息响应基于消息循环,窗口不断从消息队列中获取消息并分发给响应的窗口过程处理。
OrangeEdit-2.0.3
09-01
由世界各地的德国KUKA机器人发烧友制作的绿色编程软件,可支持以C语言为基础开发的多种语言编程,特别像KUKA工业机器人程序汇编,使用简便,步奏简明。且只需发送E-mail至官网info@OrangeEdit.de即可免费获得激活码,免注册、免使用会员积分等繁琐步骤。码农们的最爱。
操作系统Oranges学习笔记(二) 第三章4 保护模式下的中断和异常
memcpy0的博客
07-24 1473
文章目录3.4 保护模式下的中断和异常1. 中断和异常机制2. 外部中断3. 设置和编程操作8259A4. 建立IDT5. 实现一个中断6. 时钟中断试验7. 几点额外说明(1) 特权级变换(2) 中断或异常发生时的堆栈变化(3) 中断门和陷阱门的区别 3.4 保护模式下的中断和异常 以前,我们使用中断都是在实模式下进行的,如 int 15h 得到内存信息后在保护模式下把它们显示出来。原因在于,在保护模式下,中断机制发生了很大变化。 保护模式下,原来的中断向量表已经被IDT所代替,实模式下能用的BIOS中
ORANGE'S一个操作系统的实现》第7章 TTY与键盘输入的关系。
RT_776的博客
01-10 374
  背景:我感觉这块部分有一些逻辑上的复杂,于是我把它的关系结构画了一张图来表述,并且在图上解答了我自己想到的几个问题。   关系如图所示:    ...
import vue from vue_你知道import Vue from 'vue' 吗?
weixin_39965102的博客
11-29 3724
前言随着前端模块化的兴起,特别在 vue、react等一些热门框架的崛起,越来越多的公司加入了这一阵营。提及前端的模块化(这里主要说的是Javascript语言的模块化),就不得不说 es6标准提出的 export 和 import 两个命令了。当然还有 commonJS里的 module.exports 和 require 这一对组合了,这里暂不介绍这两组命令的具体用法。以后 我会专门写一篇文章...
第七章 输入输出系统之键盘
雪夜
01-06 793
自己动手写操作系统
写文章

热门文章

  • 【VS Code】settings.json的两种配置位置(全局用户设置和工作区设置) 58750
  • 【离散数学】图论 第七章(3) 图的矩阵表示(邻接矩阵、可达矩阵、传递闭包求解算法) 32283
  • 【软件工程】软件需求说明书、概要设计说明书、详细设计说明书参考模板 28084
  • 【VS Code】文件夹(Folder)和工作区(Workspace)的使用 23068
  • 【C/C++】printf格式化输出-科学计数法形式的浮点数 20590

分类专栏

  • 最小瓶颈路 3篇
  • SQL 8篇
  • 构造 1篇
  • 正则表达式 1篇
  • 数位DP 2篇
  • 机器学习 1篇
  • 数据结构和算法学习 89篇
  • CCF 4篇
  • 树链剖分 2篇
  • 软件工程
  • 离散数学 28篇
  • AcWing
  • 设计模式 5篇
  • 云计算
  • Codeforces
  • 蓄水池抽样 1篇
  • 编程语言和语言哲学 113篇
  • 基础课程和实践项目 115篇
  • 编程环境和项目工具 41篇
  • PAT 39篇
  • 最小生成树 6篇
  • 网络流之最大流 1篇
  • LeetCode 735篇
  • 线性代数 1篇
  • 动态规划 84篇
  • LCS 3篇
  • 01背包 6篇
  • 多重背包
  • 完全背包 4篇
  • 树形DP 3篇
  • 设计 21篇
  • Linux 3篇
  • 外语学习
  • 日语学习
  • 英语学习
  • 模拟 52篇
  • 哈希 52篇
  • 哈希集合 12篇
  • 哈希映射 50篇
  • 回溯 18篇
  • 分治 4篇
  • 树-二叉树 45篇
  • 树-线索二叉树 1篇
  • 树-完全二叉树 1篇
  • 树-KD树 1篇
  • LCA 1篇
  • 数组 115篇
  • 队列 7篇
  • 循环队列 2篇
  • 双向队列 2篇
  • 25篇
  • 链表 39篇
  • 双向链表 4篇
  • 字符串 111篇
  • 字符串哈希 2篇
  • 字典树 9篇
  • KMP 10篇
  • Manacher 2篇
  • 并查集 16篇
  • 种类并查集 5篇
  • 带权并查集 1篇
  • 树-二叉搜索树 22篇
  • 平衡树-AVL树 1篇
  • 平衡树-Treap 2篇
  • 优先队列-二叉堆 16篇
  • 优先队列-左式堆 1篇
  • 区间数据结构-ST表 2篇
  • 区间数据结构-莫队 4篇
  • 区间数据结构-树状数组 13篇
  • 区间数据结构-线段树 7篇
  • 单调栈/队列 14篇
  • 集合 9篇
  • 映射 5篇
  • 记忆化搜索 17篇
  • 搜索 5篇
  • 枚举 17篇
  • BFS/DFS 111篇
  • 排列组合 14篇
  • 二分与三分 52篇
  • 滑动窗口 17篇
  • 双指针 48篇
  • 双向广搜 1篇
  • 图论 20篇
  • 图的连通性 3篇
  • 最短路 8篇
  • 拓扑排序 9篇
  • 二分图 6篇
  • 关键路径 1篇
  • 贪心 67篇
  • 分块 2篇
  • 排序 51篇
  • 计数排序 3篇
  • 基数排序 1篇
  • 归并排序 2篇
  • 希尔排序 1篇
  • 快速排序 6篇
  • 脑筋急转弯 9篇
  • 数学 95篇
  • 数论 11篇
  • 素数筛 1篇
  • 组合数学 5篇
  • 快速幂与矩阵快速幂 7篇
  • 约瑟夫环问题 3篇
  • 博弈论 3篇
  • 高精度 3篇
  • 有理数 1篇
  • 01字典树 5篇
  • 模运算 5篇
  • STL 2篇
  • 位操作 51篇
  • 递归/递推 35篇
  • 算法技巧-离线 1篇
  • 算法技巧-哨兵 1篇
  • 算法技巧-倍增法 2篇
  • 算法技巧-随机化 5篇
  • 算法技巧-离散化 2篇
  • 算法技巧-前缀和 49篇
  • 算法技巧-差分 9篇
  • 算法技巧-二进制压缩 3篇
  • 计算几何 6篇
  • 算法技巧-打表 9篇
  • 刷题日志与精进之路 11篇
  • HDU 32篇
  • LintCode 3篇
  • POJ 21篇
  • 洛谷 41篇
  • Code War 2篇
  • CodeUp 2篇
  • 牛客网 2篇
  • Aizu Online Judge 1篇
  • =====小项目===== 11篇
  • Console知识 2篇
  • 信息管理 1篇
  • 图形游戏 9篇
  • 控制台游戏 3篇
  • 网络爬虫 6篇
  • 虚拟实习体验项目
  • 财务报表分析 1篇
  • 读书笔记 11篇
  • 计算机组成 2篇
  • 编译原理 2篇
  • 操作系统 14篇
  • 微机原理 8篇
  • 数字逻辑 11篇
  • 数据库系统 21篇
  • 汇编语言 5篇
  • 财务管理 1篇
  • 计算机网络 32篇
  • 计算机体系结构 5篇
  • 计算机考试 3篇

最新评论

  • 【VS Code】Windows10下VS Code配置Prolog语言环境

    波波疯狂敲代码: 怎么中文化呀表情包表情包

  • 【算法学习】图论专题 判断无向图中环的存在、环的输出

    weixin_43371500: 请问是用VS环境编译的吗?但是用VS2022编译会显示错误,也即bool visited[numVertexes]; ,E0028 表达式必须含有常量

  • 【离散数学】集合论 第四章 函数与集合(3) 鸽巢原理

    2301_80144607: 例四里面是:至少有5人出生在星期一或者至少有五人出生在星期二或者至少有5人出生在星期三……或至少有5人出生在星期天,直接说“总可以挑出5人出生在星期天”是不是有一点问题或者不严谨

  • 【离散数学】集合论 第四章 函数与集合(3) 鸽巢原理

    2301_80144607: 例4是不是有点问题

  • 【数据库基础】关系代数之除法

    鹿角海棠0241: 谢谢

大家在看

  • Java | Leetcode Java题解之第503题下一个更大元素II
  • 基于SSM的蛋糕甜品店管理系统(有报告)。Javaee项目。ssm项目。 469
  • Lucas带你机器学习实战——KNN预测未来的爆品
  • Lucas带你手撕机器学习——K近邻 425
  • 基于卷尾猴算法优化的灰度熵图像多阈值分割 324

最新文章

  • LeetCode 2390. 从字符串中移除星号【栈】1347
  • LeetCode 2073. 买票需要的时间【一次遍历+数学】1325
  • LeetCode 1014. 最佳观光组合【枚举右维护左,数组】1730
2024
10月 2篇
09月 5篇
08月 9篇
06月 1篇
05月 9篇
04月 2篇
03月 2篇
02月 1篇
01月 11篇
2023年239篇
2022年84篇
2021年388篇
2020年621篇
2019年73篇
2018年70篇

目录

目录

分类专栏

目录

评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memcpy0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或 充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值

玻璃钢生产厂家当阳玻璃钢雕塑批发四川玻璃钢雕塑凳子方形玻璃钢花盆厂家直销乌鲁木齐景区玻璃钢雕塑内蒙古玻璃钢仿真水果雕塑定制江苏装饰商场美陈生产企业惠州定做玻璃钢面包雕塑贵州大型玻璃钢雕塑品牌玻璃钢雕塑价格合理成都玻璃钢卡通雕塑批发卡通猴子玻璃钢雕塑晋城玻璃钢浮雕雕塑小品延津玻璃钢雕塑出售树脂玻璃钢花盆侯马玻璃钢花盆花器宿迁商场主题美陈水景校园玻璃钢景观雕塑定制金昌玻璃钢景观雕塑报价北京人物玻璃钢雕塑定制大庆玻璃钢雕塑公司临沧玻璃钢雕塑报价南京玻璃钢动物雕塑无锡镜面玻璃钢雕塑信息推荐实用的玻璃钢景观雕塑玻璃钢仿铜人物雕塑哪家好桥北商场主题美陈西山区玻璃钢雕塑定做上海玻璃钢龙雕塑七夕商场美陈图片景观玻璃钢雕塑设计公司香港通过《维护国家安全条例》两大学生合买彩票中奖一人不认账让美丽中国“从细节出发”19岁小伙救下5人后溺亡 多方发声单亲妈妈陷入热恋 14岁儿子报警汪小菲曝离婚始末遭遇山火的松茸之乡雅江山火三名扑火人员牺牲系谣言何赛飞追着代拍打萧美琴窜访捷克 外交部回应卫健委通报少年有偿捐血浆16次猝死手机成瘾是影响睡眠质量重要因素高校汽车撞人致3死16伤 司机系学生315晚会后胖东来又人满为患了小米汽车超级工厂正式揭幕中国拥有亿元资产的家庭达13.3万户周杰伦一审败诉网易男孩8年未见母亲被告知被遗忘许家印被限制高消费饲养员用铁锨驱打大熊猫被辞退男子被猫抓伤后确诊“猫抓病”特朗普无法缴纳4.54亿美元罚金倪萍分享减重40斤方法联合利华开始重组张家界的山上“长”满了韩国人?张立群任西安交通大学校长杨倩无缘巴黎奥运“重生之我在北大当嫡校长”黑马情侣提车了专访95后高颜值猪保姆考生莫言也上北大硕士复试名单了网友洛杉矶偶遇贾玲专家建议不必谈骨泥色变沉迷短剧的人就像掉进了杀猪盘奥巴马现身唐宁街 黑色着装引猜测七年后宇文玥被薅头发捞上岸事业单位女子向同事水杯投不明物质凯特王妃现身!外出购物视频曝光河南驻马店通报西平中学跳楼事件王树国卸任西安交大校长 师生送别恒大被罚41.75亿到底怎么缴男子被流浪猫绊倒 投喂者赔24万房客欠租失踪 房东直发愁西双版纳热带植物园回应蜉蝣大爆发钱人豪晒法院裁定实锤抄袭外国人感慨凌晨的中国很安全胖东来员工每周单休无小长假白宫:哈马斯三号人物被杀测试车高速逃费 小米:已补缴老人退休金被冒领16年 金额超20万

玻璃钢生产厂家 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化