往期内容:
(一)Linux内核输出Helloworld
(二)命令行输入模块变量&分模块编写
上期内容:
(三)判断进程关系及打印进程树
课程任务:开发一个内核模块或组件,完成如下功能:
- 读取
∼/targets
文件。格式如下,
pid: 100, 234
prog: program, another program
file: a filename; another filename
- 记录
pid
或prog
进程访问了哪些文件和 IP 地址,记录file
文件被哪些进程所访问。记录内容至少包括进程 pid 和程序名、日期时间、访问模式。 - 当进程或文件数量不大于 5 个,展示它们的关系,例如,进程之间的父子关系,某个文件被哪些进程并发访问。
- 支持记录最多 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)运行结果比对
进程pid | 1 | 2 | 3 | 11 | 35 | 367 | 402 | 500 |
---|---|---|---|---|---|---|---|---|
pid2stats 查询到状态 | waiting | waiting | blocked | blocked | waiting | not found | waiting | not found |
ps aux 查询到状态 | Ss | S | I< | I | S | \ | 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
,主要是检查从文件中读取的所有pid
,prog
和file
信息。
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_list
和pid_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_struct
的 files
字段,并使用自旋锁锁定文件表。它使用 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_struct
和 files_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
查看运行结果,如下图所示。不知道问题出在哪。希望知道的朋友可以提供一些帮助,万分感谢。