从零构建Linux内核:SEU操作系统实验环境搭建全攻略

在操作系统课程中,动手修改和编译Linux内核是理解计算机系统底层原理的重要实践。对于东南大学(SEU)操作系统课程的学生而言,实验一要求实现进程隐藏相关的系统调用,这需要先搭建一个可以自由修改内核的环境。本文将带你完整走一遍从获取内核源码到成功编译安装的全过程,并针对实验一中可能遇到的坑点提供解决方案。

1. 实验环境准备

工欲善其事,必先利其器。在开始内核编译前,我们需要准备好基础环境。推荐使用Ubuntu 20.04 LTS或Fedora作为开发系统,这两个发行版对内核开发支持较好。

首先安装必要的编译工具链:

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

对于Fedora系统,使用以下命令:

sudo dnf groupinstall "Development Tools"
sudo dnf install ncurses-devel bison flex elfutils-libelf-devel openssl-devel

接下来,我们需要为内核编译预留足够的空间。建议至少有20GB的可用空间。可以通过以下命令检查:

df -h

如果空间不足,可以考虑挂载额外的存储设备或清理不必要的文件。

2. 获取与配置内核源码

Linux内核源码可以从官方仓库获取。为了与SEU实验要求保持一致,建议使用5.4.x版本的内核:

wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-5.4.211.tar.xz
tar xvf linux-5.4.211.tar.xz
cd linux-5.4.211

解压后,我们需要配置内核选项。可以使用现有配置作为基础:

cp /boot/config-$(uname -r) .config
make oldconfig

对于实验一的要求,我们需要确保以下配置项已启用:

CONFIG_MODULES=y
CONFIG_MODULE_UNLOAD=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_ALL=y

可以使用menuconfig界面进行更详细的配置:

make menuconfig

在menuconfig界面中,使用方向键导航,空格键切换选项状态([*]表示编译进内核,[M]表示编译为模块,[ ]表示不编译)。

3. 内核编译与安装

配置完成后,就可以开始编译内核了。编译过程可能会持续数小时,取决于你的硬件性能。使用-j参数可以加速编译,其后的数字建议设置为CPU核心数的1-2倍:

make -j$(nproc)

编译完成后,安装内核模块:

sudo make modules_install

然后安装内核本身:

sudo make install

这一步骤会将内核映像和相关文件复制到/boot目录下,并更新grub配置。

4. 系统启动配置与内核切换

安装完成后,我们需要配置系统以使用新编译的内核。这是SEU实验一中特别容易出错的地方。

首先更新grub配置:

sudo update-grub

重启系统时,需要特别注意选择正确的内核版本。在GRUB菜单出现时:

  1. 快速按下Shift键(对于传统BIOS系统)或Esc键(对于UEFI系统)进入菜单
  2. 使用方向键选择我们刚编译的内核版本(通常标有版本号)
  3. 按Enter启动

常见问题排查:

问题:重启后系统仍使用旧内核

解决方案:

  • 确认make install步骤没有报错
  • 检查/boot目录下是否有新内核的相关文件
  • 确认GRUB配置中包含了新内核项

问题:新内核启动失败

解决方案:

  • 尝试在GRUB菜单中编辑启动参数,添加single或init=/bin/bash进入单用户模式
  • 检查编译日志是否有警告或错误
  • 考虑回退到之前的内核版本并重新检查配置

验证当前运行的内核版本:

uname -r

应该显示我们编译的版本号(如5.4.211),而不是发行版默认的内核。

5. 实验一系统调用实现

有了可修改的内核环境,我们就可以开始实验一的系统调用实现了。首先需要定位到内核源码中系统调用相关的文件:

  1. 系统调用表:arch/x86/entry/syscalls/syscall_64.tbl
  2. 系统调用声明:include/linux/syscalls.h
  3. 系统调用实现:通常放在kernel/目录下

以hide系统调用为例,我们需要:

  1. 在syscall_64.tbl中添加新的系统调用号
  2. 在syscalls.h中声明函数原型
  3. 创建新的实现文件或修改现有文件添加功能

实现hide系统调用的基本框架:

SYSCALL_DEFINE2(hide, pid_t, pid, int, on)
{
    struct task_struct *task;
    
    // 权限检查
    if (!capable(CAP_SYS_ADMIN))
        return -EPERM;
    
    // 获取任务结构体
    rcu_read_lock();
    task = find_task_by_vpid(pid);
    if (!task) {
        rcu_read_unlock();
        return -ESRCH;
    }
    get_task_struct(task);
    rcu_read_unlock();
    
    // 实现隐藏逻辑
    if (on) {
        set_hidden_flag(task);
    } else {
        clear_hidden_flag(task);
    }
    
    put_task_struct(task);
    return 0;
}

完成修改后,需要重新编译并安装内核:

make -j$(nproc)
sudo make modules_install
sudo make install

6. 测试与验证

内核重新启动后,可以编写测试程序验证系统调用是否正常工作。创建一个简单的测试程序hide_test.c:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/syscall.h>

#define __NR_hide 333  // 与syscall_64.tbl中分配的系统调用号一致

int main(int argc, char **argv)
{
    if (argc != 3) {
        printf("Usage: %s <pid> <on>\n", argv[0]);
        return 1;
    }
    
    pid_t pid = atoi(argv[1]);
    int on = atoi(argv[2]);
    
    long ret = syscall(__NR_hide, pid, on);
    if (ret < 0) {
        perror("hide syscall failed");
        return 1;
    }
    
    printf("hide syscall succeeded\n");
    return 0;
}

编译并测试:

gcc hide_test.c -o hide_test
./hide_test <pid> 1  # 隐藏进程
./hide_test <pid> 0  # 取消隐藏

可以通过ps aux | grep <pid>或top命令观察进程是否被成功隐藏。

7. 实验一进阶实现

对于实验一中的进阶要求,如hide_user_processes系统调用和/proc文件系统接口,我们需要更深入的内核知识。

实现hide_user_processes系统调用时,需要考虑:

  1. 用户空间与内核空间的数据传输
  2. 进程遍历与条件筛选
  3. 并发安全性

而/proc文件系统的实现则需要:

  1. 创建proc文件入口
  2. 实现文件操作函数(open, read, write, release等)
  3. 维护内核数据结构与用户空间的交互

一个简单的/proc/hidden实现示例:

static int hidden_flag = 1;

static ssize_t hidden_read(struct file *file, char __user *buf,
                          size_t count, loff_t *ppos)
{
    char tmp[32];
    int len;
    
    len = snprintf(tmp, sizeof(tmp), "%d\n", hidden_flag);
    return simple_read_from_buffer(buf, count, ppos, tmp, len);
}

static ssize_t hidden_write(struct file *file, const char __user *buf,
                           size_t count, loff_t *ppos)
{
    char tmp[32];
    int val;
    
    if (count >= sizeof(tmp))
        return -EINVAL;
    
    if (copy_from_user(tmp, buf, count))
        return -EFAULT;
    
    tmp[count] = 0;
    if (kstrtoint(tmp, 10, &val))
        return -EINVAL;
    
    if (val != 0 && val != 1)
        return -EINVAL;
    
    hidden_flag = val;
    return count;
}

static const struct proc_ops hidden_fops = {
    .proc_read = hidden_read,
    .proc_write = hidden_write,
};

static int __init hidden_init(void)
{
    proc_create("hidden", 0666, NULL, &hidden_fops);
    return 0;
}

static void __exit hidden_exit(void)
{
    remove_proc_entry("hidden", NULL);
}

module_init(hidden_init);
module_exit(hidden_exit);

8. 常见问题与解决方案

在SEU操作系统实验一中,学生们常遇到以下问题:

  1. 内核编译失败

    • 确保安装了所有必要的依赖包
    • 检查系统是否有足够的磁盘空间和内存
    • 尝试减少并行编译任务数(如使用make -j2)
  2. 系统调用无法使用

    • 确认系统调用号在syscall_64.tbl中的定义
    • 检查系统调用声明是否正确定义
    • 确保测试程序使用了正确的系统调用号
  3. 进程隐藏效果不稳定

    • 检查任务结构体的获取和释放是否正确
    • 确保没有竞争条件
    • 验证权限检查逻辑是否完备
  4. /proc文件无法访问

    • 检查文件权限设置
    • 确认proc文件操作函数实现正确
    • 确保模块正确加载(如果是模块方式实现)
  5. 系统启动后功能异常

    • 确认启动的是新编译的内核
    • 检查内核日志(dmesg)是否有相关错误信息
    • 验证内核配置选项是否满足需求

对于实验环境搭建,建议在虚拟机中进行,这样出现问题时可以方便地回滚到之前的状态。同时,定期备份重要文件和工作进度,避免因系统崩溃导致工作丢失。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐