
大家好,我是情报小哥~
在嵌入式Linux系统开发中,我们常常需要监控系统进程状态。应用层有现成的top、ps命令,或者通过读写/proc文件系统就能轻松获取进程信息。但当你需要在内核驱动层面实现进程监控时,这些方法似乎不太行得通。
今天,小哥就跟大家来深入探讨嵌入式Linux驱动开发中,如何正确、安全地获取系统所有进程信息,上法力~

很多刚接触内核开发的嵌入式工程师可能会想:既然popen("ps", "r")能在应用层工作,我在驱动里调用它不就行了?或者直接在内核里读取/proc节点?
这种想法存在严重的隐患:
top、ps这些命令依赖用户态运行时环境,内核模块中调用它们会导致系统挂起或崩溃/proc文件系统本身就是内核向用户态暴露信息的接口,驱动再去读取它,相当于“自己找自己要信息”,逻辑上存在风险所以驱动是底层,不能依赖上层,所以只有一个办法了:直接访问内核原生的进程管理数据结构。
Linux内核通过一个庞大的结构体task_struct来管理每个进程,它定义在<linux/sched.h>中。这个结构体包含了进程的全部信息:
这些跟RTOS管理线程类似,实际上,task_struct包含的信息远超/proc/[pid]暴露的内容。嵌入式开发中,如果我们需要深度监控系统行为,直接访问这些字段往往能得到更全面、更实时的数据。
内核将所有进程的task_struct通过一个双向循环链表串联起来。链表的头结点是init_task,也就是我们常说的0号进程(swapper/idle进程)。只要掌握了这个链表,遍历系统所有进程就轻而易举。
下面是一个完整的内核模块示例,展示了如何遍历进程链表并打印关键信息。
#include<linux/module.h>#include<linux/kernel.h>#include<linux/init.h>#include<linux/sched.h>#include<linux/pid.h>#include<linux/mm.h>staticint __init list_task_init(void){struct task_struct *task;struct list_head *pos;int count = 0;printk(KERN_INFO "=== Start listing all processes ===\n");// 从init_task开始遍历进程链表list_for_each(pos, &init_task.tasks) {task = list_entry(pos, struct task_struct, tasks);count++;// 打印进程核心信息printk(KERN_INFO "[%d] PID: %d | Name: %s | State: %ld | ""Priority: %d | Parent PID: %d | Files: %u | VM: %luKB\n",count,task->pid,task->comm,task->state,task->prio,task->parent->pid,atomic_read(&task->files->count),task->mm ? task->mm->total_vm << (PAGE_SHIFT - 10) : 0);}printk(KERN_INFO "=== Total processes: %d ===\n", count);return 0;}staticvoid __exit list_task_exit(void){printk(KERN_INFO "Process listing module exited\n");}module_init(list_task_init);module_exit(list_task_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("Embedded Developer");MODULE_DESCRIPTION("List all processes from kernel driver");
1、不同Linux内核版本中,task_struct的字段名可能有变化。例如:
task->pid,较新版本用task->tgid(线程组ID)开发时务必核对当前内核版本的sched.h头文件。
2、示例中打印了task->mm->total_vm,但task->mm可能为NULL(内核线程),必须做空指针检查。此外,mm结构体可能在进程退出过程中被释放,在SMP系统上建议使用task_lock(task)保护。
3、进程链表在系统运行中会动态变化(fork、exit)。如果你的模块在遍历过程中可能发生进程创建/退出,需要使用RCU(Read-Copy-Update)机制来保证遍历的安全性
编写好模块后,再写个Makefile。记得要指定已编译的内核源码路径,否则会报错:
# 修改为你的内核源码路径KERNEL_DIR := /home/embedded/linux-kernelobj-m := list_task.oall:$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modulesclean:$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
编译后生成list_task.ko,在开发板上通过insmod list_task.ko加载,就能在dmesg中看到打印的所有进程信息。
对于实际产品化的驱动,建议将遍历逻辑放在字符设备的read回调函数中,用户态访问设备节点时触发输出,而不是在模块加载时一次性打印,不然对内核日志缓冲区压力还蛮大的。
小哥搜集了一些嵌入式学习资料,公众号内回复【1024】即可找到下载链接!
推荐好文点击蓝色字体即可跳转
☞专辑|Linux应用程序编程大全 ☞ 专辑|学点网络知识 ☞ 专辑|手撕C语言 ☞ 专辑|手撕C++语言
☞ 专辑|经验分享 ☞ 专辑|从单片机到Linux ☞ 专辑|电能控制技术 ☞ 专辑|嵌入式必备数学知识 ☞ MCU进阶专辑
☞ 经验分享