内核模块显示进程控制块信息
[TOC]
Proc 概念
- 在Linux中的
/proc
是一个虚拟文件系统,该目录不包含真正的文件,而是包含着运行时系统的各种信息(系统内存、安装的设备、硬件配置、进程等),是内核的控制和信息中心,被很多系统实时程序调用(lsmod
/cat /proc/modules
),可以改变其中的某些文件来达到改变内核运行状态的目的 (sysctl
) 该文件系统大致包含了
- 与进程相关的目录
- 通用的系统信息
- 网络信息
- 系统控制信息
- 该文件系统中,大部分文件 [夹]的大小都为0,除了
kcore
/mtrr
/self
文件外,这里面的每一个文件都被绑定到一个内核函数上,当文件被读取的时候即时的产生文件内容
- 在
/proc
中以数字命名的文件夹,表示系统当前正在运行的进程的PID,里面包含着与该进程相关的一些文件
cmdline
: 命令行参数。启动当前进程的完整指令,僵尸进程该文件下无内容cwd
: 到当前工作目录的一个链接environ
: 环境变量的值exe
: 当前进程的可执行文件链接,可以使用/proc/pid/exe
启用当前进程的一个拷贝fd
: 文件描述- 太多了,每一个的介绍移步 此处
seq_file
seq_file
- 由于
profs
的默认操作函数只能适用一页的缓存,所以在处理大文件 / 输出多个结构体时时很不方便,所以有一些大佬们就开发出来了伟大的seq_file
(序列文件) seq_file
的头文件定义才如下位置,这里按照新下载的内核加压缩文件为例
其结构体定义如下
struct seq_file { char *buf; size_t size; size_t from; size_t count; size_t pad_until; loff_t index; loff_t read_pos; u64 version; struct mutex lock; const struct seq_operations *op; int poll_event; const struct file *file; void *private; };
buf
表示seq_file
接口使用的缓存页指针size
: 表示缓存页大小from
: 从seq_file
中向用户态缓存区拷贝时,相对于buf
的偏移地址count
:buf
中可以拷贝到用户态的字符数index
:seq_operations
中start
/next
的下标值,seq_operations
定义下文给出read_pos
: 当前已经拷贝到用户态的文件的数据大小- ……其实都是黑匣子,关键还是要自己定义
seq_operations
seq_operations
该结构体也在上一个头文件中,定义如下:
struct seq_operations { void * (*start) (struct seq_file *m, loff_t *pos); void (*stop) (struct seq_file *m, void *v); void * (*next) (struct seq_file *m, void *v, loff_t *pos); int (*show) (struct seq_file *m, void *v); };
- 根据
Linux.die.net
给出的定义,当用户访问/proc
时,seq_file
API就会开始产生一个序列,该序列主要由start()
/stop()
/next()
组成 - 一个序列开始于用户对
start()
函数的调用,若返回值非NULL
,则会首先调用next()
函数的,next()
函数是一个从数据头迭代到数据尾的迭代器,当next()
函数被调用,同时也会紧接着调用show()
函数,show()
函数将会数据值写入用户读取的缓冲区中,读取完毕则返回NULL
,此时会再次唤醒next()
函数,当next()
返回NULL
,也就可以说是读取完毕,那么调用stop()
- 综上分析,
seq_operations
实现了读取数据序列,并且将其写入seq_file
自建的缓存中,所以,只需要写好迭代等函数的接口定义就好了,其他的前辈们都做好了黑箱,下面记录一下对每一个函数接口的理解 start
void * (*start) (struct seq_file *m, loff_t *pos);
- 该方法在 第一次读取
/proc
文件 /stop()
函数被调用之后,会被自动调用 struct seq_file *m
中,m 的类型是一个seq_file
的指针,一般指向当前seq_file
结构体loff_t *pos
中的loff_t
就是一个long long
,于此表示一个位置,某论坛大佬说,不同的版本,不一定代表字节起始地址还是一个元素地址,lwn.net
定义如下
Modules implementing a virtual file with seq_file must implement a simple iterator object that allows stepping through the data of interest. Iterators must be able to move to a specific position - like the file they implement - but the interpretation of that position is up to the iterator itself. A seq_file implementation that is formatting firewall rules, for example, could interpret position N as the Nth rule in the chain. Positioning can thus be done in whatever way makes the most sense for the generator of the data, which need not be aware of how a position translates to an offset in the virtual file. The one obvious exception is that a position of zero should indicate the beginning of the file.
The
/proc/sequence
iterator just uses the count of the next number it will output as its position.start() takes a position as an argument and returns an iterator which will start reading at that position.
- 返回值规定是一个迭代器,该迭代器的整个数据结构是保存当前位置的单个
loff_t
数值
- 该方法在 第一次读取
show
int (*show) (struct seq_file *m, void *v);
- 该方法将格式化当前迭代器所指向的元素以便于输出,返回 0 值表示正常终止
- 该方法就是将
v
指向的元素中的数据,借助seq_file
提供一的一些工具,输出到seq_file
的内部缓存中 下面的几个函数一般用于直接输出字符 / 字符串
int seq_putc(struct seq_file *m, char c); int seq_puts(struct seq_file *m, const char *s); int seq_escape(struct seq_file *m, const char *s, const char *esc);
前两个用于直接输出单个字符 / 字符串,最后一个是任何一个在 `esc` 中的 `s` 中的字符将以八进制的形式输出
下面的函数一般用于打印文件名
int seq_path(struct seq_file *m, struct vfsmount *mnt, struct dentry *dentry, char *esc);
`mnt` 和 `dentry` 中是感兴趣的文件部分, `esc` 是在输出中应该转义的一些字符。原文是这样的
next
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
v
是之前调用start
/next
返回的元素指针,上一个已经完成输出的元素的地址pos
是需要迭代器移动到的元素的索引值- 在
next
返回值非 NULL 是,迭代到下一个输出位置,若返回NULL,那么就调用stop
stop
void (*stop) (struct seq_file *m, void *v);
m
一般指向当前seq_file
结构体,v
一般是指向上一个next
/stop
的元素指针- 一般直接返回就行,除非需要在退出时做某些操作才进行处理
连结上述函数与结构体
seq_operations
集合static struct seq_operations scull_seq_ops = { .start = scull_seq_start, .next = scull_seq_next, .stop = scull_seq_stop, .show = scull_seq_show };
该静态结构体将我们自己写好的与功能相关的 iterator
操作集合包装起来形成一个集合供 seq_file
与其他函数 / 结构体调用
open
函数static int scull_proc_open(struct inode *inode, struct file *file) { return seq_open(file, &scull_seq_ops); }
该方法链接文件到 seq_file
file_operations
结构体static struct file_operations scull_proc_ops = { .owner = THIS_MODULE, .open = scull_proc_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release };
此时,该结构体中的 .open
理应为 自己编写的 my_proc_open
方法,其余的都是原来就预装好的,直接使用即可
创建
/proc
中的实际文件entry = create_proc_entry("pcbshower", 0644, NULL); if (entry) entry->proc_fops = &scull_proc_ops;
此处官方给出的建议是不使用 create_proc_read_entry
,而是调用更加贴近底层的 create_proc_entry
,参数分别表示 文件名,位置,父级目录
内核模块显示进程控制块信息
- 代码存在了Github,可以通过curl 获取,有BUG的话发PR呀,谢谢~GitHub
效果图
/**
* @file pcbshower.c
* @author FancyKing (YellyHornby@qq.com)
* @brief 利用进程控制块显示进程信息
* @version 0.1
* @date 2019-05-25 - 2019-05-26
*
* @copyright Copyright (c) 2019
*
*/
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/sched/signal.h>
#include <linux/sched/task.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#define FILENAME "pcbshower"
struct task_struct *shower;
int task_nums = 0, num_of_running = 0, num_of_interruptible = 0,
num_of_uninterruptible = 0, num_of_zombie = 0, num_of_stopped = 0,
num_of_traced = 0, num_of_dead = 0, num_of_unknown = 0, tmp_exit_state = 0,
tmp_state = 0;
static void *scull_seq_start(struct seq_file *m, loff_t *pos) {
if (*pos == 0) {
shower = &init_task;
return &shower;
} else {
if (shower == &init_task) {
return NULL;
} else {
return (void *)pos;
}
}
}
static void *scull_seq_next(struct seq_file *m, void *v, loff_t *pos) {
(*pos)++;
shower = next_task(shower);
return NULL;
}
static void scull_seq_stop(struct seq_file *m, void *v) {
if (shower == &init_task) {
seq_printf(m, "[M] total tasks : %10d\n", task_nums);
seq_printf(m, "[M] TASK_RUNNING : %10d\n", num_of_running);
seq_printf(m, "[M] TASK_INTERRUPTIBLE : %10d\n", num_of_interruptible);
seq_printf(m, "[M] TASK_UNINTERRUPTIBLE : %10d\n", num_of_uninterruptible);
seq_printf(m, "[M] TASK_TRACED : %10d\n", num_of_stopped);
seq_printf(m, "[M] TASK_TRACED : %10d\n", num_of_stopped);
seq_printf(m, "[M] EXIT_ZOMBIE : %10d\n", num_of_zombie);
seq_printf(m, "[M] EXIT_DEAD : %10d\n", num_of_dead);
seq_printf(m, "[M] UNKNOWN : %10d\n", num_of_unknown);
}
}
int scull_seq_show(struct seq_file *m, void *v) {
seq_printf(m, "#%-3d ", task_nums++);
seq_printf(m, "%7d ", shower->pid);
seq_printf(m, "%5lu ", shower->state);
seq_printf(m, "%-20s ", shower->comm);
seq_printf(m, "%-5d ", shower->prio);
seq_printf(m, "%-5d ", shower->static_prio);
seq_printf(m, "%-5d ", shower->normal_prio);
seq_printf(m, "%-4d ", shower->rt_priority);
seq_printf(m, "%u ", shower->policy);
seq_printf(m, "%-11lu ", shower->nvcsw);
seq_printf(m, "%5lu ", shower->nivcsw);
seq_printf(m, "%13lu ", shower->utime);
seq_printf(m, "%13lu ", shower->stime);
// seq_printf( m, "%lu ", shower->real_start_time.tv_sec);
seq_puts(m, "\n");
tmp_state = shower->state; // put p->state to variable t_state
tmp_exit_state = shower->exit_state; // similar to above
if (tmp_exit_state != 0) {
switch (tmp_exit_state) {
case EXIT_ZOMBIE:
num_of_zombie++;
break;
case EXIT_DEAD:
num_of_dead++;
break;
default:
break;
}
} else {
switch (tmp_state) {
case TASK_RUNNING:
num_of_running++;
break;
case TASK_INTERRUPTIBLE:
num_of_interruptible++;
break;
case TASK_UNINTERRUPTIBLE:
num_of_uninterruptible++;
break;
case TASK_STOPPED:
num_of_stopped++;
break;
case TASK_TRACED:
num_of_traced++;
break;
default:
num_of_unknown++;
break;
}
}
return 0;
}
static struct seq_operations scull_seq_ops = {.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show};
static int scull_proc_open(struct inode *inode, struct file *file) {
return seq_open(file, &scull_seq_ops);
}
static const struct file_operations scull_proc_ops = {.owner = THIS_MODULE,
.open = scull_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release};
int __init shower_init(void) {
struct proc_dir_entry *scull_seq_entry;
printk("Now installing the module: %s \n", FILENAME);
scull_seq_entry = proc_create(FILENAME, 0x644, NULL, &scull_proc_ops);
if (scull_seq_entry == NULL) {
return -ENOMEM;
} else {
return 0;
}
}
void __exit shower_exit(void) {
remove_proc_entry(FILENAME, NULL);
printk("Now removing the module: %s \n", FILENAME);
}
module_init(shower_init);
module_exit(shower_exit);
MODULE_LICENSE("GPL");
当前页面是本站的「Google AMP」版。查看和发表评论请点击:完整版 »