跳转至

实验一

创建实验私有仓库

你需要创建一个 GitHub 账号,并且创建一个私有(private)的仓库(repository),并命名为 osh-2022-labs。 请务必确保以上操作被准确无误地完成——由于一部分实验测评会使用自动化工具,如不能与上述一致,可能会影响你的实验分数。

在这个私有仓库中,你需要将助教的账号 osh-2022-ta 添加为你的 collaborator。 添加后 GitHub 会向助教发送邀请,之后再向三位助教之一发送你的姓名、学号、GitHub 账号名来让助教确认邀请。

在这个仓库下,每一次实验(实验一、实验二等等)会使用根目录下的一个名为 lab<x> 的文件夹来存放实现提交文件,例如:

  • 实验一的提交文件放在 lab1 文件夹下;
  • 实验二的提交文件放在 lab2 文件夹下;
  • 依次推进。

后续每次实验中也会再次强调提交文件的存放路径。

参考资料:

学习 Git

尽管 GitHub Web Panel 提供一部分编辑功能,为了在之后的实验中获得基本的操作体验,请同学们参考一下链接初步学会使用 Git:

可以重点关注这些子命令:init、add、commit、log、remote、push、pull、clone、branch、checkout、merge

Linux 编译

接下来实验一就算是正式开始了。

在编译源码前,我们需要先安装编译所需的依赖:

sudo apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev

kernel.org 上可以下载到 Linux 内核的源代码。 此次实验,我们选择最新的稳定版,Linux 5.16.17 的内核进行编译。

请在 AMD64 架构下的 Linux 环境中进行编译。如果你使用的设备是 Apple M1 系列、架构为 ARM,请及时联系助教进行特殊安排。

下载好 linux-5.16.17.tar.xz 文件后,解压缩为文件夹 linux-5.16.17,进入后创建默认配置:

如果 Linux 5.16.17 已不是最新版本,可以直接选用最新版本。要点在于使用较新的 5.1x 版本来完成实验。

make defconfig

然后使用下列命令之一(推荐前两者),对 Linux 内核进行修改:

make xconfig
make menuconfig
make gconfig
make nconfig

初次编译可以不进行修改,体验编译的过程。

最后进行编译,执行:

make

如果你知道自己的设备有多少 CPU 核心(虚拟核心数,或者说 CPU 线程数,可以自行搜索超线程了解含义和区别),可以使用:

make -j <cores>

<cores> 可以自行调节,不大于上述 CPU 虚拟核心数较佳。

编译完成后可以使用 QEMU 进行测试:

qemu-system-x86_64 -kernel linux-5.16.17/arch/x86_64/boot/bzImage

重复上述的修改和编译过程,即可进行对于 Linux 内核的删减。

评分标准

  • 提交编译好的内核文件 bzImage,保证其能够完成后续实验——1 分
  • bzImage 文件大小小于 7MiB——1 分
  • bzImage 文件大小小于 6MiB——1 分
  • bzImage 文件大小小于 4MiB——1 分

提示

此次实验不是调参比赛,满分可以在只修改前两层选项的情况下轻松达成,请注意哪些选项是能够真正地、切实地影响编译后的内核大小的。

此环节同时会减少内核编译时间,使得后续浪费在编译上的时间减少。

文件路径

  • 编译好的内核文件:置于 /lab1/bzImage
  • 编译内核时的配置文件:置于 /lab1/.config(可选)

创建初始内存盘

Linux 在启动时,会首先加载初始内存盘(initrd,init ram disk)进行初始化的操作。 下面我们讲解如何创建一个最小化的 initrd。

我们首先创建一个 C 程序,代码如下:

#include <stdio.h>

int main() {
    printf("Hello, Linux!\n");
    while (1) {}
}

更新return 0; 被换为了 while (1) {}

保存为 init.c。

之后编译,静态链接为可执行程序:

gcc -static init.c -o init

创建一个新的目录用于暂存文件。 在新的目录下打包 initrd:

find . | cpio --quiet -H newc -o | gzip -9 -n > ../initrd.cpio.gz

这会在目录外创建压缩后的 initrd.cpio.gz 初始内存盘。

同样,我们使用 QEMU 测试效果:

qemu-system-x86_64 -kernel linux-5.16.17/arch/x86_64/boot/bzImage -initrd initrd.cpio.gz

当你在屏幕上看到 "Hello, Linux!" 的时候,就成功了。 如果你看不清输出的信息,又发现无法上翻,可以使用以下指令:

qemu-system-x86_64 -kernel linux-5.16.17/arch/x86_64/boot/bzImage -initrd initrd.cpio.gz -nographic -append console=ttyS0

注意此命令执行后 Ctrl+C 无法终止,需要关闭 Terminal 或者 kill 对应进程。

更新:注意:init 将会作为第一个用户态进程被启动,成为所有后续进程的父进程。此进程如果退出会导致 kernel panic。 如果你遇到了 kernel panic,又认为自己的操作没有问题,不妨使用上方指令查看 log 来确认一下。

评分标准

  • 提交编译好的初始内存盘 initrd.cpio.gz,保证其能够显示 "Hello, Linux!"——2 分

文件路径

  • 编译好的初始内存盘文件:置于 /lab1/initrd.cpio.gz

添加一个自定义的 Linux syscall

本节我们将为 Linux 添加一个自定义的 syscall,来获得一串字符串 "Hello, world!\n"。

首先回到 linux 源码文件夹 linux-5.16.17,添加一个文件 custom/hello.c

#include <linux/string.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>

SYSCALL_DEFINE2(hello, char *, buf, size_t, buf_len)
{
    static const char s[] = "Hello, world!\n";
    if (strlen(s) <= buf_len) {
        return copy_to_user(buf, s, sizeof(s));
    } else {
        return -1;
    }
}

SYSCALL_DEFINE2 就是一个函数定义,此处能够很方便地同时为 32 bit 和 64 bit 声明对应函数。

此处可以直接使用 memcpy(内核态可以解引用用户态指针),不过 copy_to_user 可以协助校验目标地址是否可写。

接下来我们将此文件添加到 Linux 的编译工具链中。 打开 Makefile 文件,找到 kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ 所在的行,将 custom/ 也添加到此列表中,变为:

<...>
core-y                 += kernel/ certs/ mm/ fs/ ipc/ security/ crypto/ custom/
<...>

再在 custom 下创建文件 Makefile,内容仅为:

obj-y := hello.o

之后编译时就会将我们自行添加的 custom/hello.c 文件纳入到编译和链接中了。

最后来解决 syscall 的部分。 既然我们的架构是 x86_64(或者说 amd64,兼容 x86),打开 arch/x86/entry/syscalls/syscall_64.tbl syscall 表文件在最后一行添加一个新 syscall:

<...>
548    common  hello   sys_hello

记住这个数字 548,之后我们会用此数字来进行调用。

然后在 include/linux/syscalls.h 中添加一行声明:

// <...>
asmlinkage long sys_hello(char *buf, int buf_len);
// <...>

接下来重新编译内核,syscall 就添加完成了。

我们可以用一个 initrd 程序来进行测试。 请自行查询 syscall Linux 文档,编写程序测试以上自行添加的自定义 syscall,使得提供的 buffer 长度在充足和不足时均能输出希望的结果:

  • 长度充足时,返回 0,buffer 中存放完整的 "Hello, world!\n"
  • 长度不充足时,返回 -1

评分标准

  • 提交编译好的内核 bzImage,保证能够在 buffer 长度充足时完成 syscall——2 分
  • 编译好的内核 bzImage 也能保证在 buffer 长度不充足时完成 syscall——2 分
  • 提交测试 syscall 的 initrd 源代码文件——2 分

文件路径

  • 编译好的内核文件:置于 /lab1/syscall/bzImage
  • 测试 syscall 的源代码文件 /lab1/syscall/initrd.c

DDL 与总评分标准

此次实验截至时间为 4 月 16 日 23:59,以 commit 时间为准。

本次实验满分 10 分,由于所有可获得分数为 12 分,超出 10 分的分数会作为加分被计入实验总分。

答案/助教设计资源

参见 OSH-2022/lab1-ta