Linux内核中的遍历宏全面详解
Linux内核中大量使用遍历宏(Iteration Macros)来简化数据结构的遍历操作。这些宏提供了类型安全、简洁且高效的遍历方式,是内核编程的核心范式之一。
一、遍历宏的分类
1.1 按功能分类
Linux内核遍历宏
├── 链表遍历宏
│ ├── list_for_each_entry // 遍历标准链表
│ ├── hlist_for_each_entry // 遍历哈希链表
│ ├── list_for_each_entry_safe // 安全遍历(可删除)
│ └── list_for_each_entry_rcu // RCU保护遍历
│
├── CPU遍历宏
│ ├── for_each_possible_cpu // 所有可能的CPU
│ ├── for_each_online_cpu // 在线CPU
│ ├── for_each_present_cpu // 存在的CPU
│ └── for_each_cpu_and // CPU掩码交集
│
├── 进程遍历宏
│ ├── for_each_process // 遍历所有进程
│ ├── for_each_thread // 遍历进程的线程
│ └── for_each_task_pid // 遍历特定PID的进程
│
├── 内存遍历宏
│ ├── for_each_sg // 遍历scatter-gather列表
│ ├── for_each_vma // 遍历虚拟内存区域
│ └── for_each_zone // 遍历内存区域
│
├── 文件系统遍历宏
│ ├── for_each_mount_point // 遍历挂载点
│ ├── for_each_dentry // 遍历目录项
│ └── for_each_inode // 遍历inode
│
├── 设备遍历宏
│ ├── for_each_netdev // 遍历网络设备
│ ├── for_each_pci_dev // 遍历PCI设备
│ └── for_each_irq // 遍历中断
│
└── 定时器遍历宏
├── for_each_timer // 遍历定时器
└── for_each_hrtimer // 遍历高精度定时器
二、链表遍历宏(最常用)
2.1 标准链表遍历
c
#include <linux/list.h>
struct my_struct {
int data;
struct list_head list; // 链表节点
};
struct list_head head;
INIT_LIST_HEAD(&head);
// 1. 基本遍历(不可删除节点)
struct my_struct *entry;
list_for_each_entry(entry, &head, list) {
printk("data: %d\n", entry->data);
}
// 2. 安全遍历(可删除当前节点)
struct my_struct *entry, *tmp;
list_for_each_entry_safe(entry, tmp, &head, list) {
if (entry->data == 0) {
list_del(&entry->list);
kfree(entry);
}
}
// 3. 反向遍历
list_for_each_entry_reverse(entry, &head, list) {
printk("data: %d\n", entry->data);
}
// 4. 从指定位置开始遍历
struct my_struct *entry;
list_for_each_entry_from(entry, &head, list) {
// 从entry开始继续遍历
}
// 5. 带锁的安全遍历
spin_lock(&lock);
list_for_each_entry(entry, &head, list) {
// 在锁保护下遍历
}
spin_unlock(&lock);
2.2 RCU保护的链表遍历
c
#include <linux/rculist.h>
struct my_struct {
int data;
struct list_head list;
struct rcu_head rcu;
};
struct list_head head;
// RCU读锁保护
rcu_read_lock();
struct my_struct *entry;
list_for_each_entry_rcu(entry, &head, list) {
printk("data: %d\n", entry->data);
}
rcu_read_unlock();
// RCU安全删除
list_del_rcu(&entry->list);
call_rcu(&entry->rcu, my_struct_free);
2.3 哈希链表遍历
c
#include <linux/list.h>
#define HASH_SIZE 256
struct hlist_head hash_table[HASH_SIZE];
struct my_struct {
int key;
struct hlist_node node; // 哈希链表节点
};
// 遍历单个哈希桶
struct my_struct *entry;
struct hlist_head *head = &hash_table[hash_index];
hlist_for_each_entry(entry, head, node) {
if (entry->key == target_key) {
// 找到目标
break;
}
}
// 安全遍历(可删除)
struct my_struct *entry, *tmp;
hlist_for_each_entry_safe(entry, tmp, head, node) {
if (entry->key == target_key) {
hlist_del(&entry->node);
kfree(entry);
}
}
// RCU保护的哈希链表遍历
rcu_read_lock();
hlist_for_each_entry_rcu(entry, head, node) {
printk("key: %d\n", entry->key);
}
rcu_read_unlock();
2.4 链表遍历宏详解
| 宏名称 | 用途 | 是否可删除节点 | 线程安全 |
|---|---|---|---|
list_for_each_entry |
标准正向遍历 | 否 | 需外部锁 |
list_for_each_entry_safe |
安全遍历(支持删除) | 是 | 需外部锁 |
list_for_each_entry_reverse |
反向遍历 | 否 | 需外部锁 |
list_for_each_entry_rcu |
RCU保护遍历 | 否 | RCU保护 |
list_for_each_entry_safe_rcu |
RCU安全遍历 | 是 | RCU保护 |
hlist_for_each_entry |
哈希链表遍历 | 否 | 需外部锁 |
hlist_for_each_entry_safe |
哈希链表安全遍历 | 是 | 需外部锁 |
三、CPU遍历宏
3.1 基本CPU遍历
c
#include <linux/cpumask.h>
// 1. 遍历所有可能的CPU
int cpu;
for_each_possible_cpu(cpu) {
struct per_cpu_data *data = per_cpu_ptr(&my_data, cpu);
init_percpu_data(data);
}
// 2. 遍历在线CPU
for_each_online_cpu(cpu) {
smp_call_function_single(cpu, work_func, NULL, 1);
}
// 3. 遍历存在的CPU
for_each_present_cpu(cpu) {
init_cpu_hardware(cpu);
}
// 4. 遍历活跃CPU(用于调度)
for_each_active_cpu(cpu) {
assign_task_to_cpu(cpu);
}
// 5. 遍历CPU掩码的交集
for_each_cpu_and(cpu, cpu_online_mask, cpu_active_mask) {
// 处理既在线又活跃的CPU
}
3.2 CPU遍历宏详解
c
// 实现原理
#define for_each_possible_cpu(cpu) \
for_each_cpu((cpu), cpu_possible_mask)
#define for_each_cpu(cpu, mask) \
for ((cpu) = -1; \
(cpu) = cpumask_next((cpu), (mask)), \
(cpu) < nr_cpu_ids;)
// 实际使用示例
void show_cpu_status(void)
{
int cpu;
char buf[128];
// 打印在线CPU列表
cpumap_print_to_pagebuf(true, buf, cpu_online_mask);
pr_info("Online CPUs: %s\n", buf);
// 为每个在线CPU执行操作
for_each_online_cpu(cpu) {
pr_info("CPU%d: status=%s\n", cpu,
cpu_online(cpu) ? "online" : "offline");
}
}
四、进程遍历宏
4.1 进程遍历
c
#include <linux/sched.h>
// 1. 遍历所有进程
struct task_struct *p;
for_each_process(p) {
printk("Process: %s (PID: %d)\n", p->comm, p->pid);
}
// 2. 遍历进程的所有线程
struct task_struct *thread;
for_each_thread(p, thread) {
printk("Thread: %s (TID: %d)\n", thread->comm, thread->pid);
}
// 3. 遍历特定PID命名空间的进程
struct pid_namespace *pid_ns = task_active_pid_ns(current);
struct task_struct *p;
for_each_process_in_pid_ns(pid_ns, p) {
// 处理该命名空间的进程
}
// 4. 安全遍历(防止进程退出)
struct task_struct *p, *tmp;
read_lock(&tasklist_lock);
for_each_process_safe(p, tmp) {
// 在锁保护下遍历,可以安全地处理
get_task_struct(p); // 增加引用计数
// ... 处理进程 ...
put_task_struct(p);
}
read_unlock(&tasklist_lock);
4.2 进程遍历注意事项
c
// 错误:遍历时可能导致进程退出
for_each_process(p) {
// 进程p可能在这之后退出
printk("%s\n", p->comm); // 危险!
}
// 正确:使用RCU保护
rcu_read_lock();
for_each_process(p) {
// RCU保护下,进程不会退出
printk("%s\n", p->comm);
}
rcu_read_unlock();
// 正确:使用引用计数
struct task_struct *p;
for_each_process(p) {
get_task_struct(p); // 防止进程退出
// 安全使用p
put_task_struct(p);
}
五、内存区域遍历宏
5.1 内存区域遍历
c
#include <linux/mm.h>
// 遍历进程的虚拟内存区域
struct mm_struct *mm = current->mm;
struct vm_area_struct *vma;
down_read(&mm->mmap_lock);
for_each_vma(vma, mm) {
printk("VMA: 0x%lx-0x%lx flags=0x%lx\n",
vma->vm_start, vma->vm_end, vma->vm_flags);
}
up_read(&mm->mmap_lock);
// 遍历内存节点
int nid;
for_each_node(nid) {
struct pglist_data *pgdat = NODE_DATA(nid);
printk("Node %d: start_pfn=0x%lx\n", nid, pgdat->node_start_pfn);
}
// 遍历内存区域
struct zone *zone;
for_each_zone(zone) {
printk("Zone: %s, pages=%lu\n",
zone->name, zone->managed_pages);
}
六、设备遍历宏
6.1 网络设备遍历
c
#include <linux/netdevice.h>
// 遍历所有网络设备
struct net_device *dev;
for_each_netdev(&init_net, dev) {
printk("Device: %s, state=%d\n", dev->name, dev->state);
}
// 安全遍历(可删除设备)
struct net_device *dev, *tmp;
for_each_netdev_safe(&init_net, dev, tmp) {
if (should_remove(dev)) {
unregister_netdev(dev);
}
}
// 遍历特定命名空间的网络设备
struct net *net = current->nsproxy->net_ns;
for_each_netdev(net, dev) {
// 处理该命名空间的设备
}
6.2 PCI设备遍历
c
#include <linux/pci.h>
// 遍历所有PCI设备
struct pci_dev *pdev;
for_each_pci_dev(pdev) {
printk("PCI: %04x:%04x\n", pdev->vendor, pdev->device);
}
// 遍历特定总线的设备
struct pci_bus *bus;
for_each_pci_bus(bus) {
printk("PCI Bus: %02x\n", bus->number);
}
七、文件系统遍历宏
7.1 挂载点遍历
c
#include <linux/mount.h>
// 遍历所有挂载点
struct mount *mnt;
for_each_mount(mnt) {
printk("Mount: %s on %s\n",
mnt->mnt_devname, mnt->mnt.mnt_root->d_name.name);
}
7.2 目录项遍历
c
#include <linux/dcache.h>
// 遍历目录下的所有目录项
struct dentry *parent = dir->dentry;
struct dentry *dentry;
spin_lock(&parent->d_lock);
list_for_each_entry(dentry, &parent->d_subdirs, d_child) {
printk("Dentry: %s\n", dentry->d_name.name);
}
spin_unlock(&parent->d_lock);
八、定时器遍历宏
8.1 定时器遍历
c
#include <linux/timer.h>
// 遍历定时器(调试用)
struct timer_list *timer;
struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]);
spin_lock(&base->lock);
for_each_timer(timer, base) {
printk("Timer: expires=%lu, function=%pS\n",
timer->expires, timer->function);
}
spin_unlock(&base->lock);
九、自定义遍历宏
9.1 创建自定义遍历宏
c
// 1. 定义数据结构
struct my_array {
int *items;
int size;
};
// 2. 创建遍历宏
#define for_each_my_array(item, array) \
for (int __i = 0; \
(__i < (array)->size) && ((item) = &(array)->items[__i], 1); \
__i++)
// 3. 使用
struct my_array arr;
int *item;
for_each_my_array(item, &arr) {
printk("Item: %d\n", *item);
}
// 4. 带索引的遍历宏
#define for_each_my_array_idx(item, idx, array) \
for ((idx) = 0; \
((idx) < (array)->size) && ((item) = &(array)->items[idx], 1); \
(idx)++)
// 使用
int idx;
for_each_my_array_idx(item, idx, &arr) {
printk("arr[%d] = %d\n", idx, *item);
}
9.2 复杂数据结构的遍历宏
c
// 树形结构遍历宏
struct tree_node {
struct tree_node *parent;
struct list_head children;
int data;
};
#define for_each_tree_child(child, parent) \
list_for_each_entry(child, &(parent)->children, node)
#define for_each_tree_descendant(node, root) \
for ((node) = (root); (node); (node) = next_tree_node(node))
// 使用
struct tree_node *root;
struct tree_node *child;
struct tree_node *desc;
for_each_tree_child(child, root) {
printk("Child: %d\n", child->data);
}
for_each_tree_descendant(desc, root) {
printk("Descendant: %d\n", desc->data);
}
十、遍历宏的性能优化
10.1 预取优化
c
// 使用prefetch预取下一个元素
struct my_struct *entry;
list_for_each_entry(entry, &head, list) {
prefetch(entry->list.next); // 预取下一个节点
process_entry(entry);
}
10.2 缓存友好遍历
c
// 不好的遍历(跳跃访问)
for_each_possible_cpu(cpu) {
struct data *d = per_cpu_ptr(&my_data, cpu);
d->counter++; // 可能跨CPU缓存行
}
// 好的遍历(本地访问)
int cpu = get_cpu();
struct data *d = this_cpu_ptr(&my_data);
d->counter++;
put_cpu();
十一、常见错误与调试
11.1 遍历时删除节点的错误
c
// 错误:遍历时删除导致崩溃
struct my_struct *entry;
list_for_each_entry(entry, &head, list) {
if (entry->data == 0) {
list_del(&entry->list); // 错误!破坏了遍历
kfree(entry);
}
}
// 正确:使用_safe版本
struct my_struct *entry, *tmp;
list_for_each_entry_safe(entry, tmp, &head, list) {
if (entry->data == 0) {
list_del(&entry->list);
kfree(entry);
}
}
11.2 遍历时缺少锁保护
c
// 错误:无锁遍历并发修改的链表
list_for_each_entry(entry, &head, list) {
// 其他CPU可能正在修改链表
process_entry(entry);
}
// 正确:使用锁保护
spin_lock(&list_lock);
list_for_each_entry(entry, &head, list) {
process_entry(entry);
}
spin_unlock(&list_lock);
11.3 调试技巧
c
// 打印链表信息
void dump_list(struct list_head *head)
{
struct my_struct *entry;
int count = 0;
pr_info("List dump:\n");
list_for_each_entry(entry, head, list) {
pr_info(" [%d] data=%d, next=%p, prev=%p\n",
count++, entry->data,
entry->list.next, entry->list.prev);
}
pr_info("Total: %d entries\n", count);
}
// 使用lockdep检查锁
lockdep_assert_held(&list_lock);
list_for_each_entry(entry, &head, list) {
// 确保锁已持有
}
十二、总结
12.1 遍历宏的核心特点
- 类型安全:通过宏参数传递类型,避免强制转换
- 简洁高效:减少重复代码,编译器优化好
- 模式统一:所有遍历宏遵循相似的使用模式
- 上下文感知:有些宏需要特定的锁或RCU保护
12.2 选择指南
| 场景 | 推荐遍历宏 | 原因 |
|---|---|---|
| 标准链表操作 | list_for_each_entry |
最常用,性能好 |
| 需要删除节点 | list_for_each_entry_safe |
保存了next指针 |
| RCU保护读取 | list_for_each_entry_rcu |
RCU语义正确 |
| 哈希表遍历 | hlist_for_each_entry |
适合哈希桶 |
| CPU相关操作 | for_each_online_cpu |
只处理在线CPU |
| 进程遍历 | RCU保护的遍历 | 进程可能退出 |
| 网络设备 | for_each_netdev |
网络子系统标准 |
| 内存区域 | RCU或锁保护 | VMA可能改变 |
12.3 最佳实践
- 总是使用正确的遍历宏(普通/安全/RCU)
- 在需要时使用适当的锁保护
- 避免在遍历中长时间阻塞
- 使用prefetch优化长链表遍历
- 注意遍历方向对缓存的影响
- 自定义遍历宏时保持内核风格
遍历宏是Linux内核代码中最常见的模式之一,理解并正确使用它们对于编写高效、安全的内核代码至关重要。