加入星计划,您可以享受以下权益:

  • 创作内容快速变现
  • 行业影响力扩散
  • 作品版权保护
  • 300W+ 专业用户
  • 1.5W+ 优质创作者
  • 5000+ 长期合作伙伴
立即加入
  • 正文
  • 相关推荐
  • 电子产业图谱
申请入驻 产业图谱

手把手教你获取Linux所有进程信息

2021/08/30
910
阅读需 9 分钟
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

很多时候,我们要监控系统状态,即监控系统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("nn");//方便查看后续打印信息
  /*
  打印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("进程的个数:%dn",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 内核要需要是同一个版本,否则挂载不上去。

相关推荐

电子产业图谱

研究生在读,熟悉硬件、STM32单片机、嵌入式Linux。已收获小米、联发科、浙江大华、上能电气、英威腾、汇川技术、格力、富士康等大厂offer。在这里分享求职经验、嵌入式学习规划、考研、嵌入式Linux技术文章等。