news 2026/10/13 16:11:34

广工大OS实验:基于Linux 2.6内核的进程调度、内存管理与文件系统实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
广工大OS实验:基于Linux 2.6内核的进程调度、内存管理与文件系统实战

简介:本资源是广东工业大学操作系统课程配套的完整实验实践包,面向计算机专业本科生及操作系统初学者,聚焦进程调度、作业调度、主存管理与文件系统四大核心模块,助力理论知识向底层实现能力转化。压缩包共12个文件,含4个C语言源码(用于模拟FCFS、RR等调度算法及内存分配策略)、4个可执行程序(便于快速验证逻辑)、3份Word实验报告(含打印版与反馈表,结构规范、内容详实)、1个DAT数据文件(支撑文件系统实验的磁盘映像或测试数据),整体仅1.65MB,轻量易用。已有474人学习下载,资源由学生作者syxiong43整理,覆盖从代码编写、算法模拟到报告撰写的全流程,特别适合课程实验复现、期末复习巩固及操作系统原理深度理解。

1. 广东工业大学操作系统实验:不是抄报告的流程,而是亲手把进程调度、内存管理、文件系统“焊”进内核的硬核训练

如果你以为这门课只是照着实验手册敲几行 C 代码、改几个printf、截图交差——那等你第一次在fork()后看到子进程卡死在wait()、或在mmap()映射区写入数据却触发SIGSEGV、又或者用open()打开一个根本不存在的设备节点却返回0而不是-1时,就会明白:这根本不是“模拟”,而是一场对 Linux 内核行为边界的实弹测绘。广东工业大学操作系统实验的核心目标,是让本科生在Linux 2.6.x 系列稳定内核(主流采用 2.6.32 或 2.6.39)上,通过修改真实内核模块、重写用户态系统调用桩、调试中断上下文切换逻辑,把课本里抽象的“进程控制块 PCB”、“页表项 PTE”、“VFS 超级块 super_block”变成可打印地址、可断点跟踪、可内存 dump 验证的实体对象。它不考背诵,只考你能不能在dmesg日志里一眼认出BUG: scheduling while atomic的真正诱因;它不验收“运行成功”,只验收cat /proc/sched_debug输出中你自定义调度器的nr_switches是否随负载线性增长。适合那些已经写过链表、理解指针算术、能看懂objdump -S反汇编、且愿意为一行asm volatile("cli")调试三小时的人。


2. 从零构建实验环境:用 QEMU + BusyBox 搭建可调试、可快照、可复位的纯净内核沙箱

广东工业大学操作系统实验对环境的要求非常明确:不能依赖宿主机内核,不能污染开发机,必须支持内核源码级调试与任意时刻回滚。这意味着直接在 Ubuntu 主机上make modules_install是高危操作,而 Docker 容器因缺乏内核模块加载能力完全不可用。我们采用 QEMU-KVM 虚拟化方案,配合精简版 BusyBox 根文件系统,构建一个仅含init、sh、ls、cat等基础命令的轻量沙箱。该方案被某高校实验室连续五年用于本科教学,平均单次实验环境部署耗时控制在 12 分钟以内,且支持快照(snapshot)机制,学生可在sched.c修改崩溃后一键还原至初始状态。

2.1 下载并解压指定内核源码包(以 2.6.32 为例)

实验指导书明确要求使用linux-2.6.32.tar.gz(非最新版),因其 ABI 稳定、补丁生态成熟、且与实验配套的lab-sched.patch兼容性最佳。注意:不要用git clone获取主线内核,版本错位将导致struct task_struct偏移量不一致,引发模块加载后立即 oops。

wget https://mirrors.edge.kernel.org/pub/linux/kernel/v2.6/linux-2.6.32.tar.gz tar -xzf linux-2.6.32.tar.gz cd linux-2.6.32

提示:解压后目录大小约 480MB,建议预留 2GB 空间。若下载慢,可用国内镜像站如清华 TUNA(https://mirrors.tuna.tsinghua.edu.cn/kernel/v2.6/)替代。

2.2 配置最小化内核(.config):裁掉所有无关驱动,只留实验必需项

实验聚焦进程调度、内存管理、文件系统三大模块,其余如声卡、USB、网络协议栈(除 loopback 外)全部禁用。关键配置项如下(使用make menuconfig后保存):

配置项值说明
CONFIG_EXPERIMENTALy启用实验性功能,部分调度器 patch 依赖此开关
CONFIG_SCHED_DEBUGy必开!用于cat /proc/sched_debug观察调度行为
CONFIG_PROC_FSy/proc文件系统是观察内核状态的唯一窗口
CONFIG_BLK_DEV_LOOPmloop 设备模块,用于挂载根文件系统镜像
CONFIG_EXT2_FSy实验默认使用 ext2,无 journal 开销,便于分析磁盘 I/O
CONFIG_MODULE_UNLOADy允许rmmod卸载自定义模块,避免重启
CONFIG_DEBUG_KERNELy启用内核调试符号,gdb调试必备

执行配置命令:

make mrproper make defconfig # 先加载默认配置 make menuconfig # 进入图形界面,按上表修改 make -j$(nproc) # 编译内核(约需 8~15 分钟)

编译完成后,生成两个关键文件:

  • arch/x86/boot/bzImage:可启动的压缩内核镜像
  • vmlinux:带完整调试符号的未压缩内核(gdb调试必需)

2.3 构建 BusyBox 根文件系统镜像(initramfs)

实验不使用硬盘分区,而是将根文件系统打包为 initramfs(cpio 归档),由内核在启动时直接解压到内存。BusyBox 是唯一被允许的用户态工具集,因其体积小、源码透明、易于定制。

# 下载并编译 BusyBox 1.20.2(与 2.6.32 内核 ABI 匹配) wget https://busybox.net/downloads/busybox-1.20.2.tar.bz2 tar -xjf busybox-1.20.2.tar.bz2 cd busybox-1.20.2 make defconfig make menuconfig # 确保 'Settings' → 'Build Options' → 'Build static binary (no shared libs)' 为 y make -j$(nproc) make install # 默认安装到 _install/ 目录 # 构建 initramfs 目录结构 cd .. mkdir -p initramfs/{bin,sbin,etc,proc,sys,dev} cp -a busybox-1.20.2/_install/* initramfs/ # 创建 init 脚本(关键!决定系统启动后执行什么) cat > initramfs/init << 'EOF' #!/bin/sh mount -t proc none /proc mount -t sysfs none /sys echo "Welcome to GDUT OS Lab!" exec /bin/sh EOF chmod +x initramfs/init # 打包为 cpio cd initramfs find . | cpio -o -H newc > ../rootfs.cpio cd ..

此时得到rootfs.cpio,即实验用根文件系统镜像。

2.4 启动 QEMU 并验证环境可用性

使用以下命令启动虚拟机,关键参数说明:

  • -kernel: 指定编译好的bzImage
  • -initrd: 加载rootfs.cpio
  • -S -s: 启用 GDB 调试(暂停启动,等待 gdb 连接)
  • -append "console=ttyS0",-serial stdio: 将内核日志输出到终端
  • -monitor stdio: 启用 QEMU monitor,可动态查看 CPU/内存状态
qemu-system-x86_64 \ -kernel linux-2.6.32/arch/x86/boot/bzImage \ -initrd rootfs.cpio \ -S -s \ -append "console=ttyS0" \ -serial stdio \ -monitor stdio \ -m 512M \ -nographic

启动后,QEMU 会暂停在第一条指令处。此时另开终端,连接 GDB:

# 在 linux-2.6.32/ 目录下执行 gdb vmlinux (gdb) target remote :1234 (gdb) continue

GDB 连接成功后输入continue,虚拟机继续启动,最终进入sh提示符。输入cat /proc/version应显示Linux version 2.6.32,证明环境搭建成功。


3. 进程调度实验:实现一个基于优先级队列的 EDF(最早截止时间优先)调度器

广东工业大学操作系统实验的调度模块,明确要求绕过 CFS(Completely Fair Scheduler),在kernel/sched_fair.c之外新建kernel/sched_edf.c,实现一个符合实时性语义的 EDF 调度器。EDF 的核心逻辑是:每个任务携带deadline(绝对截止时间),调度器总选择deadline最早的就绪任务运行。这不是理论推演,而是要你在pick_next_task()中精确计算rq->edf_queue的最小堆顶,并在enqueue_task()中维护堆结构。实验验收标准是:当两个周期性任务(周期 10ms/20ms,执行时间 3ms/5ms)同时就绪时,/proc/sched_debug中edf_rq的nr_tasks必须实时反映就绪队列长度,且next_deadline字段随调度严格递增。

3.1 修改内核调度框架:注册新调度类并劫持调度路径

Linux 内核通过struct sched_class链表组织调度策略。我们要插入自己的edf_sched_class,并确保其优先级高于fair_sched_class(CFS)但低于rt_sched_class(实时)。关键修改点在kernel/sched.c:

// 在 kernel/sched.c 末尾添加 extern const struct sched_class edf_sched_class; // 声明外部调度类 // 在变量定义区(如 init_sched_fair_class() 之后)插入 const struct sched_class * const sched_class_highest = &edf_sched_class; // 注意:此行必须放在 fair_sched_class 和 rt_sched_class 之间

然后在kernel/sched_edf.c中实现调度类结构体:

// kernel/sched_edf.c #include <linux/sched.h> #include <linux/list.h> #include <linux/spinlock.h> // EDF 就绪队列:最小堆(按 deadline 排序) struct edf_rq { struct rb_root_cached root; // 使用红黑树模拟最小堆(Linux 内核无原生堆 API) unsigned int nr_tasks; u64 next_deadline; // 当前就绪队列中最早的 deadline }; // 全局 EDF 就绪队列(简化版,实际应 per-CPU) static DEFINE_SPINLOCK(edf_lock); static struct edf_rq edf_root; // 插入任务到 EDF 队列 static void enqueue_task_edf(struct rq *rq, struct task_struct *p, int flags) { struct edf_rq *edf_rq = &edf_root; unsigned long flags_irq; spin_lock_irqsave(&edf_lock, flags_irq); // 简化:直接插入红黑树,key = p->dl.deadline rb_insert_color(&p->dl.rb_node, &edf_rq->root.rb_root); edf_rq->nr_tasks++; if (edf_rq->nr_tasks == 1) edf_rq->next_deadline = p->dl.deadline; spin_unlock_irqrestore(&edf_lock, flags_irq); } // 选择下一个任务 static struct task_struct *pick_next_task_edf(struct rq *rq) { struct edf_rq *edf_rq = &edf_root; struct rb_node *leftmost; struct task_struct *p = NULL; unsigned long flags_irq; spin_lock_irqsave(&edf_lock, flags_irq); leftmost = rb_first_cached(&edf_rq->root); if (leftmost) { p = rb_entry(leftmost, struct task_struct, dl.rb_node); edf_rq->next_deadline = p->dl.deadline; } spin_unlock_irqrestore(&edf_lock, flags_irq); return p ? p : &idle_task; } // 调度类定义(必须完整实现 12 个函数指针) const struct sched_class edf_sched_class = { .next = &fair_sched_class, // 链接到下一个调度类 .enqueue_task = enqueue_task_edf, .dequeue_task = dequeue_task_edf, // 需自行实现 .pick_next_task = pick_next_task_edf, .put_prev_task = put_prev_task_edf, // 需自行实现 // ... 其余函数指针(共 12 个)均需 stub 或最小实现 };

逻辑说明:此处用红黑树替代最小堆,是因为内核已提供rbtree.h且性能足够。p->dl.deadline是我们为task_struct新增的字段(见下一步),存储任务的绝对截止时间。spin_lock_irqsave是必须的,因为调度器可能在中断上下文中被调用。

3.2 扩展 task_struct:增加 deadline 字段并初始化

EDF 调度器需要每个任务携带deadline。我们不能修改include/linux/sched.h中的struct task_struct(破坏 ABI),而是采用per-task 扩展机制:在task_struct末尾动态分配额外空间。但实验简化要求,直接在include/linux/sched.h中添加字段(教学环境允许):

// 在 include/linux/sched.h 中 struct task_struct 定义末尾添加 struct task_struct { // ... 原有字段 struct { u64 deadline; // 绝对截止时间(纳秒) u64 period; // 周期(纳秒),用于周期性任务 u64 runtime; // 单次执行时间(纳秒) struct rb_node rb_node; // 红黑树节点,用于 EDF 队列 } dl; };

并在kernel/fork.c的copy_process()函数中初始化:

// 在 copy_process() 中,dup_task_struct() 之后添加 p->dl.deadline = 0; p->dl.period = 0; p->dl.runtime = 0; RB_CLEAR_NODE(&p->dl.rb_node);

3.3 用户态接口:通过 /proc/sys/kernel/sched_edf_params 设置任务参数

内核模块不能直接读取用户输入,需通过/proc接口暴露控制点。我们在kernel/sched_edf.c中添加:

// 定义全局参数 static u64 edf_global_deadline = 0; static u64 edf_global_period = 10000000; // 10ms static u64 edf_global_runtime = 3000000; // 3ms // /proc 接口:写入 deadline 即设置当前进程 deadline static int edf_deadline_proc_do(struct ctl_table *table, int write, void __user *buffer, size_t *lenp, loff_t *ppos) { struct task_struct *p = current; if (write) { u64 val; if (*lenp < sizeof(val)) return -EINVAL; if (copy_from_user(&val, buffer, sizeof(val))) return -EFAULT; p->dl.deadline = ktime_get_ns() + val; // 转为绝对时间 p->dl.period = edf_global_period; p->dl.runtime = edf_global_runtime; } return 0; } // 注册 proc 控制项 static struct ctl_table edf_sysctl_table[] = { { .procname = "sched_edf_deadline", .data = &edf_global_deadline, .maxlen = sizeof(u64), .mode = 0644, .proc_handler = edf_deadline_proc_do, }, {} }; static struct ctl_table_header *edf_sysctl_header; static int __init edf_sched_init(void) { edf_sysctl_header = register_sysctl("kernel", edf_sysctl_table); return 0; }

编译后,用户可通过以下命令设置自身 deadline:

# 在 QEMU 中执行(需 root) echo 10000000 > /proc/sys/kernel/sched_edf_deadline # 设置 10ms 后截止

此时该进程将被加入 EDF 队列,pick_next_task_edf()会优先调度它。


4. 内存管理实验:实现一个基于 Buddy System 的简易物理页分配器

广东工业大学操作系统实验的内存管理模块,要求不使用内核默认的alloc_pages(),而是从头实现一个简化版 Buddy System 分配器,管理 16MB 物理内存(0x1000000 ~ 0x1100000),支持alloc_buddy_order(n)(分配 2^n 页)和free_buddy_page(page, order)。这不是封装调用,而是要你亲手操作页表、维护空闲链表、处理合并分裂逻辑。验收时,cat /proc/meminfo中BuddyFree字段必须准确反映各阶空闲页数,且连续分配/释放 4KB(order=0)、8KB(order=1)、16KB(order=2)页面后,dmesg日志中必须出现buddy: alloc 2^1 pages等调试信息。

4.1 初始化 Buddy System:划分内存区域并建立空闲链表数组

Buddy System 的核心是维护一个MAX_ORDER(通常为 11,对应 2^11=2048 页=8MB)的空闲链表数组free_area[MAX_ORDER]。每阶i的链表存放大小为2^i页的连续内存块。我们从物理地址0x1000000(16MB)开始管理 16MB 内存(即 4096 个页):

// mm/buddy_system.c #include <linux/mm.h> #include <linux/init.h> #include <linux/list.h> #define MAX_ORDER 11 #define START_PADDR 0x1000000 // 16MB #define MEM_SIZE (16UL * 1024 * 1024) // 16MB #define PAGE_SIZE 4096 #define NR_PAGES (MEM_SIZE / PAGE_SIZE) // 4096 pages struct free_area { struct list_head free_list; unsigned long nr_free; }; static struct free_area buddy_free_area[MAX_ORDER]; static struct page *buddy_pages; // 指向管理的物理页数组首地址 // 初始化:将整块内存按最大阶(order=MAX_ORDER)加入链表 static void __init buddy_init(void) { int i; unsigned long pfn_start = START_PADDR >> PAGE_SHIFT; // 起始 PFN struct page *page; // 分配 page 结构数组(用于描述管理的物理页) buddy_pages = memblock_virt_alloc(sizeof(struct page) * NR_PAGES, PAGE_SIZE); // 初始化所有 page 结构 for (i = 0; i < NR_PAGES; i++) { page = buddy_pages + i; INIT_LIST_HEAD(&page->lru); set_page_count(page, 0); // 初始引用计数为 0 __SetPageReserved(page); // 标记为内核保留页,防止被 alloc_pages 误用 } // 将整块内存(4096页 = 2^12 页)按 order=12 分配?但 MAX_ORDER=11,故拆为两个 2^11 块 // 起始地址 0x1000000,长度 16MB,可划分为两个 8MB 块(各 2048 页) for (i = 0; i < 2; i++) { page = buddy_pages + i * 2048; list_add_tail(&page->lru, &buddy_free_area[MAX_ORDER-1].free_list); buddy_free_area[MAX_ORDER-1].nr_free++; } printk(KERN_INFO "Buddy system initialized: %d pages managed\n", NR_PAGES); }

参数说明:memblock_virt_alloc()是内核早期内存分配函数,用于在mm_init()之前分配内存。__SetPageReserved()关键!防止内核其他模块误用这块内存。list_add_tail()将页描述符加入空闲链表,后续alloc_buddy_order()将从此链表摘取节点。

4.2 实现 alloc_buddy_order():查找、分裂、返回页框

分配逻辑:从请求阶order对应的链表开始查找,若为空,则向上查找更高阶链表,找到后不断分裂,直到得到2^order个连续页:

// 分配 2^order 个连续物理页 struct page *alloc_buddy_order(unsigned int order) { int i; struct page *page; struct free_area *area; if (order >= MAX_ORDER) return NULL; // 从 order 阶开始查找 for (i = order; i < MAX_ORDER; i++) { area = &buddy_free_area[i]; if (!list_empty(&area->free_list)) { // 找到空闲块,从链表摘下 page = list_first_entry(&area->free_list, struct page, lru); list_del(&page->lru); area->nr_free--; // 分裂:若 i > order,将块分裂为两半,另一半加入 i-1 阶链表 while (i > order) { i--; struct page *buddy = page + (1 << i); // 计算 buddy 地址 list_add_tail(&buddy->lru, &buddy_free_area[i].free_list); buddy_free_area[i].nr_free++; } return page; } } return NULL; // 分配失败 }

逻辑说明:page + (1 << i)是 Buddy System 的核心——同一阶的两个块互为 buddy,地址相差2^i个页。分裂时,将高阶块一分为二,其中一半(buddy)加入低一阶链表,另一半(page)继续参与下一轮分裂或返回。此函数返回的是struct page *,调用者需通过page_to_pfn(page)获取物理页帧号。

4.3 实现 free_buddy_page():合并、归还、维护链表

释放逻辑:检查释放页的 buddy 是否空闲,若空闲则合并为高一阶块,递归合并直至无法合并:

// 释放 page 开始的 2^order 个页 void free_buddy_page(struct page *page, unsigned int order) { int i; struct page *buddy; struct free_area *area; if (order >= MAX_ORDER) return; for (i = order; i < MAX_ORDER - 1; i++) { buddy = find_buddy(page, i); // 计算 buddy 地址 if (!buddy || !page_is_buddy(buddy, i)) break; // buddy 不空闲,停止合并 // buddy 空闲,从 i 阶链表中移除 list_del(&buddy->lru); buddy_free_area[i].nr_free--; // 合并:将 page 和 buddy 视为一个块,page 取较小地址 if (buddy < page) { struct page *tmp = page; page = buddy; buddy = tmp; } // 合并后,块大小变为 2^(i+1),进入下一循环 } // 将最终块加入 i 阶链表 area = &buddy_free_area[i]; list_add_tail(&page->lru, &area->free_list); area->nr_free++; } // 辅助函数:计算 buddy 地址 static inline struct page *find_buddy(struct page *page, unsigned int order) { unsigned long page_idx = page - buddy_pages; unsigned long buddy_idx = page_idx ^ (1 << order); if (buddy_idx >= NR_PAGES) return NULL; return buddy_pages + buddy_idx; } // 辅助函数:检查 buddy 是否空闲且阶匹配 static inline int page_is_buddy(struct page *page, unsigned int order) { if (page_count(page) != 0) // 引用计数非零,说明已被分配 return 0; // 检查是否在对应阶的空闲链表中(简化判断:仅检查链表节点有效性) return !list_empty(&page->lru); }

关键点:find_buddy()使用异或运算page_idx ^ (1 << order)计算 buddy 地址,这是 Buddy System 的数学本质。page_is_buddy()中page_count(page) == 0是判断页是否空闲的可靠依据(内核标准做法)。


5. 文件系统实验:在 ext2 上添加一个简单的“加密属性”(crypt_attr)扩展

广东工业大学操作系统实验的文件系统模块,要求不修改 ext2 核心逻辑,而是利用 ext2 的i_file_acl字段(原用于 POSIX ACL)作为扩展槽位,在struct ext2_inode中复用该字段存储一个 4 字节的“加密密钥 ID”,并在ext2_readpage()/ext2_writepage()中注入加解密逻辑。这不是全盘加密,而是实现一个可开关的、基于 inode 级别的轻量加密标记。验收时,debugfs -R "stat /testfile" /dev/sda1必须显示i_file_acl: 0x12345678,且对该文件的cat操作会触发内核日志ext2: crypt_attr enabled for inode 123。

5.1 复用 i_file_acl 字段:定义 crypt_attr 结构并修改 inode 读写流程

ext2 的i_file_acl原本指向一个外部 ACL 数据块,但我们将其重定义为crypt_attr_id(密钥 ID)。修改fs/ext2/ext2.h:

// fs/ext2/ext2.h #define EXT2_ENCRYPT_ATTR_ENABLED 0x80000000 // 最高位作为启用标志 #define EXT2_ENCRYPT_ATTR_MASK 0x7FFFFFFF // 低 31 位为密钥 ID // 在 ext2_inode 结构中,i_file_acl 字段复用 struct ext2_inode { // ... 原有字段 __le32 i_file_acl; // 复用:bit31=enable, bits0-30=key_id // ... };

然后在fs/ext2/inode.c的ext2_iget()(读取 inode)和ext2_write_inode()(写入 inode)中注入逻辑:

// fs/ext2/inode.c static int ext2_iget(struct super_block *sb, unsigned long ino, struct ext2_inode *raw_inode) { struct inode *inode; struct ext2_inode_info *ei; inode = iget_locked(sb, ino); if (!inode) return -ENOMEM; ei = EXT2_I(inode); // ... 原有字段赋值(i_mode, i_uid 等) // 解析 crypt_attr ei->i_crypt_attr_id = le32_to_cpu(raw_inode->i_file_acl); if (ei->i_crypt_attr_id & EXT2_ENCRYPT_ATTR_ENABLED) { ei->i_crypt_enabled = 1; ei->i_crypt_key_id = ei->i_crypt_attr_id & EXT2_ENCRYPT_ATTR_MASK; printk(KERN_INFO "ext2: crypt_attr enabled for inode %lu, key_id=0x%x\n", inode->i_ino, ei->i_crypt_key_id); } else { ei->i_crypt_enabled = 0; ei->i_crypt_key_id = 0; } unlock_new_inode(inode); return 0; } // 在 ext2_write_inode() 中,反向写入 int ext2_write_inode(struct inode *inode, struct writeback_control *wbc) { struct ext2_inode_info *ei = EXT2_I(inode); struct ext2_inode *raw_inode; struct buffer_head *bh; // ... 获取 raw_inode 和 bh if (ei->i_crypt_enabled) { raw_inode->i_file_acl = cpu_to_le32( EXT2_ENCRYPT_ATTR_ENABLED | ei->i_crypt_key_id); } else { raw_inode->i_file_acl = 0; } // ... 后续写入逻辑 return 0; }

逻辑说明:i_crypt_enabled是内存中的标志位,i_crypt_key_id存储实际密钥 ID。ext2_iget()在 inode 加载时解析i_file_acl,ext2_write_inode()在 inode 回写时重新编码。printk日志是验收关键证据。

5.2 在 page cache 层注入加解密钩子:修改 ext2_readpage() 和 ext2_writepage()

真正的加解密发生在 page cache 的读写路径。我们修改fs/ext2/inode.c中的ext2_readpage()和ext2_writepage():

// fs/ext2/inode.c static int ext2_readpage(struct file *file, struct page *page) { struct inode *inode = page->mapping->host; struct ext2_inode_info *ei = EXT2_I(inode); // 若启用 crypt_attr,对 page 内容进行解密(此处为示意:XOR 密钥 ID) if (ei->i_crypt_enabled && page->index == 0) { // 仅对第 0 页(文件头)操作 void *addr = kmap(page); int i; for (i = 0; i < PAGE_SIZE && i < 64; i += 4) { // 仅解密前 64 字节 u32 *p = (u32*)(addr + i); *p ^= ei->i_crypt_key_id; // 简单 XOR 解密 } kunmap(page); } // 调用原始 readpage(ext2_block_read_full_page) return ext2_block_read_full_page(page, ext2_get_block); } static int ext2_writepage(struct page *page, struct writeback_control *wbc) { struct inode *inode = page->mapping->host; struct ext2_inode_info *ei = EXT2_I(inode); // 若启用 crypt_attr,对 page 内容进行加密 if (ei->i_crypt_enabled && page->index == 0) { void *addr = kmap(page); int i; for (i = 0; i < PAGE_SIZE && i < 64; i += 4) { u32 *p = (u32*)(addr + i); *p ^= ei->i_crypt_key_id; // 简单 XOR 加密 } kunmap(page); } // 调用原始 writepage return block_write_full_page(page, ext2_get_block, wbc); }

注意:此处kmap()/kunmap()是必须的,因为 page 可能不在高端内存,需映射到内核虚拟地址空间。page->index == 0限制只对文件头加密,避免影响大文件性能。ext2_get_block()是 ext2 的块映射函数,保持原有逻辑不变。

5.3 用户态触发:通过 ioctl 设置 crypt_attr

内核不能直接接收用户输入,需通过ioctl接口。我们在fs/ext2/file.c中为 ext2 文件添加EXT2_IOC_SET_CRYPT命令:

// fs/ext2/file.c #define EXT2_IOC_SET_CRYPT _IOW('f', 16, __u32) long ext2_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { struct inode *inode = filp->f_path.dentry->d_inode; struct ext2_inode_info *ei = EXT2_I(inode); __u32 key_id; switch (cmd) { case EXT2_IOC_SET_CRYPT: if (copy_from_user(&key_id, (void __user *)arg, sizeof(key_id))) return -EFAULT; ei->i_crypt_enabled = 1; ei->i_crypt_key_id = key_id; // 立即更新 inode 的 i_file_acl 字段(需同步到磁盘) mark_inode_dirty(inode); return 0; default: return -ENOTTY; } }

用户态程序(set_crypt.c)可调用:

#include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #define EXT2_IOC_SET_CRYPT _IOW('f', 16, __u32) int main(int argc, char *argv[]) { int fd = open(argv[1], O_RDWR); if (fd < 0) { perror("open"); return 1; } ioctl(fd, EXT2_IOC_SET_CRYPT, 0x12345678); close(fd); return 0; }

执行./set_crypt /mnt/testfile后,该文件 inode 的i_file_acl即被设为0x80000000 | 0x12345678 = 0x92345678,且下次读写时触发加解密逻辑。


6. 避坑指南:广东工业大学操作系统实验中 5 个高频翻车点与血泪解决方案

这门课的“玄学”程度远超想象——很多错误不会直接报错,而是表现为dmesg里一闪而过的BUG: unable to handle kernel paging request,或是cat /proc/sched_debug输出全为 0。以下是我在某高校实验室带教三年、累计

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/13 16:08:53

知识工作插件化指南:从采集到发布的全流程自动化

你知道那种感觉吗&#xff1f;浏览器里开了四十多个标签页&#xff0c;笔记软件里堆着三年前的剪藏&#xff0c;邮箱里躺着一堆“稍后处理”的附件。每天工作八小时&#xff0c;至少有两个小时花在“搬运信息”上&#xff1a;从网页复制到笔记&#xff0c;从笔记整理成文档&…

作者头像 李华
网站建设 2026/10/13 16:07:18

操作系统课堂小测复盘:线程与调度核心考点与避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 16:06:04

ECU NM休眠唤醒与上下电的本质区别及协同设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/13 16:05:17

IPD流程管理工具选型指南与2026趋势分析

1. IPD流程管理工具的核心价值与选型痛点IPD&#xff08;Integrated Product Development&#xff09;作为一套成熟的产品开发管理体系&#xff0c;正在被越来越多的科技制造企业采用。但真正落地IPD时&#xff0c;最让管理者头疼的往往是工具选型问题——市面上的PLM、PPM、AL…

作者头像 李华
网站建设 2026/10/13 16:05:11

Ghidra 11.0.2 Linux实战:headless批量反编译与固件逆向指南

简介&#xff1a;Ghidra 11.0.2 是一款面向软件逆向工程&#xff08;SRE&#xff09;领域的开源框架安装包&#xff0c;适用于安全研究、漏洞分析、恶意代码排查及二进制逆向学习的中高级技术人员。它内置反汇编、汇编、反编译、绘图与脚本等完整工具链&#xff0c;可在 Window…

作者头像 李华
网站建设 2026/10/13 16:03:06

代码视频五步流水线:模型辅助生成与时间轴对齐实战

1. 拆解“代码视频”这件事&#xff1a;为什么模型不直接生成视频 很多人第一次听到“用模型做代码视频”这个说法&#xff0c;脑子里浮现的画面大概是&#xff1a;敲一行提示词&#xff0c;等上几十秒&#xff0c;一段带配音、带字幕、带转场的成品视频就吐出来了。我一开始也…

作者头像 李华