操作系统第一次实验
实验内容
本次实验的主要内容是在 Linux 内核中设计并实现一个新的系统调用 sys_proc_dfs。该系统调用需要能够接收一个指定的进程 PID,并以此为起点,采用深度优先搜索的方式遍历该进程的所有后代。在遍历过程中,需要收集每个后代进程的特定信息,包括其 PID、状态、用户态和内核态运行时间、父/子/兄进程 PID、上一次调度的起止时间戳,以及一个表示数据是否成功写入用户缓冲区的标志。最后,系统调用需将收集到的这些进程记录安全地拷贝到用户空间提供的缓冲区中,供用户态测试程序读取和展示。
实验思路
本实验采取分阶段推进的策略。首先着手于搭建和验证内核编译环境,通过添加一个极简的 sys_hello 系统调用,跑通了从修改内核源码、编译、安装、重启到用户态测试的完整流程,为后续开发奠定基础。
明确了基本流程后,根据实验要求,在内核头文件中定义了用于数据交换的struct proc_dfs_info 结构体。紧接着,在内核中注册了新的系统调用 sys_proc_dfs,并实现了其基本框架,确保能够正确接收用户传入的PID、缓冲区地址和大小等参数,并进行初步的合法性校验。
实验的核心在于内核态深度优先搜索逻辑的实现。为此,设计了 kernel_dfs_traverse 辅助函数。该函数利用Linux内核提供的链表遍历宏(如 list_for_each_entry)和RCU读写锁来安全地访问进程数据结构(task_struct),递归地遍历 children 和 sibling 链表,实现了DFS算法。
在遍历过程中,kernel_dfs_traverse 函数负责从 task_struct 及其内嵌的调度相关结构体(如 sched_entity)中提取所需的进程信息,并将其填充到一个临时的内核态 kinfo 结构体中。
最后,编写了用户态测试程序 test_dfs.c。该程序负责解析命令行参数获取目标PID,分配缓冲区,调用新的 sys_proc_dfs 系统调用,接收内核返回的进程记录数量和数据,并将这些数据格式化为易于阅读的表格进行输出。整个开发过程伴随着持续的 printk/dmesg 内核调试和用户态测试,以解决编译依赖、语法错误、API理解偏差等问题,确保最终实现的功能符合实验要求。
实验过程
环境搭建
本实验使用VMware+Ubuntu,下文所有操作均在虚拟机中进行。
下载Linux源代码并做好相关环境配置,为后续实验奠定基础。
- 更新包列表并安装编译依赖
sudo apt update
sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev git
- 下载Linux内核源码
apt source linux=6.14.0-33.33
- 配置内核
# 1. 进入源码目录
cd linux-6.14.0/
# 2. 复制当前系统的内核配置文件
cp /boot/config-$(uname -r) .config
# 3. 基于旧的 .config 文件自动回答所有新配置项
make olddefconfig
环境验证
在配置好的环境中,尝试写一个sys_hello系统调用以验证环境正常并熟悉编写系统调用的过程。
- 分配系统调用号
编辑 arch/x86/entry/syscalls/syscall_64.tbl 文件。在最后一行添加:548 64 hello sys_hello。548 为新系统调用号,64 表示这是一个64位系统调用,hello 是系统调用的名称,sys_hello 是内核中对应的函数名。
- 声明函数原型
编辑 include/linux/syscalls.h 这个头文件,在最后一个 #endif 之前,添加:asmlinkage long sys_hello(void); 声明我们的函数,以便内核的其他部分能找到它。
- 实现函数
编辑 kernel/sys.c 文件,在最末尾添加我们对系统调用的函数实现。
#include <linux/printk.h> // 包含 printk 函数的定义
SYSCALL_DEFINE0(hello)
{
printk(KERN_INFO "Hello, World! My first syscall is working!\n");
return 0;
}
SYSCALL_DEFINE0 是一个宏,用于定义一个没有参数的系统调用;KERN_INFO 是日志级别;系统调用最后应返回一个值。
-
编译并安装内核
# -j$(nproc) 会使用所有的 CPU 核心来并行编译,速度最快 make -j$(nproc) # 添加执行权限 chmod +x debian/scripts/* # 安装内核模块 sudo make modules_install # 安装内核 sudo make install # 重启虚拟机 sudo reboot -
编写用户态测试程序 创建一个新文件test_hello.c,写入以下代码。
其中,#include <stdio.h> #include <unistd.h> #include <sys/syscall.h> // 包含 syscall() 函数 #include <errno.h> // 包含 perror() #define SYS_hello 548 int main() { long ret; printf("正在调用 sys_hello (syscall #%d)...\n", SYS_hello); ret = syscall(SYS_hello); if (ret == 0) { printf("调用成功! 请检查 dmesg 查看内核日志。\n"); } else { // 如果 ret 不是 0 (比如 -1),说明出错了 // perror 会根据 errno 自动打印错误信息 perror("syscall(sys_hello) 失败"); } return 0; }548是在syscall_64.tbl中分配的系统调用号;syscall()是调用任意系统调用的标准 C 库函数。 -
测试sys_hello系统调用
# 编译 gcc ~/test_hello.c -o ~/test_hello # 运行 ~/test_hello # 查看内核日志 dmesg | tail -n 5
定义数据结构
在内核中创建头文件 include/linux/proc_dfs.h,并定义用于数据交换的 struct proc_dfs_info 结构体,为后续编写系统调用做好准备。
#ifndef _LINUX_PROC_DFS_H
#define _LINUX_PROC_DFS_H
#include <linux/types.h>
struct proc_dfs_info {
pid_t pid;
int state; // 进程状态
long utime; // 用户态运行时间
long stime; // 内核态运行时间
pid_t parent_pid;
pid_t first_child_pid;
pid_t next_sibling_pid;
u64 sched_start_ns;
u64 sched_end_ns;
int report;
};
#endif
其中包括该进程 PID、状态、用户态和内核态运行时间、父/子/兄进程 PID、上一次调度的起止时间戳,以及是否上报的标志。
搭建系统调用框架并测试
仿照之前的 sys_hello,搭建 sys_proc_dfs 的系统调用框架并进行简答测试。为之后实现dfs逻辑细节留下空间。
-
分配系统调用号 编辑
arch/x86/entry/syscalls/syscall_64.tbl文件。在最后一行添加:549 64 proc_dfs sys_proc_dfs。549为新系统调用号,64表示这是一个64位系统调用,proc_dfs是系统调用的名称,sys_proc_dfs是内核中对应的函数名。 -
声明函数原型
在 include/linux/syscalls.h 的末尾 #endif 前,添加:
#include <linux/proc_dfs.h>
asmlinkage long sys_proc_dfs(
pid_t top_pid,
struct proc_dfs_info __user *buf,
size_t buf_size
);
- 实现系统调用函数骨架
在 kernel/sys.c 的末尾,编写系统调用函数的骨架。
#include <linux/uaccess.h> // 包含 copy_to_user / copy_from_user
#include <linux/proc_dfs.h> // 包含结构体定义
SYSCALL_DEFINE3(proc_dfs,
pid_t, top_pid,
struct proc_dfs_info __user *, buf,
size_t, buf_size)
{
printk(KERN_INFO "[proc_dfs] 系统调用被触发!\n");
printk(KERN_INFO "[proc_dfs] 目标 PID: %d, 缓冲区地址: %p, 缓冲区大小: %ld 字节\n",
top_pid, buf, buf_size);
/* --- 参数合法性检查 ---
* 检查用户传入的缓冲区地址是否为空
* 检查缓冲区大小是否至少能容纳一个结构体
*/
if (buf == NULL || buf_size < sizeof(struct proc_dfs_info)) {
printk(KERN_WARNING "[proc_dfs] 缓冲区无效! buf: %p, buf_size: %ld\n",
buf, buf_size);
return -EINVAL; // 返回 "Invalid Argument" (无效参数) 错误
}
// 暂时先返回 0,表示成功找到了 0 个进程
printk(KERN_INFO "[proc_dfs] 骨架执行完毕,暂时返回 0。\n");
return 0;
}
top_pid 为起始进程 PID;buf 为用户空间指针,指向 proc_dfs_info 数组;buf_size:缓冲区字节数,用于计算能容纳的记录数。在骨架中,打印调试信息,检查传入参数的合法性,并返回0。
- 编写用户态测试程序
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <errno.h>
#define __NR_proc_dfs 549
struct proc_dfs_info {
pid_t pid;
int state;
long utime;
long stime;
pid_t parent_pid;
pid_t first_child_pid;
pid_t next_sibling_pid;
unsigned long long sched_start_ns; // 在 C 语言中用 unsigned long long 对应 u64
unsigned long long sched_end_ns;
int reported;
};
int main(int argc, char *argv[]) {
pid_t pid;
struct proc_dfs_info buffer[100]; // 在栈上分配一个能装100个记录的缓冲区
size_t buf_size = sizeof(buffer); // 缓冲区总大小(字节)
long num_procs_found;
if (argc < 2) {
fprintf(stderr, "用法: %s <PID>\n", argv[0]);
return 1;
}
pid = atoi(argv[1]); // 把第一个参数(字符串)转成整数 PID
printf("正在调用 sys_proc_dfs (syscall #%d) ...\n", __NR_proc_dfs);
printf("目标 PID: %d, 缓冲区大小: %ld 字节\n", pid, buf_size);
num_procs_found = syscall(__NR_proc_dfs, pid, buffer, buf_size);
if (num_procs_found < 0) {
perror("syscall(proc_dfs) 失败");
return 1;
}
printf("系统调用成功!\n");
printf("内核(暂时)返回了 %ld 个进程。\n", num_procs_found);
return 0;
}
#include <linux/proc_dfs.h> 因为那个是内核头文件,用户空间程序找不到。使用之前分配的系统调用号,并进行系统调用,输出调试信息。
- 测试系统调用框架
编译修改后的整个内核,并进行测试。
实现内核态的进程树 DFS 遍历
填充之前实现的系统调用骨架,实现dfs遍历的逻辑细节。
-
创建dfs上下文结构体
其中,struct dfs_context { struct proc_dfs_info __user *user_buf; size_t max_count; size_t current_count; };user_buf指向用户缓冲区,max_count表示缓冲区最多容纳的 proc_dfs_info 个数,current_count表示当前已写入多少条。 -
实现dfs核心函数
对于每一个进程,从 task_struct 中获取所需的 proc_dfs_info,然后写入缓冲区,最后使用static void kernel_dfs_traverse(struct task_struct *task, struct dfs_context *ctx) { struct task_struct *child; struct proc_dfs_info kinfo; // 内核空间的临时信息 u64 last_duration; // 用于计算上一个时间片的持续时长 /* --- 1. 缓冲区检查 --- */ if (ctx->current_count >= ctx->max_count) { return; } /* --- 2. 彻底初始化 kinfo --- */ memset(&kinfo, 0, sizeof(kinfo)); /* --- 3. 填充所有字段 --- */ kinfo.pid = task->pid; kinfo.state = task->__state; kinfo.utime = task->utime; kinfo.stime = task->stime; if (task->real_parent) { kinfo.parent_pid = task->real_parent->pid; } struct task_struct *first_child = list_first_entry_or_null( &task->children, struct task_struct, sibling); if (first_child) { kinfo.first_child_pid = first_child->pid; } struct task_struct *next_sibling = NULL; if (task->real_parent && !list_is_last(&task->sibling, &task->real_parent->children)) { next_sibling = list_next_entry(task, sibling); } if (next_sibling) { kinfo.next_sibling_pid = next_sibling->pid; } kinfo.sched_start_ns = task->se.exec_start; // 计算上一个时间片的持续时长 last_duration = task->se.sum_exec_runtime - task->se.prev_sum_exec_runtime; // 计算结束时间 = 开始时间 + 持续时长 kinfo.sched_end_ns = task->se.exec_start + last_duration; // --- "是否上报" --- kinfo.report = 0; /* --- 4. 一次性、原子性地写入 --- */ if (copy_to_user(&ctx->user_buf[ctx->current_count], &kinfo, sizeof(kinfo))) { printk(KERN_WARNING "[proc_dfs] copy_to_user 失败! 停止。\n"); ctx->max_count = 0; return; } /* --- 5. 写入成功,递增计数器 --- */ ctx->current_count++; kinfo.report = 1; // 已上报 /* --- 6. 递归 --- */ list_for_each_entry(child, &task->children, sibling) { kernel_dfs_traverse(child, ctx); if (ctx->max_count == 0) { return; } } }list_for_each_entry宏递归地对子进程进行遍历。 -
填充系统调用骨架
创建一个 dfs 的上下文变量 ctx 用于记录缓冲区使用情况,并对其进行初始化;在用 kernel_dfs_traverse 对进程进行访问的前后,使用SYSCALL_DEFINE3(proc_dfs, pid_t, top_pid, struct proc_dfs_info __user *, buf, size_t, buf_size) { struct task_struct *start_task; struct dfs_context ctx; long process_count; // 内核调试日志 printk(KERN_INFO "[proc_dfs] 系统调用被触发!\n"); printk(KERN_INFO "[proc_dfs] 目标 PID: %d, 缓冲区地址: %p, 缓冲区大小: %ld 字节\n", top_pid, buf, buf_size); // 参数合法性检查 if (buf == NULL || buf_size < sizeof(struct proc_dfs_info)) { printk(KERN_WARNING "[proc_dfs] 缓冲区无效!buf: %p, buf_size: %ld\n", buf, buf_size); return -EINVAL; } ctx.user_buf = buf; ctx.current_count = 0; ctx.max_count = buf_size / sizeof(struct proc_dfs_info); printk(KERN_INFO "[proc_dfs] 缓冲区最多可容纳 %ld 个进程记录。\n", ctx.max_count); rcu_read_lock(); start_task = find_task_by_vpid(top_pid); if (start_task == NULL) { printk(KERN_WARNING "[porc_dfs] PID %d 未找到!\n", top_pid); rcu_read_unlock(); return -ESRCH; } printk(KERN_INFO "[proc_dfs] 成功找到 PID %d,开始DFS遍历...\n", top_pid); struct task_struct *child; list_for_each_entry(child, &start_task->children, sibling) { kernel_dfs_traverse(child, &ctx); } rcu_read_unlock(); process_count = ctx.current_count; printk(KERN_INFO "[proc_dfs] 遍历完成,总共找到 %ld 个后代进程。\n", process_count); return process_count; }rcu_read_lock()和rcu_read_unlock()来加锁,保证读取安全;使用find_task_by_vpid来得到传入 id 的进程;对每一个子进程,调用 kernel_dfs_traverse 进行遍历。 -
修改用户测试程序
进行系统调用,并将信息打印成表格。int main(int argc, char *argv[]) { pid_t pid; struct proc_dfs_info buffer[100]; size_t buf_size = sizeof(buffer); long num_procs_found; // 内核将返回找到的进程数 if (argc < 2) { fprintf(stderr, "用法: %s <PID>\n", argv[0]); fprintf(stderr, "例如: %s 1\n", argv[0]); return 1; } pid = atoi(argv[1]); printf("正在调用 sys_proc_dfs (syscall #%d) ...\n", __NR_proc_dfs); printf("目标 PID: %d, 缓冲区大小: %ld 字节\n", pid, buf_size); // -- 调用系统调用 -- num_procs_found = syscall(__NR_proc_dfs, pid, buffer, buf_size); if (num_procs_found < 0) { perror("syscall(proc_dfs) 失败"); return 1; } if (num_procs_found == 0) { printf("系统调用成功!未找到后代进程。\n"); return 0; } printf("系统调用成功!内核返回了 %ld 个后代进程记录:\n\n", num_procs_found); printf("PID\t 状态\t 父PID\t 首子PID\t 兄PID\t 用户态(ns)\t 内核态(ns)\t 调度开始(ns)\t\t 调度结束(ns)\t 是否上报\n"); printf("------------------------------------------------------------------------------------------\n"); for (long i = 0; i < num_procs_found; i++) { printf("%d\t %d\t %d\t %d\t\t %d\t %ld\t\t %ld\t\t %llu\t %llu\t %d\n", buffer[i].pid, buffer[i].state, buffer[i].parent_pid, buffer[i].first_child_pid, buffer[i].next_sibling_pid, buffer[i].utime, buffer[i].stime, buffer[i].sched_start_ns, buffer[i].sched_end_ns, buffer[i].report ); } return 0; } -
安装与测试
重新编译内核,安装并重启,然后编译运行用户测试程序。
实验结果
编译最后的测试程序,运行 test_dfs 1,结果如下:
wuyukai@wuyukai-VMware-Virtual-Platform:~$ ./test_dfs 1
正在调用 sys_proc_dfs (syscall #549) ...
目标 PID: 1, 缓冲区大小: 6400 字节
系统调用成功!内核返回了 100 个后代进程记录:
PID 状态 父PID 首子PID 兄PID 用户态(ns) 内核态(ns) 调度开始(ns) 调度结束(ns) 是否上报
----------------------------------------------------------------------------------------------------------------------------------------------------------------
544 1 1 0 561 107000000 118000000 79369766971 79369821202 0
561 1 1 0 606 22000000 16000000 99864731908 99865023368 0
606 1 1 0 613 11000000 15000000 75392887420 75393031595 0
613 1 1 0 616 19000000 28000000 91164404923 91164554888 0
616 1 1 0 1349 126000000 83000000 60621728406 60621928507 0
1349 1 1 0 1351 2000000 3000000 12855576696 12855619296 0
1351 1 1 1422 1352 6000000 15000000 99815140906 99815211246 0
1422 8193 1351 0 0 0 0 13130215843 13130219384 0
1352 1 1 0 1357 125000000 65000000 80206570229 80206879186 0
1357 1 1 0 1368 11000000 13000000 14002669395 14002763954 0
1368 1 1 0 1381 83000000 54000000 76130952271 76130957691 0
1381 8193 1 0 1382 44000000 803000000 14172405048 14172408299 0
1382 1 1 0 1388 15000000 29000000 69696448658 69696460936 0
1388 8193 1 0 1390 2000000 5000000 78964019084 78964062445 0
1390 1 1 0 1395 9000000 17000000 15490356330 15490420098 0
1395 1 1 0 1400 40000000 46000000 76606692000 76606697233 0
1400 1 1 0 1411 26000000 36000000 69696614986 69696633825 0
1411 1 1 0 1448 18000000 6000000 13056056773 13056124258 0
1448 1 1 0 1451 19000000 48000000 76031503650 76031649401 0
1451 1 1 0 1489 1000000 19000000 93239783783 93239819667 0
1489 1 1 0 1588 26000000 42000000 69696596147 69696610688 0
1588 1 1 1603 1591 10000000 18000000 49233498121 49233566798 0
1603 1 1588 0 0 3000000 2000000 13926085085 13926097562 0
1591 1 1 0 1611 66000000 20000000 14017484583 14017866238 0
1611 1 1 1617 1624 5000000 6000000 16714207457 16714247802 0
1617 1 1611 1697 0 3000000 11000000 14318180136 14318550074 0
1697 1 1617 1738 0 4000000 2000000 14383255648 14383283060 0
1738 1 1697 1740 0 1000000 1000000 14393999867 14394102101 0
1740 1 1738 2281 1765 38000000 34000000 76605822162 76605959078 0
2281 128 1740 0 0 0 1000000 16773897505 16773901749 0
1765 1 1738 1836 0 25000000 24000000 69695502068 69695551143 0
1836 1 1765 1896 1956 941000000 336000000 80207991064 80208563414 0
1896 1 1836 0 2138 40000000 41000000 41256202133 41256223190 0
2138 1 1836 0 2143 64000000 24000000 41504245270 41504397821 0
2143 1 1836 2155 0 19000000 8000000 46204908663 46204916159 0
2155 1 2143 0 2223 4000000 1000000 16137161008 16137182789 0
2223 1 2143 0 0 2000000 1000000 16385586344 16385607663 0
1956 1 1765 0 1959 5000000 6000000 16699314183 16699342785 0
1959 1 1765 0 1977 49000000 24000000 32212090457 32212110410 0
1977 1 1765 0 1982 20000000 26000000 42914576764 42914672637 0
1982 1 1765 0 1985 28000000 17000000 46292998467 46293069392 0
1985 1 1765 0 1988 6000000 9000000 16699226093 16699241029 0
1988 1 1765 0 2001 3000000 4000000 16699004705 16699031894 0
2001 1 1765 0 2006 5000000 2000000 16699079207 16699105762 0
2006 1 1765 0 2009 5000000 2000000 16698925517 16698952979 0
2009 1 1765 0 2028 36000000 22000000 69689326374 69689348540 0
2028 1 1765 0 2030 5000000 2000000 16699212301 16699239200 0
2030 1 1765 0 2036 6000000 4000000 16698971479 16699002186 0
2036 1 1765 0 2041 3000000 3000000 16699207640 16699231871 0
2041 1 1765 0 2048 7000000 1000000 46875506069 46875968787 0
2048 1 1765 0 0 33000000 14000000 69689169401 69689204840 0
1624 1 1 0 1630 14000000 13000000 76130943602 76130975685 0
1630 1 1 1631 1737 88000000 34000000 46877142485 46879119226 0
1631 8193 1630 0 1687 0 0 14123124750 14123186621 0
1687 1 1630 0 1688 4000000 2000000 16771122465 16771154051 0
1688 1 1630 0 1693 12000000 19000000 16717247308 16717261986 0
1693 1 1630 0 1698 9000000 2000000 14383261257 14383267695 0
1698 1 1630 0 1699 65000000 37000000 69695580955 69695617064 0
1699 1 1630 0 1700 16000000 6000000 16727499037 16727536663 0
1700 1 1630 0 1772 2000000 6000000 16771089792 16771161815 0
1772 1 1630 0 1776 7000000 4000000 16771135760 16771149241 0
1776 1 1630 0 2283 6000000 2000000 14443816226 14443853139 0
2283 1 1630 0 0 2000000 5000000 16771899040 16771932665 0
1737 1 1 0 1754 10000000 8000000 46781354145 46781397514 0
1754 1 1 0 1889 13000000 18000000 69696426082 69696444731 0
1889 1 1 1895 1899 2000000 0 16657779189 16657841361 0
1895 1 1889 0 0 3000000 3000000 16709365228 16709375860 0
1899 1 1 0 1898 57000000 25000000 16016890997 16016897392 0
1898 1 1 0 1933 3000000 4000000 16709024906 16709125173 0
1933 1 1 0 1943 3000000 1000000 16247679511 16247697655 0
1943 1 1 0 1953 10000000 9000000 96116749338 96116785477 0
1953 1 1 0 1941 16000000 24000000 69696581606 69696595288 0
1941 1 1 0 2157 32000000 9000000 36212177193 36212303057 0
2157 1 1 0 2159 30000000 8000000 16234133565 16234166973 0
2159 1 1 0 2182 3000000 1000000 76605889353 76605902239 0
2182 1 1 0 2227 3000000 7000000 16242899896 16242923677 0
2227 1 1 0 2214 32000000 12000000 36212228032 36212349116 0
2214 1 1 0 2274 60000000 16000000 76601876223 76601901776 0
2274 1 1 0 2185 53000000 14000000 33217599737 33217692862 0
2185 1 1 0 2321 8000000 10000000 76606339717 76606346047 0
2321 1 1 2324 2328 5000000 7000000 69684523983 69684562481 0
2324 1 2321 2472 2644 7000000 15000000 46886144628 46886166818 0
2472 1 2324 2491 0 1000000 7000000 47126285332 47126528845 0
2491 1 2472 2524 0 3000000 8000000 47126193896 47126200270 0
2524 8193 2491 0 2580 40000000 51000000 100480894434 100480903653 0
2580 8193 2491 0 0 0 2000000 47127315917 47127378508 0
2644 1 2321 2681 0 6000000 8000000 69780582492 69780622526 0
2681 1 2644 2682 0 17000000 73000000 100480885215 100480956386 0
2682 1 2681 2683 0 2000000 1000000 69784198308 69784207587 0
2683 8193 2682 0 0 1000000 0 69784738165 69785880778 0
2328 1 1 2329 0 56000000 38000000 47768809274 47769250023 0
2329 8193 2328 0 2342 0 0 46572691908 46572876578 0
2342 1 2328 0 2343 4000000 4000000 46882292384 46882346218 0
2343 1 2328 0 2345 10000000 7000000 46806415830 46806452436 0
2345 1 2328 0 2349 10000000 5000000 46747240429 46747723323 0
2349 1 2328 2633 2350 18000000 266000000 47817028499 47817466726 0
2633 1 2349 0 0 6000000 58000000 69690634211 69690754811 0
2350 1 2328 0 2351 32000000 13000000 69695482399 69695536988 0
2351 1 2328 0 2352 5000000 9000000 46802348549 46802373257 0
2352 1 2328 0 2449 6000000 8000000 46882276093 46882385109 0
观察 pid 的顺序可以得知,该系统调用已经正确遍历了进程树,用户程序通过读取传入的缓冲区,输出了每条进程对应的信息。最后一列"是否上报"在我这里的实现是,写入缓冲区前是 0,写入之后置为 1,因此缓冲区内全为 0,符合预期。
实验总结与反思
本次实验成功地在 Linux 内核 (版本 6.14.0) 中添加了一个新的系统调用 sys_proc_dfs。该调用能够按深度优先顺序遍历指定进程的后代,收集包括 PID、状态、运行时间、亲缘关系、调度时间戳在内的信息,并通过用户提供的缓冲区返回给用户态程序。用户态测试程序也成功实现了对该系统调用的调用和结果的格式化展示。
在本次实验中,大部分时间花在了环境配置上。我这次先跑通 hello world,再跑通 sys_proc_dfs 的骨架,最后实现 dfs 的细节,这一过程看似绕路,实则我觉得是很有必要的,在代码量不那么大的时候,就暴露并解决了很多配置上遇到的问题。
另外,在阅读 Linux 内核源代码的过程中,我发现,尽管通常来说 C 语言是面向过程的,但是源代码中有很多的 struct,有点面向对象的味道,井然有序,我觉得这是很好的,阅读和调试都比较方便。