2基于proc的Linux进程控制块信息读取-实验2:创建显示系统进程信息的proc模块

2基于proc的Linux进程控制块信息读取-实验2:创建显示系统进程信息的proc模块

一.实验目的

·了解Linux进程控制块task_strcut,并理解其重要成员变量的含义
·理解基于seq_file机制的proc伪文件操作机制
·熟练掌握生成proc伪文件的Linux内核模块代码实现方法
·认识Linux的进程概念,深入理解操作系统的进程概念

二.实验背景

1.Linux进程控制块

在这里插入图片描述

·Linux的进程控制块PCB使用task_struct结构体进行描述
·进程概念
·进程控制块概念
·进程控制块中保存的信息是进程在执行过程的快照,包含右图中用户进程在用户空间中的分布信息,但不仅仅局限于这些信息。
·进程控制块位于内核空间,只能被特权级别的进程,即操作系统进程操作,而不能被用户程序操作
·换句话说,用户程序不能直接获取进程信息,必须通过OS。
·task_struct结构体定义在<linux/sched.h>头文件中
struct task_struct { /*Linux 3.13.0内核 */
volatile long state; //进程状态
void *stack; //进程内核栈
unsigned long flags; //进程标记
struct mm_struct *mm; //进程内存结构体
struct thread_struct *thread; //所包含的线程的结构体
pid_t pid; //进程唯一标识符
pid_t tgid; //线程组对应的进程标识符
char comm[TASK_COMM_LEN]; //进程名称
…/*其他省略 */
};
在这里插入图片描述

·Linux中的进程状态和转换
#define TASK_RUNNING 0 //表示运行或可运行
#define TASK_INTERRUPTIBLE 1 //进程正在睡眠或者说阻塞,但可被唤醒
#define TASK_UNINTERRUPTIBLE 2 //进程正在睡眠或者说阻塞,而且不能被唤醒
#define __TASK_STOPPED 4 //进程停止执行
#define __TASK_TRACED 8 //进程可以被追踪,比如处于调试状态
/* in tsk->exit_state */
#define EXIT_ZOMBIE 16
#define EXIT_DEAD 32
在这里插入图片描述

2.LKM模块操作Proc文件的方式

·Proc文件只能由Linux内核代码创建。
·Porc文件有一套规定的操作函数和方法。
·Linux内核3.x.x版本中proc文件的访问方式与2.x.x版本相比发生了重大改变。网络上很多proc的编程说明都只适用于老版本内核
·Proc文件包括两个级别的操作:
·文件级别的操作:创建proc时指定操作函数结构体
·记录级别的操作:打开proc文件时指定操作函数结构体
·Proc记录采用seq_file机制(序列文件机制)进行操作
·seq_file机制的相关函数和定义说明包含在<linux/seq_file.h>头文件中
在这里插入图片描述

三.关键代码及分析

·Proc文件创建函数(proc伪文件是一种内核数据结构,它只能被内核模块操作)
static inline struct proc_dir_entry *proc_create(const char *name, umode_t mode, struct proc_dir_entry *parent,conststruct file_operations *proc_fops)
-第一个参数name是要创建的proc伪文件的名称
-第二个参数mode是定义该伪文件的属性,以UGO(User-Group-Other)的模式表示
-第三个参数parent表示要创建的proc伪文件所在的文件夹。(为NULL 直接在/proc目录下创建)
-第四个参数proc_fops是一个采用C99语法定义的结构体。该结构体包含了对创建的tasklist伪文件所定义的各个操作函数。

·一个创建示例
static const struct file_operations my_proc =
{
.owner = THIS_MODULE, //表明该结构定义的函数用于本内核模块
.open = my_open, // open操作由自定义的my_open函数实现
.read = seq_read, //read操作由系统的seq_read来完成
.llseek = seq_lseek, //llseek操作由系统的seq_llseek来完成
.release = seq_release //release操作由系统的seq_resease来完成
}; //先定义proc文件操作函数结构体,并实现自己定义的函数,如my_open

·在定义的proc文件操作函数my_proc中的open函数中指明使用该结构体
static int my_open(struct inode *inode, struct file *file)
{
return seq_open(file, &my_seq_fops);
}

·模块初始化函数中调用proc_create过程:
char modname[] = “tasklist”;//声明要创建的proc文件的名称,此处为“/proc/tasklist”
struct proc_dir_entry* my_proc_entry;
my_proc_entry = proc_create(modname, 0, NULL, &my_proc);

·seq_file机制
seq_printf( struct seq_file *, const char *, … );
seq_printf( m, "%3d ", taskcounts ); —>将taskcounts变量按照格式%3d写入到m的缓冲区中,m是struct seq_file *类型的变量。

·首先定义记录操作函数结构体
static struct seq_operations my_seq_fops = {
.start = my_seq_start,
.next = my_seq_next,
.stop = my_seq_stop,
.show = my_seq_show
};
start、next、stop、show分别调用用户自己设计的函数。

·proc记录操作
·struct seq_file * m:表示操作的序列对象
·loff_t * pos: 表示序列对象的位置或者序列号。该变量可以由自己的程序控制,初始化为0.
·void * v:表示序列对象的入口位置或则说地址

·star函数工作机制
static void * my_seq_start(struct seq_file *m, loff_t *pos)
{
///printk(KERN_INFO"Invoke start\n"); //可以输出调试信息
if ( pos == 0 ) // 表示遍历开始
{
task = &init_task; //遍历开始的记录地址
return &task; //返回一个非零值表示开始遍历
}
else //遍历过程中
{
if (task == &init_task ) //重新回到初始地址,退出
return NULL;
return (void
)pos ;//否则返回一个非零值
}
}
该函数使用的init_task是一个Linux系统的全局变量,定义在<linux/sched.h>中,表示这是系统进程中的第一个进程,其类型是task_struct。my_seq_start 函数的功能是:当前序列对象位置为0时,也就是从头开始遍历时,遍历对象的入口位置为init_stak 的位置;当序列对象位置不为0时,如果遍历对象的位置等于init_task,说明遍历结束,返回NULL;否则返回一个不为0的位置,表示继续工作。
在这里插入图片描述

·show函数工作机制
static int my_seq_show(struct seq_file *m, void *v)
{//获取进程的相关信息
//printk(KERN_INFO"Invoke show\n");
seq_printf( m, "#%-3d ", taskcounts++ );
seq_printf( m, "%5d ", task->pid );
seq_printf( m, "%lu ", task->state );
seq_printf( m, "%-15s ", task->comm );
seq_puts( m, “\n” );
return 0;
}
该函数完成进程信息的显示,使用seq_printf、seq_puts 等函数来输出信息。输出的信息包括:当前序列对象的进程的 pid state 和 comm,也就是进程的标识符、状态和名称。

·next 函数工作机制
static void * my_seq_next(struct seq_file *m, void *v, loff_t *pos)
{
//printk(KERN_INFO"Invoke next\n");
(*pos)++;
task= next_task(task); //指向下一个进程
return NULL;
}
该函数完成遍历过程中下一个序列对象的查找。(*pos) ++ 表示序列对象位置移动。next_task 也是在<linux\sched.h>中给出的宏定义,就是找到下一个进程控制块的位置。
在这里插入图片描述

·stop 函数工作机制
static void my_seq_stop(struct seq_file *m, void *v)
{
//printk(KERN_INFO"Invoke stop\n");
// do nothing
}
此处stop 函数什么也没做;stop 函数主要完成一些清理工作。如果在前面的函数中使用了内存分配、加锁等,都必须在stop中完成回收内存、解锁的工作。

四.实验结果与分析

·在tasklist.c和Makefile文件存在的当前工作目录下,运行make指令:

make

在这里插入图片描述
通过ls命令查看是否生成tasklist.ko 文件。确认 tasklist.ko 文件后,在工作目录输入
在这里插入图片描述

sudo insmod tasklist.ko

在这里插入图片描述

lsmod 查看内核中已加载的模块

在这里插入图片描述

dmesg 查看日志信息

在这里插入图片描述

sudo rmmod tasklist.ko 从内核中撤销模块

在这里插入图片描述

dmesg 查看日志

在这里插入图片描述

·执行ls /proc 命令查看确认在/proc 目录下生成了tasklist 文件
·执行加载操作前:在这里插入图片描述
·执行记载操作后: 在这里插入图片描述

·首先完成内核模块的编译、动态添加。在内核模块中创建/proc/tasklist 伪文件。
在这里插入图片描述
·当用户执行 cat /proc/tasklist 命令时,内核查找对应的 struct file_opertions my_proc 来找到操作该文件所使用的的函数。在打开时内核执行my_open函数,该函数将定义的struct seq_operation my_seq_fops 与序列文件绑定,说明操作tasklist 伪文件需要使用哪些函数来进行遍历操作。在序列文件打开后,应用程序cat中的读操作会调用内核模块中指定的seq_read 函数来完成读取操作
·seq_show等函数在完成工作后,seq_file 文件缓冲区的数据有操作系统返回给cat进程。Cat进程会调用printf将其显示在屏幕上。

在这里插入图片描述

·执行cat /proc/tasklist 时,read函数的操作流程
在这里插入图片描述

发布了170 篇原创文章 · 获赞 9 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/wct3344142/article/details/104132940