很多时候,我们要监控系统状态,即监控系统cpu负载、进程状态等情况,如果我们在 Linux 应用层,我们有很多方式,命令行中常用 top、ps 命令,代码中,我们可以使用 popen 函数去执行一个 top 命令,获取返回值。或者我们直接读写 /proc下面的文件,都可以达到目的。
但如果要你在内核(驱动)中去获取这些信息,你无法执行 top 命令。即便可以使用 flip_open 函数和加 vfs_read 内核函数去读写 /proc 节点文件,但 Linux 本身不建议这样做,这会破坏节点(驱动)之间的独立性,如果产生依赖关系,很可能产生各种各样的问题:如果你在一个节点驱动中读写另外一个节点的内容,而另外一个挂载出了问题,系统就会不稳定。
所以我们一般直接从 Linux 内核本身获取信息,去分析 Linux 内核源码,从他本身的数据结构(结构体、变量、链表)中获取信息。
今天教大家如何在驱动中直接获取 linux 系统中所有进程信息。进程有很多信息,在 /proc/[pid] 目录中中只放了很少一部分,我们访问内核数据结构可以获取全部所有信息。
我们知道Linux系统管理进程是使用PCB(process control block),进程控制块,内核使用一个结构体描述它,这个结构体现在有600多行,叫 task_struct 结构体,这个结构体在 linux 内核源码 linux/include/linux/sched.h 中。
task.c
# include <linux/kernel.h>
# include <linux/module.h>
# include <uapi/linux/sched.h>
# include <linux/init_task.h>
# include <linux/init.h>
# include <linux/fdtable.h>
# include <linux/fs_struct.h>
# include <linux/mm_types.h>
//内核模块初始化函数
static int __init traverse_pcb(void)
{
struct task_struct *task, *p;//定义指向task_struct类型的指针
struct list_head *pos;//定义双向链表指针
int count=0;//定义统计系统进程个数的变量
printk("Printf process'message begin:\n");//提示模块开始运行
task = &init_task;//指向0号进程的PCB
list_for_each(pos,&task->tasks)//使用list_for_each宏来遍历进程链表
{
p = list_entry(pos,struct task_struct,tasks);//指向当前进程的task_struct结构
count++;//统计系统进程个数
printk("\n\n");//方便查看后续打印信息
/*
打印task_struct中的字段.comm:name.pid:进程的pid号;state:进程的状态;
prio:动态优先级;static_prio:静态优先级; parent'pid:父进程的pid号;
count:文件系统信息,文件被使用的次数; umask:进程权限位的默认设置;
使用atomic_read原子操作是为了(p->files)->count字段计数不被打断
*/
printk("comm:%s; pid:%d; state:%lx; prio:%d; static_prio:%d; parent'pid:%d; count:%d; umask:%d;", \
p->comm,p->pid,p->state,p->prio,p->static_prio,(p->parent)->pid, \
atomic_read((&(p->files)->count)),(p->fs)->umask);
//打印进程地址空间的信息
if((p->mm)!=NULL)
printk("total_vm:%ld;",(p->mm)->total_vm);//total_vm:线性区总的页数
}
printk("进程的个数:%d\n",count);//打印进程个数
return 0;
}
//内核模块退出函数
static void __exit end_pcb(void)
{
printk("traverse pcb is end.");
}
module_init(traverse_pcb);//入口
module_exit(end_pcb);//出口
MODULE_LICENSE("GPL");//许可证
Makefile
KERNELDIR := /home/book/linux/tool/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := task.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译
我们会编译出一个task.ko内核模块,把它拷贝到板子中,进行挂载。
挂载上以后就会触发 _init 函数,就会打印。当然我们也可以把这段代码放在 xxx_read 函数中,在 _init 函数中创建节点,访问一次节点就打印一次,都可以。
博主这个系统进程很少,只有 70-80 个,是 buildroot 自己编译的文件系统,没有什么功能。
我们通过定义的p结构体指针,可以访问系统中所有进程的 io、运行时间、内存信息、进程被调用次数,任何和进程有关的信息都存在于 task_struct 中。
Linux 内核采用 task_struct 来描述一个进程。当系统起来以后,随着 init(pid=1)进程 fork 出其他进程,会有一个双向链表,将所有的由 init 创建的进程串起来,我们通过遍历这个双向链表,进而获取所有进程的 task_struct 结构体,把信息取出来。在驱动中这样做,远比访问 /proc 文件方便多了。
note:编译之前记得准备好你的 Linux 内核源码,因为编译需要引用头文件,所以我们在 Makefile 中写明 Linux 内核源码目录(源码必须是编译过的源码,编译 Linux 大概需要半个多小时)。另外需要注意,你编译驱动所引用的内核和你板子中真正运行的 Linux 内核要需要是同一个版本,否则挂载不上去。