操作系统原理的上机实验的报告一共有四个,其他的报告的地址是:
第一次:https://shiraha.cn/2020/class-FoDOS-experiment-1/
第二次:https://shiraha.cn/2020/class-FoDOS-experiment-2/
第三次:https://shiraha.cn/2020/class-FoDOS-experiment-3/
第四次:https://shiraha.cn/2020/class-FoDOS-experiment-4/
Front-matter
本次实验的所有源代码可以在https://dev.azure.com/Pure-Asahi/_git/2020_Spring_In_Class_Job?path=%2Fosnmb%2Fexp 查看到。
实验要求
本次上机实验的实验要求如下:
《操作系统原理》第一次上机实验 一、实验目的
- 理解操作系统生成的概念和过程;
- 理解操作系统两类用户界面(操作界面,系统调用)概念;
二、实验内容
- 在 Ubuntu 或其他 Linux 平台环境下裁剪和编译 Linux
内核,并启用新的内核。- 在 Ubuntu 或其他 Linux 平台为 Linux 内核增加 1-3 个新
的系统调用,并启用新的内核,编写一个应用程序测试新增加的系
统调用是否能正确工作。- 在 Windows 环境下,编写一个批处理程序(算命大
师.bat),程序运行后,输入:出生年月日(例如 2000-07-31)。系
统输出相应的属相和星座,例如:你属兔, 狮子座。要求:输入进
行合法性检查,能循环接收用户的输入,直到输入 q 或 Q 就退出。
要求一、二在上机前自己做过了,本次实验报告只简述实验过程和贴图。要求三完成了简单的 Bat 版本和 Powershell 的复杂版本。
实验内容
- 在 Ubuntu 或其他 Linux 平台环境下裁剪和编译 Linux
内核,并启用新的内核。 - 在 Ubuntu 或其他 Linux 平台为 Linux 内核增加 1-3 个新
的系统调用,并启用新的内核,编写一个应用程序测试新增加的系
统调用是否能正确工作。 - 在 Windows 环境下,编写一个批处理程序(算命大
师.bat),程序运行后,输入:出生年月日(例如 2000-07-31)。系
统输出相应的属相和星座,例如:你属兔, 狮子座。要求:输入进
行合法性检查,能循环接收用户的输入,直到输入 q 或 Q 就退出。
第一、第二个实验的内容原文来自本博客:https://shiraha.cn/2019/Linux-FoDOS-experiment-dian/,选择的 Linux 平台是 Arch Linux。
第三个实验的内容原文来自本博客:https://shiraha.cn/2020/class-FoDOS-experiment-bat-programming/,这篇文章只包括了 Powershell 的脚本的实验过程。
实验过程
下面是对于每一个任务的原理和过程的简述:为了遵循实际实验的最佳顺序,讲述顺序为实验二、实验一、实验三(实验二的完成本身需要实验一的工作
增加系统调用
总的来说,增加最简单的系统调用主要分为 增加新函数 -> 增加新声明 -> 将新调用增加到系统调用注册表 三个步骤。复制一份下载的纯净内核文件,修改其中的include/uapi/asm-generic/unistd.h
和include/linux/syscalls.h
两个头文件来增加系统调用的函数。
进入系统调用注册表的目录arch/x86/entry/syscalls
,根据要安装的操作系统位数修改其下的tbl
文件,注册增加的系统调用。特别注意不应该与已经存在的系统调用的编号冲突,这点在后面增加宏的时候再次重复。
使用vim打开系统调用的实现的源文件kernel/sys.c
来增加刚才增加的函数的实现。如果要增加的是在内核缓冲区打印消息这种最简单的系统调用,则它可以是下面这样:
1 | asmlinkage void sys_shirohashow(void) |
这样,我们就增加了一个系统调用函数sys_shirohashow
的实现。完成之后还要在include/uapi/asm-generic/unistd.h
增加宏。这个系统调用的含义是在内核缓冲区(用户程序不可以实现的功能)打印一条消息,需要使用dmesg -c
命令清除所有缓冲区日志来查看。
dmesg
命令主要有下面三个用途:
- 列出所有检测到的硬件
- 输出指定行数的日志
- 清空dmesg缓冲区日志
这里主要就是使用了第三个功能,用来检查printk
的输出结果。
此外,上述函数体的修饰符asmlinkage
宏确保了这个函数使用堆栈传递参数;所有的系统调用函数都应该使用这个进行修饰。
添加完实现之后还需要在unistd.h
文件中增加这个函数的声明。这个文件在 Arch 中的位置是include/uapi/asm-generic/unistd.h
;我们使用 Vim 打开它并在末尾增加:
1 |
|
765 是这个系统调用的编号,选取它的原因是因为 346/283 被其他系统调用占用了而它没有。需要确认一个系统调用编号是否已经被占用,可以打开系统调用注册表进行确认(比如syscall_64.tbl
,可以使用find
指令找到这个文件所在目录)。
还有一处需要增加声明的地方是include/linux/syscalls.h
;打开这个文件并且增加标准的 C 函数声明:
1 | asmlinkage void sys_shirohashow(void); |
这个文件应该有注释表明了这个函数实现所在的位置,建议遵循规则增加到注释kernel/sys.c
的区域。
将修改过的内核和纯净内核使用diff -Naur
生成差异补丁,再使用patch
命令应用到纯净内核上。使用make -j6
进行多线程编译。当然你也可以直接在修改的内核上直接编译,patch 一次纯属娱乐,没太多实际意义。当然也可以作为对于patch
指令的熟悉。
编译成功后执行安装,并将必要的文件复制到特定的地方之后更新grub的引导信息,就完成了内核的应用。应用新内核将会在下一个任务的实验过程中较为详细的介绍。
应用新内核之后,在运行新内核的操作系统上写C程序,调用增加的系统调用,就可以看到我们在内核中编写的程序可以成功运行了。随后通过一些方法(对于printk
,就是使用dmesg -c
查看内核缓冲区)验证实验结果。对于上述步骤增加的系统调用,提供一个示例 C 程序用于测试:
1 |
|
编译后执行,应该就可以在内核缓冲区中看到输出的鼓励话语了。
编译内核
实验背景:使用 Hyper-V (第二代) 虚拟机平台,安装 Arch Linux。已经下载好了对应版本的 Linux 官方的纯净内核源代码文件。
从官网上下载的内核已经提供了编译的配置文件,甚至还提供了一个比较图形化的menuconfig
页面,提供了较为可视化的 .config 文件的生成方法。因为我们只是在一般的 PC 上安装新 Linux 内核,所以不需要进行特别的裁剪和配置,使用缺省配置就好:
1 | zcat /proc/config.gz > .config |
复制得到 .config 文件之后还建议修改文件中的CONFIG_LOCALVERSION
的值,避免将要编译的内核文件覆盖当前内核文件。生成了 config 文件之后就可以使用make -jx
开始多线程编译了。建议 x 取值和计算机所持有的物理核心数相同以获得最大编译速度。
安装新编译生成的新内核的步骤可以简单归纳为:安装内核模块 -> 复制内核文件 -> 制作initramfs镜像 -> 复制System.map -> 生成新的启动引导。
安装内核模块可以使用下面的命令:
1 | sudo make modules_install |
这条命令会将编译好的模块安装到主目录 /lib/modules 下。这样,会使得这些模块独立于虚拟机原有内核的模块。
复制内核文件可以使用下面的命令:
1 | # 对于32位(i686)内核: |
将内核编译完成生成的bzImage(较大的压缩的内核映像,使用gzip压缩)文件复制到/boot
目录下。
制作initramfs(初始内存盘)镜像可以通过复制并且修改mkinitcpio(一个创建initramfs的脚本)preset,这样就可以通过官方内核一样的方式生成自定义内核的initramfs镜像。复制之后需要使用vim修改这个文件。
1 | sudo cp /etc/mkinitcpio.d/linux.preset /etc/mkinitcpio.d/linux53.preset |
打开linux53.preset文件之后,修改部分字段使得它与新的自定义内核所匹配。需要修改的字段如下:
1 | # /etc/mkinitcpio.d/linux53.preset |
修改并保存之后执行sudo mkinitcpio -p linux53
就可以使用官方内核生成的方式生成自定义内核的initramfs镜像。
复制System.map的步骤可能不是必须的。如果虚拟机的/boot挂载到的分区的文件系统是ext4
格式。就需要将解压目录下的System.map文件复制到/boot中,并且创建/boot/System.map,将新建的System.map软链接到复制到其中的System.map中。需要执行的命令如下:
1 | sudo touch /boot/System.map |
但是由于本次使用的虚拟机的 boot 分区挂载的分区是vfat
文件系统的,无需也不必创建软链接;所以本次上机实验中这一步实际并没有去做。
最后需要更新更新 grub 引导的配置信息。在安装了 grub 之后,使用命令grub-mkconfig -o /boot/grub/grub.cfg
可以生成grub默认的配置信息。它会自动地将刚添加的内核增加到启动配置中。在Ubuntu这种系统中,这个命令被包装成了update-grub
或其他形式。因为本次实验使用的是 Arch 发行版,所以需要完整的命令。
最后使用 reboot
指令重启虚拟机,通过安装的 screenfetch
工具来获取当前的系统信息,就可以发现系统的内核信息已经更新了,实验完成。在 Arch 中,需要使用系统默认的包管理工具 pacman
安装 screenfetch
。
脚本编写
分析实验要求,绘制程序执行流程图:
接下来就是使用一些具体的脚本语言实现这个设计了。
对于 Powershell 脚本,可以使用的“库”有很多,实现也就可以更加的花哨一些。单就读取用户输入方面们就有很多的方案可供选择:可以使用Powershell原生的Read-Host
,也可以使用框架提供的GUI窗口;甚至还可以使用 Visual Basic 的窗口。
得益于 Powershell 脚本功能的强大,我们可以定义一个函数完成对于日期合法性的判断。最后根据用户输入的日期进行一些处理,得到输出的字符串即可。为了节约篇幅,这里省略了一些预先声明的,和处理日期生成输出字符串相关的一些变量的初始化(以省略号代替)。使用框架提供的输入窗口获取用户输入的源代码如下:
1 | Add-Type -AssemblyName System.Windows.Forms |
使用原生的Read-Host
获取用户输入也是一种选择;下面是使用Read-Host
获得用户输入的部分代码(和上面代码不同的部分):
1 | while(1) |
上述代码如果对于 Powershell 不甚了解的话则难以理解其中的一些部分,这是因为 Powershell 基于 .NET 框架,提供了很多 Windows 的原生接口。如果是相对低级的 bat 批处理程序,代码就会更加的简洁易懂。下面的代码是使用 bat 脚本语法书写的“算命大师”。和上面的 ps1 脚本不同,仅执行任务要求的星座、属性计算(使用分支语句实现),并且没有复杂的日期判断:
1 | @echo off |
bat可以取出字符串的一部分为变量赋值,然后再对变量的值进行判断。运行之后应该就可以看到结果了。
实验结果
这里是任务完成后的截图或其他证明。
任务一:增加系统调用
执行实验过程中的实例代码,使用dmesg -c
命令可以获得以下输出:
可以看到已经在内核缓冲区输出了系统调用中的字符串了。
任务二:安装新内核
下图是安装新内核之后使用screenfetch
命令看到的输出:
内核版本已经由默认的 5.3.4 变成了 5.3.10,说明新内核已经成功使用了。
任务三:脚本编程
使用Read-Host
的 Powershell 脚本:
使用 .NET 框架提供窗口的 Powershell 脚本:
输入内容后会得到和第一张图一样的输出。需要特别注意的是:一般的家用版本的 Windows 10 自带的 Powershell 是不允许直接加载ps1
脚本的,需要先行修改运行策略。修改的脚本如下:
1 | Set-ExecutionPolicy -Scope CurrentUser RemoteSigned |
最后是 BAT 批处理脚本“算命大师”的运行结果:
可以看到基本功能已经实现并且正常工作。
体会
通过这次实验,我熟悉了Linux内核的应用过程,对Linux命令的理解更进一步;更加生动的理解了系统调用的概念以及Linux操作系统从POST开始之后的启动过程;熟悉了 Powershell 脚本编程,以及基于这项技术的简单开发和部分 .NET API 的使用;
对于受众较小的 Arch Linux 操作系统,比起在网络上漫无目的的查找论坛、博客,不如认真研读官方文档的 trouble-shooting 以及对一些问题可能原因的分析和解释。不仅可以对解决问题带来更加精准的帮助,还可以拓宽我们看待问题的视野。