零基础小白速通北邮操作系统课设(四)判断读入进程的合理性并打印进程状态及关系

往期内容:
(一)Linux内核输出Helloworld
(二)命令行输入模块变量&分模块编写
上期内容:
(三)判断进程关系及打印进程树


课程任务:开发一个内核模块或组件,完成如下功能:

  1. 读取 ∼/targets 文件。格式如下,
pid: 100, 234 
prog: program, another program 
file: a filename; another filename
  1. 记录 pidprog 进程访问了哪些文件和 IP 地址,记录 file 文件被哪些进程所访问。记录内容至少包括进程 pid 和程序名、日期时间、访问模式。
  2. 当进程或文件数量不大于 5 个,展示它们的关系,例如,进程之间的父子关系,某个文件被哪些进程并发访问。
  3. 支持记录最多 20 个进程,给出进程分别为 5,10,20 个情况下的模块性能,包括 CPU 和内存使用情况。

第五周周报

本周主要实现了给定pid输出进程状态和合并读取进程以及输出关系模块两个任务,具体介绍如下。

一、给定进程pid输出进程状态

保存在pid2stats模块中。该模块的作用是用于查询从文件中得到进程是否存在主机中,如果无效则返回。

1. 头文件引入

本代码使用了以下几个头文件:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
  • <linux/module.h> 提供了编写内核模块的基本功能;
  • <linux/kernel.h> 提供了内核编程的一些基本函数和数据类型;
  • <linux/init.h> 提供了初始化和清理函数的宏定义;
  • <linux/sched.h> 提供了进程管理相关的函数和数据结构;
  • <linux/sched/signal.h> 提供了进程信号相关的函数和数据结构。
2. 模块参数定义

本代码定义了一个模块参数 target_pid,用于接收用户输入的进程PID。同时,使用了 MODULE_PARM_DESC() 宏定义来为该参数添加描述信息。

static int target_pid;

module_param(target_pid, int, 0);
MODULE_PARM_DESC(target_pid, "Target process PID");
3. 进程状态检查函数

本代码实现了一个名为 check_process_state() 的函数,用于检查指定进程的状态。

int check_process_state(int pid) {
    // 检查输入进程pid
    printk(KERN_INFO "Process %d", pid);
    struct task_struct *task = pid_task(find_vpid(pid), PIDTYPE_PID);
    // 如果没有找到pid对应进程则跳过
    if (task == NULL) {
        printk(KERN_INFO "Process %d not found.\n", pid);
        return -1;
    }
    // 利用__state查看进程状态
    unsigned long state = task->__state;
    if (state == TASK_RUNNING) {
        printk(KERN_INFO "Process %d is running.\n", pid);
    } else if (state == TASK_INTERRUPTIBLE || state == TASK_UNINTERRUPTIBLE) {
        printk(KERN_INFO "Process %d is waiting.\n", pid);
    } else if (state == TASK_STOPPED) {
        printk(KERN_INFO "Process %d is stopped.\n", pid);
    } else if ((int)(state) == 1026){
    	printk (KERN_INFO "Process %d is blocked\n", pid); 
    } else {
        printk(KERN_INFO "Process %d is in state %lu.\n", pid, state);
    }
    
    return 0;
}

该函数首先输出待检查进程的PID,然后使用 pid_task() 函数从内核中获取该进程的 task_struct 结构体。如果该进程不存在,则输出错误信息并返回 -1。如果该进程存在,则读取其状态,并根据状态输出相应的信息。

其中,进程状态的值被存储在 task->__state 成员中,该成员为一个 unsigned long 类型。不同状态的值分别对应。

4. 模块初始化及退出
  • 模块初始化将pid_list中的每个pid都代入target_pid进行检查。
  • 模块退出部分只打印一行退出信息。
static int __init my_init(void) {
    printk(KERN_INFO "*****************Checking Process State*****************\n");
    int pid_list[] = {1, 2, 3, 11, 35, 367, 402, 500};
    for (int i = 0; i < 8; i++){
        target_pid = pid_list[i];
        check_process_state(target_pid);
    }
    return 0;
}

static void __exit my_exit(void) {
    printk(KERN_INFO "*****************Module unloaded***********************\n");
}

module_init(my_init);
module_exit(my_exit);
5. 模块运行结果
(1) 模块运行方法

​ 命令行输入以下命令在内核日志中查看运行结果。

sudo insmod pid2stats.ko
sudo rmmod pid2stats
sudo dmesg
(2)模块运行结果
[  780.682674] *****************Checking Process State*****************
[  780.682678] Process 1
[  780.682680] Process 1 is waiting.
[  780.682680] Process 2
[  780.682681] Process 2 is waiting.
[  780.682682] Process 3
[  780.682682] Process 3 is blocked
[  780.682683] Process 11
[  780.682683] Process 11 is blocked
[  780.682684] Process 35
[  780.682685] Process 35 is waiting.
[  780.682686] Process 367
[  780.682686] Process 367 not found.
[  780.682687] Process 402
[  780.682688] Process 402 is waiting.
[  780.682688] Process 500
[  780.682689] Process 500 not found.
[  786.218657] *****************Module unloaded***********************
(3)模块运行结果比对

命令行输入以下命令查看进程状态。

ps aux

查询结果如下。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cZny0Lez-1679831897351)(./…/…/…/…/WeChat/Downloads/WeChat Files/wxid_3glfzjfjq2qm22/FileStorage/Temp/1679795634820.png)]

其中,进程状态的表示如下。

  • D:不可中断的睡眠状态(通常是IO等待状态)
  • R:正在运行或在运行队列中等待
  • S:休眠状态,通常是指等待某个事件完成或等待输入输出完成
  • T:已停止或已被追踪
  • t:已停止或已被追踪,且已被挂起
  • W:进程处于无记账状态
  • X:进程已死亡
  • Z:进程已终止但是其父进程尚未对其进行清理操作
  • I :进程正在等待某事件发生,被阻塞
(4)运行结果比对
进程pid1231135367402500
pid2stats查询到状态waitingwaitingblockedblockedwaitingnot foundwaitingnot found
ps aux查询到状态SsSI<IS\S\
二、将文件读取结果中的无效pid删除并输出其他进程之间的关系(不包含并发访问文件)

由于直接将读取的进程进行关系输出操作会出现因为部分pid无效而导致进程被杀死的情况,因此需要判断进程的状态(是否存在于主机)再进行进一步筛选。基于上周小组成员提交的read.c进行修改,阅读parser代码后得知读取结果存储在结构体struct parser_result 中,结构体示意如下。

// 解析内核跟踪信息
struct parser_result
{
    int *pids;  // 进程ID序列
    int pids_len;  // 进程ID数量
    char **progs;  // 进程名称序列
    int progs_len;  // 进程名称数量
    char **files;  // 文件名序列
    int files_len;  // 文件数量
};

在此介绍本周任务增量。

1. 检查进程状态

调用函数check_process_state()在上一部分中已经实现并完成测试,函数返回值为int类型,如果进程不存在于主机中则返回-1,否则返回0并输出进程状态。

2. 打印读取进程信息

调用函数check_parser,主要是检查从文件中读取的所有pidprogfile信息。

void check_parser (struct parser_result res){
    int i;
    printk (KERN_INFO "*************Check initial Result*****************\n");
    i = 0;
    for (; i < res.pids_len; i++) {
        pr_info("pids: %ld", res.pids[i]);
    }
    i = 0;
    for (; i < res.progs_len; i++) {
        pr_info("progs: %s", res.progs[i]);
    }
    i = 0;
    for (; i < res.files_len; i++) {
        pr_info("files: %s", res.files[i]);
    }
}
3. 筛选有效pid

调用函数filter_existing_pids,方法是调用函数check_process_state()判断每个pid的状态,如果在主机中则加入到进程列表中。代码如下。

// screen out invalid pids
int filter_existing_pids(int *pid_list, int pid_count, int *new_pid_list) {
    int i, new_count = 0;
    printk (KERN_INFO "*************Check Process States*****************\n");
    for (i = 0; i < pid_count; i++) {
        if (check_process_state(pid_list[i]) == 0) {
            new_pid_list[new_count++] = pid_list[i];
        } 
    }
    return new_count;
}
4. 修正parser结果

调用函数edit_result ()实现思路是:

  • 新建整型数组pid_list存取parser读到的pid序列,用pid_count保存有效的pid数量并更新有效的pid序列。
  • 检查pid_count确保存在有效pid才对数组进行判断。
  • 更新ans的值。
void edit_result (struct parser_result *ans){
    // init, fliter existing pids
    int *pid_list = ans->pids;
    int pid_count = filter_existing_pids(pid_list, ans->pids_len, pid_list);
    printk(KERN_INFO "*************Check valid count*********************\n");
    // check if pid_count > 0
    if (pid_count == 0) {
        printk(KERN_INFO "Process list does not contain valid PID\n");
        return ;
    }
    printk (KERN_INFO "valid count = %d\n", pid_count);
    for (int i = 0; i < pid_count; i++){
    	printk (KERN_INFO "valid pid: %d\n", pid_list[i]);
    }
    // edit ans value
    ans->pids = pid_list;
    ans->pids_len = pid_count;
}
5. 输出进程关系

调用函数check_relations () 主要是用pid_listpid_count接受结构体参数并调用process_relationship()函数,函数说明及测试在上周报告中已经呈现。函数如下。

// print relations between pids
void check_relations (struct parser_result ans){
    int *pid_list = ans.pids;
    int pid_count = ans.pids_len;
    // print relations
    printk(KERN_INFO "*************Print relations***********************\n");
    process_relationship(pid_list, pid_count);
}

为说明方便,在此呈现process_relationship()函数。

// print the relationship of process read
void process_relationship(int *pid_list, int pid_count)
{
    int i, j;

    // 遍历 pid_list 中的进程
    for (i = 0; i < pid_count; i++) {
        struct pid *pid_struct = find_get_pid(pid_list[i]);
        if (!pid_struct) {
            printk(KERN_INFO "Process with PID %d not found\n", pid_list[i]);
            continue;
        }
        struct task_struct *task = pid_task(pid_struct, PIDTYPE_PID);
        // if find_vpid(pid) == NULL, the kernel corupts
        // struct task_struct *task = pid_task(find_vpid(pid_list[i]), PIDTYPE_PID);
        if (task == NULL) {
            printk(KERN_INFO "Process with PID %d not found\n", pid_list[i]);
            continue;
        }

        // 输出进程的父子关系
        if (task->real_parent != NULL) {
            for (j = 0; j < pid_count; j++) {
                // Judge if the process in pid_list
                struct task_struct *another_task = pid_task(find_vpid(pid_list[j]), PIDTYPE_PID);
                if (task->real_parent->pid == another_task->pid){
                    printk(KERN_INFO "Process with PID %d has parent with PID %d\n", task->pid, task->real_parent->pid);
                    break;
                }
            }
        }
        if (!list_empty(&task->children)) {
            struct task_struct *child;
            list_for_each_entry(child, &task->children, sibling) {
                // Judge if the process in pid_list
                for (j = 0; j < pid_count; j++){
                    struct task_struct *another_task = pid_task(find_vpid(pid_list[j]), PIDTYPE_PID);
                    if (child->pid == another_task->pid){
                        printk(KERN_INFO "Process with PID %d has child with PID %d\n", task->pid, child->pid);
                        break;
                    }
                }
            }
        }

        // 输出进程之间的兄弟关系,但是只能找到pid比自身小的
        if (i > 0) {
            for (j = 0; j < i; j++) {
                struct task_struct *sibling_task = pid_task(find_vpid(pid_list[j]), PIDTYPE_PID);
                if (sibling_task != NULL && sibling_task->real_parent == task->real_parent) {
                    printk(KERN_INFO "Process with PID %d and process with PID %d are siblings\n", task->pid, sibling_task->pid);
                }
            }
        }
    }
}
6. 主函数
static int __init test_read_init(void)
{
    pr_info("***********Testing read function****************************\n");
    const char* file_path = "file.txt";
    char buf[BUF_SIZE]={0};
    ssize_t file_string_length = read_file(file_path, buf, 200, 0);
    pr_info("file content: %s", buf);
    pr_info("file length: %ld", file_string_length);
    struct parser_result ans = parse_string(buf, file_string_length);
    
    check_parser (ans);
    // redit parser result
    edit_result (&ans);
    // print relation information
    check_relations (ans);      
    
    return 0;
}
7. 测试输出结果

file.txt文件如下:

pid: 1, 2, 367, 3, 402, 500
prog: program, program1, program2, program3 
file: a filename, filename2, filename3, filename4

其中pid为367和500的节点查询不到,输出结果如下。
在这里插入图片描述

三、未尽事宜——输出并发访问同一文件的进程

尝试任务3中的后半部分,想要先查看某个进程访问的所有文件,做了两种尝试,但是结果不同,不知道问题所在。

  • 遍历fdtable中的文件进行输出。(非本人编写,本人仅调试)
  • 使用iterable_fd加上访问函数实现对进程下的所有文件进行输出。
1. 初始化和退出函数示意
static void __exit proc_exit(void)
{
    printk(KERN_INFO "Module unloaded.\n");
}

static int __init my_module_init(void)
{
    printk (KERN_INFO "**********************File information**********************\n");
    // 方法一
    processiFileInfo(target_pid);
    // 方法二
    print_process_files(target_pid);
    return 0;
}

static void __exit my_module_exit(void)
{
    printk(KERN_INFO "**********************Goodbye, world!************************\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
2. 方法一代码

processiFileInfo 接收一个PID,并使用 pid_task 获取相应的 task_struct。然后,它访问 task_structfiles 字段,并使用自旋锁锁定文件表。它使用 for 循环迭代所有文件描述符,并使用 fget 获取文件。如果文件存在,则使用 printk 打印其名称,并使用 fput 释放它。

static int processiFileInfo(int pid)
{
    struct task_struct *task = pid_task(find_vpid(pid), PIDTYPE_PID);
    struct files_struct *files = task->files;
    struct fdtable *fdt = files_fdtable(files);
    unsigned long fd;

    printk(KERN_INFO "Searching Process File Table\n");
    spin_lock(&files->file_lock);
    for (fd = 0; fd < fdt->max_fds; fd++) {
        if (test_bit(fd, fdt->open_fds)) {
            struct file *file = fget(fd);
            if (file) {
                // 输出文件名
                printk(KERN_INFO "Current file name: %s\n", file->f_path.dentry->d_name.name);
                fput(file);
            }
        }
    }
    spin_unlock(&files->file_lock);
    return 0;
}
3. 方法二代码

print_process_files 也接收一个PID,并以类似的方式检索 task_structfiles_struct。它使用 iterate_fd 函数迭代所有文件描述符,并为每个文件描述符调用 print_file_callback 函数。该函数使用 printk 打印文件描述符号和文件名。

static int print_file_callback(const void *private_data, struct file *file, unsigned int fd)
{
    if (file) {
        printk(KERN_INFO "FD: %d, Name: %s\n", fd, file->f_path.dentry->d_name.name);
    }
    else {
    	printk(KERN_INFO "FD: %d, NULL file\n", fd);
    }
    return 0;
}

void print_process_files(int pid)
{
    struct task_struct *task;
    struct files_struct *files;

    task = pid_task(find_vpid(pid), PIDTYPE_PID);
    if (task == NULL) {
        printk(KERN_INFO "Process with PID %d not found\n", pid);
        return;
    }

    files = task->files;
    if (files == NULL) {
        printk(KERN_INFO "Process with PID %d has no files\n", pid);
        return;
    }

    printk(KERN_INFO "Process with PID %d has the following files:\n", pid);

    iterate_fd(files, 0, print_file_callback, NULL);
}
4. 输出样例

运行时设置target_pid=1000查看运行结果,如下图所示。不知道问题出在哪。希望知道的朋友可以提供一些帮助,万分感谢。
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏洛特兰兰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值