操作系统原理实验(4):深渊:竞争条件与死锁

本文档详细介绍了操作系统实验,旨在理解和解决竞争条件与死锁问题,以及实现键盘中断处理。实验涉及初始化中断控制器、处理定时器中断、避免死锁、解析键盘扫描码以及实现大小写和小键盘锁定功能。实验揭示了并发编程的挑战,如不确定性的竞争条件和可能导致系统挂起的死锁。通过Rust语言,实验者不仅修复了测试与中断处理程序之间的竞争,还自定义实现了键盘输入的响应机制。
摘要由CSDN通过智能技术生成

一、实验目的

当多个任务访问同一个资源(数据)是就会引发竞争条件问题,这不仅在进程间会出现,在操作系统和进程间也会出现。由竞争条件引发的问题很难复现和调试,这也是其最困难的地方。本实验的目的在于了解竞争条件和死锁现象,并掌握处理这些问题的初步方法等。。

二、实验过程&错误

内容(一):死锁的复现及死锁的简单处理

在硬件中断的帖子中,我们设置了可编程中断控制器,以正确地将硬件中断转发到CPU。为了处理这些中断,我们将新条目添加到中断描述符表中,就像我们为异常处理程序一样。我们将学习如何获取定期的计时器中断以及如何从键盘获取输入。
中断提供了一种从附加硬件设备通知CPU的方法。因此,与其让内核定期检查键盘中的新字符(一个称为轮询的进程),键盘还可以将每个按键通知内核。这样做的效率要高得多,因为内核只需要在发生了什么事情时才采取行动。我们无法将所有硬件设备直接连接到CPU。相反,通过一个单独的中断控制器从所有设备聚合中断,然后通知CPU:
大多数中断控制器是可编程的,这意味着它们支持不同优先级的中断。例如,这允许定时器中断比键盘中断更高的优先级,以确保精确的时间保持。与异常不同,硬件中断异步发生。这意味着它们完全独立于所执行的代码并且可以在任何时间发生。因此,我们在内核中突然有一个并发性的形式,所有潜在并发相关的错误。
8259 PIC:Intel 8259是1976年推出的可编程中断控制器(PIC)。长期以来,它已被较新的APIC所取代,但出于向后兼容性的原因,它的接口仍然在当前系统上得到支持。与APIC相比,8259 PIC的建立要容易得多,因此我们将在以后的文章中切换到APIC之前,使用它来介绍中断。8259具有8条中断线和几条用于与CPU通信的线路。当时的典型系统配备了两个8259个PIC的实例,一个初级PIC和一个二级PIC连接到主系统的一条中断线上。
15行的大部分具有固定的映射,例如,次级PIC的线4被分配给鼠标。每个控制器可以通过两个I/O端口、一个“命令”端口和一个“资料”端口来配置。对于主控制器,这些端口是0x20(命令)和0x21(数据)。对于辅助控制器,它们为0xA0(命令)和0xA1(数据)。
步骤1:重新映射。pics的默认配置不可用,因为它向CPU发送范围0-15中的中断向量编号。这些数字已被CPU异常占用,例如8号对应于双故障。
要解决此重叠问题,我们需要将PIC中断重新映射到不同的数字。实际范围无关紧要,只要它不与异常重叠。我们这里选择范围32-47,因为这些是在32个例外时隙之后的第一组自由数字。该配置通过将特殊值写入PICS的命令和数据端口而发生。幸运的是,已经有一个名为PIC8259_SIMPLE的机箱,因此我们不需要自己编写初始化序列。要将机箱添加为依赖项,我们将以下项目添加到项目中,我们需要在toml文件中加入如下代码:
在这里插入图片描述

然后设置主/副PIC布局的链式PICS结构,这里需要将interrupts文件加入如下代码:
在这里插入图片描述

我们将pics的偏移设置为上面提到的32-47范围。通过在Mutex中封装chainedpics结构,我们可以获得安全的可变访问(通过锁定方法),我们在下一步中需要这样做。chainedpics::新函数不安全,因为错误的偏移可能会导致未定义的行为。我们现在可以在init函数中初始化8259PIC,这里需要将lib文件修改如下:
在这里插入图片描述

我们使用Initialize函数执行PIC初始化。与chainedpics::new函数一样,此函数也不安全,因为如果PIC配置不当,则会导致未定义的行为。如果一切顺利,我们应该在执行cargo Xrun时看到"it did not crash"消息。
问题1-1:编译不成功
在这里插入图片描述

解决方法1-1:找一个有网络的地方。。。注意,校园网不可以,一定要用别的或者流量
现象1-1:运行成功
步骤2:启动中断。到目前为止,还没有发生任何事情,因为在CPU配置中仍然禁用了中断。这意味着CPU根本不听中断控制器的声音,所以没有中断可以到达CPU。让我们改变这一点,我们将lib文件修改成如下样子:
在这里插入图片描述

但发现有双重故障:
在这里插入图片描述

造成这种双重故障的原因是硬件定时器(确切地说是Intel 8253)默认启用,所以我们一启用中断就开始接收定时器中断。因为我们还没有为它定义一个处理程序函数,所以会调用我们的双故障处理程序。
步骤3:处理定时器中断。正如我们从上图看到的,计时器使用主PIC的第0行。这意味着它到达CPU作为中断32(0+偏移32)。代替硬编码索引32,我们将其存储在中断索引ENUM中,将interrupts文件增加如下代码:
在这里插入图片描述

enum是一个类似C-like enum,因此我们可以直接为每个变量指定索引。repr(U8)属性指定每个变量都表示为U8。我们将在未来为其他中断添加更多的变体。现在,我们可以为计时器中断添加一个处理程序函数,继续对interrupts文件进行修改和添加如下代码:
在这里插入图片描述

计时器_中断_处理程序具有与异常处理程序相同的签名,因为CPU对异常和外部中断的响应是相同的(唯一的区别是某些异常会推送错误代码)。InterruptDescriptorTable结构实现了IndexMut特性,因此我们可以通过数组索引语法访问单个条目。在定时器中断处理程序中,我们在屏幕上打印一个点。由于计时器中断是定期发生的,我们希望看到每个计时器滴答上出现一个点。然而,当我们运行它时,我们看到只有一个点被打印出来:
在这里插入图片描述

注意,这里需要对main文件进行修改,去掉原本的stack_overflow函数才能得到结果。
在这里插入图片描述

步骤4:结束中断。原因是PIC期望从我们的中断处理程序得到一个明确的“中断结束”(EOI)信号。此信号告诉控制器中断已被处理,系统已准备好接收下一个中断。因此PIC认为我们还在忙着处理第一个定时器中断,在发送下一个信号之前耐心地等待EOI信号。要发送EOI,我们再次使用静态PICS结构,对interrupts文件进行如下修改:
在这里插入图片描述

notifyendofinterrup确定主PIC还是次PIC发送中断,然后使用命令和数据端口向各个控制器发送EOI信号。如果辅助PIC发送中断,则需要通知两个PIC,因为辅助PIC连接到主PIC的输入行。我们需要小心使用正确的中断矢量号,否则我们可能会意外删除一个重要的未发送中断或导致系统挂起。这是函数不安全的原因。当我们现在执行cargo xrun时,我们会看到屏幕上定期出现的点:
在这里插入图片描述

我们使用的硬件定时器称为可编程间隔定时器,简称PIT。如名称所示,可以在两个中断之间配置间隔。我们不会在这里详细讨论,因为我们将很快切换到APIC定时器,但是OSDevwiki有一篇关于配置PIT的广泛文章。
步骤5:死锁。
我们的内核中现在有一个并发的形式:定时器中断是异步发生的,因此它们可以随时中断我们的启动功能。幸运的是,rust的所有权系统在编译时防止了许多类型的并发相关的错误。一个值得注意的例外是死锁。如果线程试图获取永远不会变为空闲的锁,则会发生死锁。从而该线程无限期地悬挂。我们可以在内核中引发死锁。记住,我们的println宏调用vga_buffer::_print函数,它使用spinlock锁定全局写入程序。如图:
在这里插入图片描述

它锁定写入器,在其上调用WITH_FMT,并在函数结束时隐式地解锁它。现在,假设在写入器被锁定时发生中断,并且中断处理程序也试图打印一些内容:
在这里插入图片描述

WRITER被锁定,因此中断处理程序等待直到它变为空闲。但这永远不会发生,因为start函数只在中断处理程序返回后继续运行。因此,整个系统将挂起。
步骤6:引发死锁:
通过在我们的_start函数末尾打印循环中的东西,我们可以很容易地在内核中引发这种死锁,将main文件修改如下:
在这里插入图片描述

运行结果是这样的:
在这里插入图片描述

我们看到只有有限数量的连字符被打印出来,直到第一个定时器中断发生。然后系统挂起,因为计时器中断处理程序在试图打印点时会死锁。这就是我们在上面的输出中没有看到点的原因。由于计时器中断是异步发生的,因此每次运行时连字符的实际数量都会有所不同。这种不确定性使得与并发相关的bug很难调试。
步骤7:解决死锁:
为了避免死锁,只要互斥信号量被锁定,我们就可以禁用中断。比方说讲vga_buffer文件修改如下:
在这里插入图片描述

without_interrupts 函数采用闭包,并在没有中断的环境中执行。我们使用它来确保只要Mutex被锁定,就不会发生中断。当我们现在运行我们的内核时,我们看到它在不挂起的情况下继续运行。(我们仍然没有注意到任何点,但这是因为它们滚动得太快了。尝试放慢打印速度,例如在循环中放置一个for_in 0…10000{}。)
我们可以将相同的更改应用于我们的串行打印功能,以确保它不会出现死锁。将serial文件修改如下;
在这里插入图片描述

请注意,禁用中断不应该是一个通用的解决方案。问题是它增加了最坏的中断延迟,即系统对中断作出反应的时间。因此,中断应该只在很短的时间内被禁用。

内容(二):竞争条件

步骤1:修正文件。
如果我们运行Cargo xtest,我们可能会看到test_println_Output测试失败。注意注意,这里是“可能”,我做了三次才失败了一次!!
在这里插入图片描述

原因是测试和计时器处理程序之间的竞争条件。
测试将一个字符串打印到VGA缓冲区,然后通过手动迭代缓冲区_chars数组来检查输出。出现争用情况是因为计时器中断处理程序可能在println和读取屏幕字符之间运行。请注意,这并不是一个危险的数据竞争,Rust在编译时完全阻止了这一点。有关详细信息,请参阅Rustonom图标。要解决这个问题,我们需要在测试的整个时间内锁定编写器,这样计时器处理程序就不能写一个。在中间的屏幕上。我们需要对vga_buffer文件进行修改:
在这里插入图片描述

我们进行了以下更改:
1、我们通过显式地使用lock()方法将写入器锁定在完整的测试中。代替println,我们使用可允许打印到已锁定的写入器的wertelnMarco。
2、为避免另一个死锁,我们禁用测试持续时间的中断。否则,当写入器仍处于锁定状态时,测试可能会中断。
3、由于定时器中断处理程序仍然可以在测试之前运行,所以我们在打印字符串S之前打印一条附加的新行N。这样,我们可以避免当计时器处理程序已经打印过一些时的测试失败。
随着上述变化,cargo Xtest现在确定性地再次成功。这是一个非常无害的竞争条件,只造成测试失败。正如您所想象的那样,由于它们的非确定性特性,其他竞争条件可能更难以调试。幸运的是,rust会阻止我们的数据竞争,这是最严重的竞争条件,因为它们会导致各种未定义的行为,包括系统崩溃和内存损坏。

内容(三):使用pc-keyboard 库实现键盘中断处理函数

步骤1:键盘输入。
现在,我们能够处理来自外部设备的中断,我们终于能够增加对键盘输入的支持。这将允许我们第一次与内核交互。请注意,我们只介绍如何处理这里的PS/2键盘,而不是USB键盘。然而,主板模拟USB键盘作为PS/2设备来支持较早的软件,因此我们可以安全地忽略USB键盘,直到我们的内核有USB支持。
与硬件计时器一样,默认情况下,键盘控制器已启用。因此,当您按键时,键盘控制器向PIC发送中断,PIC将其转发到CPU。CPU查找IDT中的处理程序函数,但相应的条目为空。因此发生双故障。因此,让我们为键盘中断添加处理程序函数。它非常类似于我们为定时器中断定义了处理程序,它只是使用了一个不同的中断号。将interrupts文件修改如下:
在这里插入图片描述

正如我们从上图看到的,键盘使用主PIC的第1行。这意味着它作为中断33到达cpu(1偏移量32)。我们将此索引作为新的键盘变量添加到InterruptIndex enum中。我们不需要显式地指定值,因为它默认为前一个值加一个,也就是33。在中断处理程序中,我们打印一个k,并将中断信号的结束发送给中断控制器。
我们现在看到,当我们按下一个键时,屏幕上会出现一个k。然而,这只适用于我们按下的第一个键,即使我们继续按下键,屏幕上也不会出现更多的k键。这是因为键盘控制器不会发送另一个中断,直到我们阅读了所谓的按下键的扫描代码。
在这里插入图片描述

步骤2:读取扫描代码。
为了找出哪个键被按下,我们需要查询键盘控制器。我们通过从PS/2控制器的数据端口读取数据来做到这一点,该数据端口是编号为0x60的I/O端口,将interrupts文件进行修改:
在这里插入图片描述

我们使用x86_64库的端口类型从键盘的数据端口读取字节。这个字节被称为扫描代码,是一个表示按键/发布的数字。我们还没有对扫描代码做任何事情,只需将其打印到屏幕上:
在这里插入图片描述

相邻的键具有相邻的扫描码,并且按下一个键导致不同于释放它的不同的扫描码。但是,我们如何将扫描代码准确地转换为实际的关键动作?
步骤12:解释扫描代码。
扫描码和键之间的映射有三种不同的标准,即所谓的扫描码集.这三者都回到了早期IBM计算机的键盘上:IBMXT、IBM 3270 PC和IBMAT。幸运的是,后来的计算机没有继续定义新的扫描代码集的趋势,而是模仿了现有的集并对它们进行了扩展。今天,大多数键盘可以配置为模仿这三组中的任何一组。
默认情况下,PS/2键盘模拟扫描代码集1(“XT”)。在这个集合中,扫描代码字节的下面7位定义键,最重要的位定义是按下(“0”)还是发布(“1”)。没有出现在原始IBMXT键盘上的键(如键盘上的Enter键)依次生成两个扫描代码:一个0xe0转义字节,然后一个代表该键的字节。有关所有集合1扫描码及其相应密钥的列表,请查看OSDevWiki。
要将扫描代码转换为键,可以使用Match语句,这里对interrupts文件进行修改。
在这里插入图片描述

上述代码转换数字键0-9的按键并忽略所有其它键。它使用匹配语句为每个扫描代码分配一个字符或一个字符。然后,它使用iflet来解构可选的密钥。通过在图形中使用相同的变量名称关键字,我们会遮蔽前面的声明,这是一种常见的模式,用于在rust中破坏选项类型。
在这里插入图片描述

将其他键转换为相同的方式。幸运的是,有一个名为PC-keyboard的库,用于翻译ScanCode集1和2的ScanCode,因此我们不必自己实施。要使用库,我们将其添加到我们的Cargo.toml中,并将其导入到我们的lib.rs:中,这里需要对toml文件进行修改。
在这里插入图片描述

现在我们可以使用这个库重写键盘_中断处理程序,即将interrupts文件修改如下:
在这里插入图片描述

我们使用lazy_static宏创建受互斥体保护的静态键盘对象。在每个中断上,我们锁定互斥体,从键盘控制器读取ScanCode,并将其传递到add_byte方法,该方法将ScanCode转换为选项。KeyEvent包含导致该事件的密钥以及该事件是否为引发或释放事件。
要解释此关键事件,我们将其传递到Process_KeyEvent方法,如果可能,该方法将密钥事件转换为字符。例如,根据是否按下Shift键,将A键的按事件转换为小写A字符或大写A字符。现在我们可以输入字符了:
在这里插入图片描述

注意,一定要找一个网络好的地方,而且不能是校园网。

内容(四)不使用 pc-keyboard 库实现键盘的主要键位的解析

这个可以说是本次实验中最难的一个地方了,需要我们自己实现
步骤1:首先呢,我们先把我们前面写的,所使用的到pc*keyboard这个库的代码进行修改、注释掉,这样才能够编写我们的代码:
在这里插入图片描述

原本是使用lazy_static宏创建受互斥体保护的静态键盘对象。在每个中断上,我们锁定互斥体,从键盘控制器读取ScanCode,并将其传递到add_byte方法,该方法将ScanCode转换为选项。KeyEvent包含导致该事件的密钥以及该事件是否为引发或释放事件。
如果我们想实现的话,首先,也要实现一个keyboard_interrupt_handler函数,对我们输入进来的_stack_frame和interruptstackframe进行处理,这里可以采用我们的博客中所给的实例:
在这里插入图片描述

上述代码转换数字键0-9的按键并忽略所有其它键。它使用匹配语句为每个扫描代码分配一个字符或一个字符。然后,它使用iflet来解构可选的密钥。通过在图形中使用相同的变量名称关键字,我们会遮蔽前面的声明,这是一种常见的模式,用于在rust中破坏选项类型。
那么,现在的问题就是,如何解决映射的问题,或者说如何解决扫描码与键的应设问题,扫描码和键之间的映射有三种不同的标准,即所谓的扫描码集.这三者都回到了早期IBM计算机的键盘上:IBMXT、IBM 3270 PC和IBMAT。幸运的是,后来的计算机没有继续定义新的扫描代码集的趋势,而是模仿了现有的集并对它们进行了扩展。今天,大多数键盘可以配置为模仿这三组中的任何一组。
默认情况下,PS/2键盘模拟扫描代码集1(“XT”)。在这个集合中,扫描代码字节的下面7位定义键,最重要的位定义是按下(“0”)还是发布(“1”)。没有出现在原始IBMXT键盘上的键(如键盘上的Enter键)依次生成两个扫描代码:一个0xe0转义字节,然后一个代表该键的字节。我们可以找到下面一张表:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

那么,就按照这张图的进行修改便是了吧,我们可以添加上我们想要实现的键盘按键的代码,比方说想要实现A,就在其中加入0x1e分支用来知道我们按下了按键‘A’,以此来在屏幕上打印出一个字母A
最终我们得到的代码是这样的:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可以看到还有一些按键我没有实现,其中大部分是我不知道该让他们在界面上打印出什么值来,比方说alt键,难道说我打印一个alt出来吗。。。
现象1-1:现在我的结果是这样的:
在这里插入图片描述

步骤2:
一直打印‘.’让我感到很烦,那么,我就把这个去掉吧。一直打印‘.’的原因是我们在定时器中断函数里面用了一个print宏,仅仅是为了提示我们到达了定时器中断,那么,把这个就注释掉吧。
在这里插入图片描述

现象2-1:现在我的打印是这样的:
在这里插入图片描述

实验成功了
步骤3:
我对这个结果还是不是很满意,我想要实现打印字符串可以吗?当然,只需要把代码修改成这个样子:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后我让那些什么F1啦,ALT啦都加进来,这样才对嘛
在这里插入图片描述

但是这里有一个问题,由于我们是在虚拟机上运行的,当我们按下alt键的时候,他会以为我们要访问“搜索”功能,所以会跳出qemu界面,所以,alt键最好还是不要用了。
步骤3:接下来,我想实现大小写的切换,怎么办呢?
我可以通过一个标示量flag和if判断来实现:
首先通过static定义一个全局静态变量用于判断
在这里插入图片描述

然后使用if语言进行判断即可,0是大写1是小写,只不过我们可能要写26的判断分支才能够完全解决大小写问题,有些麻烦:
最终得到的代码如下所示:
在这里插入图片描述
在这里插入图片描述

代码很长,我也在找优化的方法,但是至少,功能是实现了,可以实现字母的大小写切换:
在这里插入图片描述

步骤4:接下来,考虑小键盘的锁死问题。
我想实现再按下numlock的时候为开,否则为关。当然,这个可能没有办法和我的主机系统相联系,因为我是在虚拟机上实现的,所以,我就来人为设定,初始的时候电脑的numlock是关闭的,我的代码中也是显示为关闭的,将会打印“num is locked”语句
实现代码如下:由于代码过长,进行了分栏处理

extern "x86-interrupt" fn keyboard_interrupt_handler(
	_stack_frame: &mut InterruptStackFrame)
{
	use x86_64::instructions::port::Port;

	let mut port = Port::new(0x60);
	let scancode : u8 = unsafe{ port.read() };

	// new
	let key = match scancode{
	0x02 = > Some("1"),
		0x03 = > Some("2"),
		0x04 = > Some("3"),
		0x05 = > Some("4"),
		0x06 = > Some("5"),
		0x07 = > Some("6"),
		0x08 = > Some("7"),
		0x09 = > Some("8"),
		0x0a = > Some("9"),
		0x0b = > Some("0"),
		0x0c = > Some("-"),
		0x0d = > Some("="),
		//0x0e => Some(""),退格
		0x0f = > Some("    "),
		0x10 = > Some("Q"),
		0x11 = > Some("W"),
		0x12 = > Some("E"),
		0x13 = > Some("R"),
		0x14 = > Some("T"),
		0x15 = > Some("Y"),
		0x16 = > Some("U"),
		0x17 = > Some("I"),
		0x18 = > Some("O"),
		0x19 = > Some("P"),
		0x1a = > Some("["),
		0x1b = > Some("]"),
		0x1c = > Some("\n"),
		0x1d = > Some("left ctrl"),
		0x1e = > Some("A"),
		0x1f = > Some("S"),
		0x20 = > Some("D"),
		0x21 = > Some("F"),
		0x22 = > Some("G"),
		0x23 = > Some("H"),
		0x24 = > Some("J"),
		0x25 = > Some("K"),
		0x26 = > Some("L"),
		0x27 = > Some(";"),
		0x28 = > Some("'"),
		//0x29 => Some(""),别管
		0x2a = > Some("left shift"),
		0x2b = > Some("\\"),
		0x2c = > Some("Z"),
		0x2d = > Some("X"),
		0x2e = > Some("C"),
		0x2f = > Some("V"),
		0x30 = > Some("B"),
		0x31 = > Some("N"),
		0x32 = > Some("M"),
		0x33 = > Some(","),
		0x34 = > Some("."),
		0x35 = > Some("/"),
		0x36 = > Some("right shift"),
		0x37 = > Some("*)"),
		0x38 = > Some("left alt"),
		0x39 = > Some(" "),
		0x3a = > Some("caps"),
		0x3b = > Some("F1"),
		0x3c = > Some("F2"),
		0x3d = > Some("F3"),
		0x3e = > Some("F4"),
		0x3f = > Some("F5"),
		0x40 = > Some("F6"),
		0x41 = > Some("F7"),
		0x42 = > Some("F8"),
		0x43 = > Some("F9"),
		0x44 = > Some("F10"),
		0x45 = > Some("numlock"),
		//0x46 => Some(""),别管
		0x47 = > Some("7)"),
		0x48 = > Some("8)"),
		0x49 = > Some("9)"),
		0x4a = > Some("-)"),
		0x4b = > Some("4)"),
		0x4c = > Some("5)"),
		0x4d = > Some("6)"),
		0x4e = > Some("+)"),
		0x4f = > Some("1)"),
		0x50 = > Some("2)"),
		0x51 = > Some("3)"),
		0x52 = > Some("0)"),
		0x53 = > Some(".)"),
		0x57 = > Some("F11"),
		0x58 = > Some("F12"),
		_ = > None,
	};
	if let Some(key) = key{
		unsafe{
			if key == "caps"{
				if flag == 0{
					flag = 1;
				}
else {
   flag = 0;
}
}
else if key == "numlock"{
   if nlock == 0{
	   nlock = 1;
   }
else {
   nlock = 0;
}
}
else {
   if flag == 0{
	   if nlock == 0{
		   if key == "7)"{
			   print!("num is locked");
		   }
else if key == "8)"{
   print!("num is locked");
}
else if key == "9)"{
   print!("num is locked");
}
else if key == "-)"{
   print!("num is locked");
}
else if key == "4)"{
   print!("num is locked");
}
else if key == "5)"{
   print!("num is locked");
}
else if key == "6)"{
   print!("num is locked");
}
else if key == "+)"{
   print!("num is locked");
}
else if key == "1)"{
   print!("num is locked");
}
else if key == "2)"{
   print!("num is locked");
}
else if key == "3)"{
   print!("num is locked");
}
else if key == "0)"{
   print!("num is locked");
}
else if key == ".)"{
   print!("num is locked");
}
else if key == "*)"{
   print!("num is locked");
}
else {
   print!("{}", key);
}
}
else {
   if key == "7)"{
	   print!("7");
   }
else if key == "8)"{
   print!("8");
}
else if key == "9)"{
   print!("9");
}
else if key == "-)"{
   print!("-");
}
else if key == "4)"{
   print!("4");
}
else if key == "5)"{
   print!("5");
}
else if key == "6)"{
   print!("6");
}
else if key == "+)"{
   print!("+");
}
else if key == "1)"{
   print!("1");
}
else if key == "2)"{
   print!("2");
}
else if key == "3)"{
   print!("3");
}
else if key == "0)"{
   print!("0");
}
else if key == ".)"{
   print!(".");
}
else if key == "*)"{
   print!("*");
}
else {
   print!("{}", key);
}
}
}
else {
   if key == "A"{
	   print!("a");
   }
else if key == "B"{
print!("b");
}
else if key == "C"{
print!("c");
}
else if key == "D"{
print!("d");
}
else if key == "E"{
print!("e");
}
else if key == "F"{
print!("f");
}
else if key == "G"{
print!("g");
}
else if key == "H"{
print!("h");
}
else if key == "I"{
print!("i");
}
else if key == "J"{
print!("j");
}
else if key == "K"{
print!("k");
}
else if key == "L"{
print!("l");
}
else if key == "M"{
print!("m");
}
else if key == "N"{
print!("n");
}
else if key == "O"{
print!("o");
}
else if key == "P"{
print!("p");
}
else if key == "Q"{
print!("q");
}
else if key == "R"{
print!("r");
}
else if key == "S"{
print!("s");
}
else if key == "T"{
print!("t");
}
else if key == "U"{
print!("u");
}
else if key == "V"{
print!("v");
}
else if key == "W"{
print!("w");
}
else if key == "X"{
print!("x");
}
else if key == "Y"{
print!("y");
}
else if key == "Z"{
print!("z");
}
else if nlock == 0{
   if key == "7)"{
	   print!("num is locked");
   }
else if key == "8)"{
   print!("num is locked");
}
else if key == "9)"{
   print!("num is locked");
}
else if key == "-)"{
   print!("num is locked");
}
else if key == "4)"{
   print!("num is locked");
}
else if key == "5)"{
   print!("num is locked");
}
else if key == "6)"{
   print!("num is locked");
}
else if key == "+)"{
   print!("num is locked");
}
else if key == "1)"{
   print!("num is locked");
}
else if key == "2)"{
   print!("num is locked");
}
else if key == "3)"{
   print!("num is locked");
}
else if key == "0)"{
   print!("num is locked");
}
else if key == ".)"{
   print!("num is locked");
}
else if key == "*)"{
   print!("num is locked");
}
else {
   print!("{}", key);
}
}
else {
   if key == "7)"{
	   print!("7");
   }
else if key == "8)"{
   print!("8");
}
else if key == "9)"{
   print!("9");
}
else if key == "-)"{
   print!("-");
}
else if key == "4)"{
   print!("4");
}
else if key == "5)"{
   print!("5");
}
else if key == "6)"{
   print!("6");
}
else if key == "+)"{
   print!("+");
}
else if key == "1)"{
   print!("1");
}
else if key == "2)"{
   print!("2");
}
else if key == "3)"{
   print!("3");
}
else if key == "0)"{
   print!("0");
}
else if key == ".)"{
   print!(".");
}
else if key == "*)"{
   print!("*");
}
else {
   print!("{}", key);
						}
					}
				}
			}
		}
	}

		unsafe{
			PICS.lock()
				.notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
		}
}

现象4-1:实现结果如下
在这里插入图片描述

可以实现小键盘的锁死,但是注意,由于虚拟机的运行,导致了在小键盘锁死的情况下,有些键在虚拟机系统上有其原本的意思,可能会干扰试验,比方说“2”,比方说“-”,实验的时候请注意。

三、实验重难点

1、死锁的必要条件:

〈1〉互斥条件。即某个资源在一段时间内只能由一个进程占有,不能同时被两个或两个以上的进程占有。这种独占资源如CD-ROM驱动器,打印机等等,必须在占有该资源的进程主动释放它之后,其它进程才能占有该资源。这是由资源本身的属性所决定的。如独木桥就是一种独占资源,两方的人不能同时过桥。
〈2〉不可抢占条件。进程所获得的资源在未使用完毕之前,资源申请者不能强行地从资源占有者手中夺取资源,而只能由该资源的占有者进程自行释放。如过独木桥的人不能强迫对方后退,也不能非法地将对方推下桥,必须是桥上的人自己过桥后空出桥面(即主动释放占有资源),对方的人才能过桥。
〈3〉占有且申请条件。进程至少已经占有一个资源,但又申请新的资源;由于该资源已被另外进程占有,此时该进程阻塞;但是,它在等待新资源之时,仍继续占用已占有的资源。还以过独木桥为例,甲乙两人在桥上相遇。甲走过一段桥面(即占有了一些资源),还需要走其余的桥面(申请新的资源),但那部分桥面被乙占有(乙走过一段桥面)。甲过不去,前进不能,又不后退;乙也处于同样的状况。
〈4〉循环等待条件。存在一个进程等待序列{P1,P2,…,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一源,…,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。就像前面的过独木桥问题,甲等待乙占有的桥面,而乙又等待甲占有的桥面,从而彼此循环等待。
上面我们提到的这四个条件在死锁时会同时发生。也就是说,只要有一个必要条件不满足,则死锁就可以排除。

2、竞争条件:

pdf中的代码没有办法在linux上正常运行,因为它的调用方法是错误的,我们在经过实验三之后,已经是在我们的main文件中设置了使用文件夹作为一个库,我们可以调用我们所写的vga_buffer中的println宏,但是没有办法调用vga_buffer中的某个类或者结构体。如果我们想使用mod vga_buffer来引用vga_buffer这个库,将会遇到两者的冲突,所以,这里我采取了网站上的竞争条件的情况。
网上是故意设计了一个测试和计时器处理程序之间的竞争条件。
测试将一个字符串打印到VGA缓冲区,然后通过手动迭代缓冲区_chars数组来检查输出。出现争用情况是因为计时器中断处理程序可能在println和读取屏幕字符之间运行。请注意,这并不是一个危险的数据竞争,Rust在编译时完全阻止了这一点。有关详细信息,请参阅Rustonom图标。要解决这个问题,我们需要在测试的整个时间内锁定编写器,这样计时器处理程序就不能写一个’.’在中间的屏幕上。

3、rust语言——变量

1、变量名中可以包含字母、数字和下划线。也就只能是 abcdefghijklmnopqrstuvwxyz013456789_ 以及大写字母。
2、变量名必须以字母或下划线开头。也就是不能以数字开头。
3、变量名是区分大小写的。也就是大写的 A 和小写的a 是两个不同的字符
Rust 语言中声明变量的语法格式如下

let variable_name = value;            // 不指定变量类型
let variable_name:dataType = value;   // 指定变量类型
默认情况下,Rust 语言中的变量是不可变的。Rust 语言中使用 let 声明的变量,在第一次赋值之后,是不可变更不可重新赋值的,变成了只读状态。let 关键字声明的变量默认情况下是不可变更的,在定义了一个变量之后,默认情况下我们是不能通过赋值再改变它的值的。
Rust 语言提供了mut关键字表示 可变更。我们可以在变量名的前面加上mut 关键字来告诉编译器这个变量是可以重新赋值的。
let mut variable_name:dataType = value;  //可更变量的声明语法
let mut variable_name = value;        //让编译器自动推断类型
对于const,常量贯穿于整个程序的生命周期。更具体的,Rust 中的常量并没有固定的内存地址。这是因为实际上它们会被内联到用到它们的地方。为此对同一常量的引用并不能保证引用到相同的内存地址。
const N: i32 = 5;    //定义一个整型常量
对于static,Rust以静态量的方式提供了类似“全局变量”的功能。它们与常量类似,不过静态量在使用时并不内联。这意味着对每一个值只有一个实例,并且位于内存中的固定位置。
static N: i32 = 5;
static NAME: &'static str = "Steve";     //静态量贯穿于整个程序的生命周期,因此任何存储在常量中的引用有一个'static生命周期
//因为这是可变的,一个线程可能在更新N同时另一个在读取它,导致内存不安全。
//因此访问和改变一个static mut是不安全(unsafe)的,因此必须在unsafe块中操作
static mut NUM: i32 = 10;
unsafe {
	NUM=NUM+1;
}

四、实验心得体会

再费尽心思完成了实验三之后,实验四就显得不是那么麻烦了。因为实验四的一部分已经在实验三中进行了,而我在实验三中直接完成了那一部分,所以对于我来说,实验四只是需要重复一遍,再仔细研究一下其中的语法就可以了。
实验四中的重点难点我已经标注了,在本实验报告的第三部分,但如果说最难的是哪一部分,应该是第四个实验,让我们通过自己的能力实现不适用它所提供的库来实现对键盘的响应。虽然我们已经在实验步骤中有过不通过库函数实现按键响应的操作,也许只是实现简单地响应只不过是要添加变量分支就是了。
但是,这样怎么能体现这次实验的难点与重点?所以我自己学习了一下rust语言的变量定义和全局变量的使用,以及if分支条件判断的用法。最终,实现了两个我自己的功能:
1、通过大小写锁定按键来实现控制输入的英文字母的大小写
2、通过小键盘锁定按键来实现对小键盘的锁定
后面还能不能加功能呢?当然可以,比方说,可以再尝试加入退格按键的响应,不过这就要求我们在vga_buffer文件里面下一个部分清屏函数;也可以实现上下左右方向按键来实现在指定位置的输入,但是这就要影响到可能我们要记忆我们所输入的字符串的值,就会麻烦很多。 由于我在做这个实验的时候恰逢考试周,我在完成实验三四的制定内容后也就只能将创新止步于此了,也许在我完成了考试复习并通过考试之后,还可以回来继续我未完成的创新,在这里只是记录一下。(2019.11.8)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值