跳转至

操作系统第一次实验

实验内容

本次实验的主要内容是在 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),递归地遍历 childrensibling 链表,实现了DFS算法。

在遍历过程中,kernel_dfs_traverse 函数负责从 task_struct 及其内嵌的调度相关结构体(如 sched_entity)中提取所需的进程信息,并将其填充到一个临时的内核态 kinfo 结构体中。

最后,编写了用户态测试程序 test_dfs.c。该程序负责解析命令行参数获取目标PID,分配缓冲区,调用新的 sys_proc_dfs 系统调用,接收内核返回的进程记录数量和数据,并将这些数据格式化为易于阅读的表格进行输出。整个开发过程伴随着持续的 printk/dmesg 内核调试和用户态测试,以解决编译依赖、语法错误、API理解偏差等问题,确保最终实现的功能符合实验要求。

实验过程

环境搭建

本实验使用VMware+Ubuntu,下文所有操作均在虚拟机中进行。

下载Linux源代码并做好相关环境配置,为后续实验奠定基础。

  1. 更新包列表并安装编译依赖

 sudo apt update
 sudo apt install -y build-essential libncurses-dev bison flex libssl-dev libelf-dev git
其中,-y选项用于在所有交互式提示自动回答yes。

  1. 下载Linux内核源码
 apt source linux=6.14.0-33.33
  1. 配置内核
 # 1. 进入源码目录
 cd linux-6.14.0/
 # 2. 复制当前系统的内核配置文件
 cp /boot/config-$(uname -r) .config
 # 3. 基于旧的 .config 文件自动回答所有新配置项
 make olddefconfig

环境验证

在配置好的环境中,尝试写一个sys_hello系统调用以验证环境正常并熟悉编写系统调用的过程。

  1. 分配系统调用号

编辑 arch/x86/entry/syscalls/syscall_64.tbl 文件。在最后一行添加:548 64 hello sys_hello548 为新系统调用号,64 表示这是一个64位系统调用,hello 是系统调用的名称,sys_hello 是内核中对应的函数名。

  1. 声明函数原型

编辑 include/linux/syscalls.h 这个头文件,在最后一个 #endif 之前,添加:asmlinkage long sys_hello(void); 声明我们的函数,以便内核的其他部分能找到它。

  1. 实现函数

编辑 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 是日志级别;系统调用最后应返回一个值。

  1. 编译并安装内核

     # -j$(nproc) 会使用所有的 CPU 核心来并行编译,速度最快
     make -j$(nproc)
     # 添加执行权限
     chmod +x debian/scripts/*
     # 安装内核模块
     sudo make modules_install
     # 安装内核
     sudo make install
     # 重启虚拟机
     sudo reboot
    

  2. 编写用户态测试程序 创建一个新文件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 库函数。

  3. 测试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逻辑细节留下空间。

  1. 分配系统调用号 编辑 arch/x86/entry/syscalls/syscall_64.tbl 文件。在最后一行添加:549 64 proc_dfs sys_proc_dfs549 为新系统调用号,64 表示这是一个64位系统调用,proc_dfs 是系统调用的名称,sys_proc_dfs 是内核中对应的函数名。

  2. 声明函数原型

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
);

  1. 实现系统调用函数骨架

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。

  1. 编写用户态测试程序

#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;
}
在用户空间定义完全相同的结构体以便 C 编译器知道如何分配内存和解析数据。不能 #include <linux/proc_dfs.h> 因为那个是内核头文件,用户空间程序找不到。使用之前分配的系统调用号,并进行系统调用,输出调试信息。

  1. 测试系统调用框架

编译修改后的整个内核,并进行测试。

实现内核态的进程树 DFS 遍历

填充之前实现的系统调用骨架,实现dfs遍历的逻辑细节。

  1. 创建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 表示当前已写入多少条。

  2. 实现dfs核心函数

    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;
            }
        }
    }
    
    对于每一个进程,从 task_struct 中获取所需的 proc_dfs_info,然后写入缓冲区,最后使用 list_for_each_entry 宏递归地对子进程进行遍历。

  3. 填充系统调用骨架

    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;
    }
    
    创建一个 dfs 的上下文变量 ctx 用于记录缓冲区使用情况,并对其进行初始化;在用 kernel_dfs_traverse 对进程进行访问的前后,使用 rcu_read_lock()rcu_read_unlock() 来加锁,保证读取安全;使用 find_task_by_vpid 来得到传入 id 的进程;对每一个子进程,调用 kernel_dfs_traverse 进行遍历。

  4. 修改用户测试程序

    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;
    }
    
    进行系统调用,并将信息打印成表格。

  5. 安装与测试

重新编译内核,安装并重启,然后编译运行用户测试程序。

实验结果

编译最后的测试程序,运行 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,有点面向对象的味道,井然有序,我觉得这是很好的,阅读和调试都比较方便。