当前位置:首页>Linux>嵌入式Linux驱动中如何安全获取系统所有进程信息?

嵌入式Linux驱动中如何安全获取系统所有进程信息?

  • 2026-10-11 05:48:19
嵌入式Linux驱动中如何安全获取系统所有进程信息?

大家好,我是情报小哥~

在嵌入式Linux系统开发中,我们常常需要监控系统进程状态。应用层有现成的top、ps命令,或者通过读写/proc文件系统就能轻松获取进程信息。但当你需要在内核驱动层面实现进程监控时,这些方法似乎不太行得通。

今天,小哥就跟大家来深入探讨嵌入式Linux驱动开发中,如何正确、安全地获取系统所有进程信息,上法力~

1、内核驱动不用应用层的方法

很多刚接触内核开发的嵌入式工程师可能会想:既然popen("ps", "r")能在应用层工作,我在驱动里调用它不就行了?或者直接在内核里读取/proc节点?

这种想法存在严重的隐患:

  • 内核态无法执行用户态命令:top、ps这些命令依赖用户态运行时环境,内核模块中调用它们会导致系统挂起或崩溃
  • 读写/proc会造成循环依赖:/proc文件系统本身就是内核向用户态暴露信息的接口,驱动再去读取它,相当于“自己找自己要信息”,逻辑上存在风险
  • 破坏驱动独立性:一个合格的内核驱动不应该依赖任何用户态组件,否则移植性、稳定性都会大打折扣

所以驱动是底层,不能依赖上层,所以只有一个办法了:直接访问内核原生的进程管理数据结构。

2、内核如何管理进程

Linux内核通过一个庞大的结构体task_struct来管理每个进程,它定义在<linux/sched.h>中。这个结构体包含了进程的全部信息:

  • 进程标识:PID、进程名称、父进程指针
  • 进程状态:运行、睡眠、僵尸等
  • 调度信息:优先级、时间片、运行时间统计
  • 内存管理:内存映射、页表指针
  • 文件系统:打开的文件列表、文件系统根目录
  • 还有很多很多……

这些跟RTOS管理线程类似,实际上,task_struct包含的信息远超/proc/[pid]暴露的内容。嵌入式开发中,如果我们需要深度监控系统行为,直接访问这些字段往往能得到更全面、更实时的数据。

内核将所有进程的task_struct通过一个双向循环链表串联起来。链表的头结点是init_task,也就是我们常说的0号进程(swapper/idle进程)。只要掌握了这个链表,遍历系统所有进程就轻而易举。

3、遍历进程链表

下面是一个完整的内核模块示例,展示了如何遍历进程链表并打印关键信息。

#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)机制来保证遍历的安全性

4、编译与运行:Makefile配置要点

编写好模块后,再写个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回调函数中,用户态访问设备节点时触发输出,而不是在模块加载时一次性打印,不然对内核日志缓冲区压力还蛮大的。

END

来源:嵌入式情报局


版权归原作者所有,如有侵权,请联系删除。
▍推荐阅读
从夯到拉,锐评32个AI编程工具!
用VSCode打造嵌入式Linux开发的终极IDE
B-52轰炸机内部,没有MCU的时代,一台纯机械设备,竟能计算天空坐标
→点关注,不迷路←

最新文章

随机文章