操作系统第一部分实验 OS内核基础(3个学时)

第一部分实验   OS内核基础(3个学时)

  • 实验目的

1)学习Linux内核项目和应用程序项目的编译和调试方法。(对应《教程》实验一的3.2任务一和3.3—3.5任务二)

2)学习Linux的系统调用和添加内核函数。(对应《教程》实验四的唯一任务)

3)学习Linux对键盘设备和显示器终端的中断处理过程。(对应《教程》实验十的3.1任务一)

  • 实验步骤

1、跟随《教程》实验一3.2的指导流程,从平台领取Linux0.11内核项目,学习调试方法,按要求提交修改后的代码,记录以下实验相关步骤并回答相关问题。

(1)截图记录Linux0.11内核项目生成前后的项目目录,标记其发生的变化,发现其生成用于支持OS启动的可执行文件是什么?

图1  生成前的目录结构

生成前目录结构:

.vscode:Visual Studio Code的配置文件目录

boot:启动相关文件

fs:文件系统相关代码

include:头文件

init:初始化相关代码

main.c:主程序文件

makefile:用于构建项目的Makefile

kernel:内核相关代码

lib:库文件

mm:内存管理

图2  生成后的目录结构

生成后目录结构:

项目所有(.c)文件都会生成对应(.o)目标文件

artifact.done:应该是表示构建过程完成的标记文件

BIOS-bochs-latest:最新的Bochs BIOS文件

bochs.exe、bochsdbg.exe等:Bochs相关的可执行文件,用于模拟和调试

bochsrc.bxrc、bochsrcdbg.bxrc:Bochs的配置文件

floppya.img、floppyb.img、harddisk.img:磁盘镜像文件,用于Bochs模拟环境中

linux011.bin、linux011.exe: Linux 0.11内核的可执行文件

发生的变化主要有以下几点:

添加了多个与Bochs模拟器相关的文件,包括BIOS文件、可执行文件、配置文件和磁盘镜像。

编译生成了.o目标文件。

用于支持OS启动的可执行文件:

可以看到生成了linux011.bin和linux011.exe。这些文件是Linux 0.11内核编译后的可执行文件,用于在Bochs等模拟器中加载和运行操作系统。特别是linux011.bin是一个裸机二进制文件,可以直接被Bochs加载以启动操作系统。

(2)对main.c文件的修改,使得OS启动后会输出新的字符串信息;截图记录Display窗口的输出结果,描述OS启动后在终端输出了哪些信息?

图3  Display窗口的输出结果

答:操作系统启动后,在Bochs的Display窗口中输出了以下信息:

Bochs BIOS 信息:

Bochs版本:Bochs BIOS - build: 02/16/17

修订版本和日期:Revision: 13073 Date: 2017-02-16

BIOS设置选项Options: apmbios pcibios pnpbios eltorito rombios32

启动信息:

从软盘启动:Booting from Floppy...

系统加载信息:

加载系统:Loading system...

分区表状态:Partition table ok.

文件系统状态:39036/62000 free blocks, 19513/20666 free inodes

内存和缓冲区信息:

内存和缓冲区使用情况:3435 buffers = 3517440 bytes buffer space
可用内存:Free mem: 12582912 bytes
对main.c文件修改,使得OS启动后会输出的新的字符串信息:
"Hello Linux!!!" 、"WangYu" 通过修改main.c文件来实现的,修改的代码仅仅是在234行添加了printf("Hello Linux!!!\n\r"); printf("Wangyu\n\r");。

提示符信息:

Ok. 

[/usr/root]#

在这个终端输出中,我们可以看到操作系统成功加载并进入了命令行界面,显示了用户名和命令提示符。表明操作系统加载正常,而且已经运行到允许用户交互的程度。

2、跟随《教程》实验一3.3—3.5的指导流程,从平台领取Linux0.11应用程序项目,编译运行,提交3.5修改后的newapp;记录以下实验相关步骤并回答相关问题。

(1)熟悉Linux终端的基本命令,运行linuxapp应用程序后,尝试使用命令查看磁盘、A驱动器和B驱动器中的文件列表,能否成功?为什么?

答:我们尝试运行linuxapp程序,但遇到了cannot execute binary file的错误,接着通过chmod +x linuxapp命令成功给文件赋予了执行权限,并且程序最终成功运行并输出了"Hello world!"。

图4  运行linuxapp应用程序

尝试使用命令查看磁盘、A驱动器和B驱动器中的文件列表:

图5  无法进行A驱动器文件列表的查看

因为/dev/fd0是一个设备文件,而不是一个包含文件和目录的文件系统,所以ls /dev/fd0命令实际上没有意义,不会显示任何内容。正确的做法是先挂载这个设备到一个目录,然后在那个目录中使用ls命令。

但是如图5所示,在我的bochs上无法进行挂载操作,所以不能成功查看/dev/fd0,也就是A驱动器文件列表,B驱动器和磁盘文件列表同理不可以查看。

(2)不要使用vi在终端进行c源文件和makeflie文件的编写,尝试在虚拟机之外创建所需文件,转移到FloppyB中;启动虚拟机中的linux,通过mcopy和sync保存到硬盘,再编译执行。请截图记录3.5自己完成程序的输出结果。

图6  将在虚拟机外创建的文件转移到FloppyB中

图7  程序编译执行结果

3、跟随《教程》实验四的3.1-3.5的指导流程,为内核添加一个新的系统调用,测试并调试之,按要求提交修改后的内核和测试程序。

(1)截图记录执行max系统调用的输出结果, 描述一个在这条实验中遇到的问题以及解决方法。

#define __LIBRARY__

#include <unistd.h>

#include <stdio.h>

#define __NR_max 87

_syscall2intmaxint, max1, int, max2)

int main()

{

    int i = max(100,200);

    printf("the max of %d and %d is %d\n",100,200,i);

    return 0;

}

以上时我执行max系统调用的main.c测试文件,下图是输出结果:

图8  max系统调用的输出结果

问题:实验开始我将系统调用程序命名为test.c,即使程序内容相同,我发现在命令行中没有得到任何输出。

解决方法:重命名为main.c或t.c等后就可以正常输出。

图9  当文件名为test.c时命令行没有输出

原因分析:

这是因为test is a shell builtin,test是一个内建于shell中的命令,它用于评估表达式。在大多数shell中(如bash、sh等),test用于条件判断,它不产生输出,而是通过其退出状态码表达真假。因此在命令行中输入test并尝试执行时,shell实际上是在调用这个内建命令,而不是我编译的可执行文件。而t is hashed(./t)表明t被shell记录(hashed),并且指向当前目录中名为t的可执行文件。尝试运行这个程序时,shell能够正确找到并执行这个文件。

这是我不太熟悉Linux系统的命令导致的,系统调用测试文件意外与命令同名。

图10  出现该问题的原因

(2)绘制系统调用的流程图(标注涉及哪些源程序文件及其中的函数和对象)

通过调试得知,用户空间的程序通过包含更新过的unistd.h并使用_syscall2函数调用新的系统调用。当系统调用被触发时,通过汇编语言层的入口点,调度器将控制权传递给对应的内核函数sys_max。执行完成后,结果通过系统调用接口返回给用户空间,由用户程序接收并处理。实现了从用户空间到内核空间的函数调用和数据传递,从而允许我们的用户程序执行内核级别的操作。

图11  系统调用流程图

4、跟随《教程》实验十的3.1的指导流程,调试键盘输入和显示器输出的全过程,并修改内核程序改变输出控制,按要求提交修改后内核程序。

(1)通过对键盘中断服务程序和shell进程的输出过程,绘制键盘符号A的数据流图(标注整个流程中字符经过的寄存器和内存单元)

图12  键盘符号的数据流图

  1. 首先a被送入TTY读队列(内存单元)中。
  2. 键盘中断被触发时,A的扫描码首先被存储在某个CPU寄存器中(eax寄存器)。中断服务程序通过这些寄存器获取扫描码。key_table调用将使用这个扫描码来查找对应的ASCII值或控制命令。涉及到查阅一个预先定义好的表,该表将扫描码映射到ASCII字符。
  3. 字符的ASCII转换,orb %al,%al指令显示了对AL寄存器(eax寄存器)内容的操作,涉及到ASCII字符的调整或状态标记。

图13  “a”字符的ASCII码转换

4、通过callput_queue转换后的ASCII字符随后被送入TTY的缓冲区(内存单元)

5、从TTY到Shell,Shell从TTY的缓冲区(内存单元)读取字符,循环检查并读取TTY缓冲区的数据。

6、Shell处理可能包括命令解析、命令历史记录等。字符在这一过程中存储在用户数据段缓冲区(内存单元)中。

7、输出到TTY,直接回显的输入字符a被Shell发送回TTY写队列(内存单元)

8、字符显示在屏幕上,a最终被写入显存(内存单元)

(2)截图记录终端字符输出被置换前后的结果截图,观察输入的回显是否也被置换?为什么?

图14  输出置换前后结果

我认为输入的回显也被置换了。因为当我按下F12再次执行ls指令时,我输入的ls指令也是呈现为 **。说明我直接输入的回显也是被置换的。

这是因为输入的回显要从终端对应的tty写缓冲队列中取字符,并显示在屏幕上,而我在控制台写函数中使用judge来判断如果输入字符为阿拉伯数字、英文就转换为*。所以但凡要写在控制台上的字符都要经过这个函数,而经过这个函数后就发生了字符的置换。

图15  输出置换逻辑

  • 实验拓展与思考
  1. Linux0.11内核支持OS启动的几个可执行文件分别有什么作用?

答:Linux0.11内核支持OS启动的几个可执行文件作用:

  1. bootsect.bin,引导扇区程序,它是计算机启动时BIOS首先加载的代码。它负责加载更复杂的启动加载程序,如setup.bin。
  2. setup.bin是第二阶段的引导加载程序。在早期的Linux系统中,引导扇区程序将控制权转交给setup程序,它负责一些启动前的准备工作,如设置和检测硬件参数、准备进一步加载内核所需的环境。
  3. linux011.bin,这是实际的Linux内核二进制文件,包含了操作系统的核心代码。一旦setup程序完成其任务,它会加载这个内核二进制文件到内存中并执行。
  4. linux011.exe是为了在特定环境下运行Linux0.11内核而生成的特殊格式文件。是适配到特定模拟环境Bochs的可执行文件。
  5. artifact.done, BIOS-bochs-latest, bochs.exe, VGABIOS-lgpl-latest*这些文件和目录涉及模拟器(如Bochs)使用的BIOS和配置文件,以及用于记录构建过程或状态的标记文件。
  6. floppya.img, floppyb.img, harddisk.img这些是虚拟磁盘镜像文件,用于模拟器中模拟软驱和硬盘。

在准备Linux0.11内核启动的过程中,确保引导扇区、设置程序和内核二进制文件按正确顺序被加载是关键步骤。每个组件都扮演了启动过程中不可或缺的角色。

  1. 实验四的思考与练习第2题,给出主要代码,截图包含自己姓名的测试应用程序执行结果。

Linux 0.11 内核中添加两个系统调用函数 Iam 和 Whoami,函数原型如下:

  1. int Iam(const char* name);将字符串 name 的内容保存到内核中,返回值是拷贝的字符数, 如果 name 长度大于 32,则返回-1,并置 errno 为 EINVAL。
  2. int Whoami(char* name, int size);将 Iam 保存到内核中的字符串拷贝到数据缓冲区 name 中,size 为数据缓冲区 name 的长度,返回值是拷贝的字符数。如果 size 小于所需空间,则 返回-1,并置 errno 为 EINVAL。

要求:编写两个应用程序。其中,Iam 应用程序调用 Iam 函数,并使用命令行中的第一个参数作为 Iam 函数的参数;Whoami 应用程序调用 Whoami 函数,并打印输出获取的字符串。测试效果如下图:

图16  程序输出演示

提示:

1. errno 是一个传统的错误代码返回机制。当一个函数调用出错时,会把错误值存放到全局变量 errno 中,调用者就可以通过判断 errno 来决定如何应对错误,请读者找到 errno 是在哪里定义的。

2. 要在内核中保存字符串数据,需要在内核中定义一个字符数组全局变量。可以把该字符数组和两个系统调用的内核函数定义在 kernel/sys.c 文件中。

3. Iam 和 Whoami 两个系统调用使用了指针参数传递用户地址空间的逻辑地址(字符串的开始地 址),若在内核空间直接访问这个地址,访问的仍然是内核空间的数据,而不是用户空间的 数据。所以,在Iam函数中需要在一个循环中重复调用 get_fs_byte 函数,获取用户空间中的数据;在 Whoami函数中需要在一个循环中重复调用 put_fs_byte 函数,将内核空间中的数据复制到用户空间(详情请参考 include/asm/segment.h文件)。

主要代码(省略系统调用注册部分的代码):

图17  sys.c的系统调用实现逻辑


(a)Iam测试程序                       

      

(b)Whoami测试程序

图18  测试程序实现

我认为在此基础上,测试程序还可以利用系统函数的返回值来进行一些逻辑处理,进而提高程序鲁棒性,比如用flag值存储函数返回值,如果值为-1,提示错误。

int flag = Iam(argv[1]);

    if (res == -1) {

        perror("Error in Iam");

}而在图18中,我编写了两个处理逻辑较为简单的测试程序。

图19  测试结果展示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值