Linux内核并发编程:手把手教你用atomic_inc和atomic_dec实现一个安全的引用计数器

张开发
2026/4/21 17:19:03 15 分钟阅读

分享文章

Linux内核并发编程:手把手教你用atomic_inc和atomic_dec实现一个安全的引用计数器
Linux内核并发编程实战用原子操作构建安全的引用计数器在驱动开发中引用计数是管理资源生命周期的核心机制。想象这样一个场景当多个进程同时打开同一个字符设备时如何确保设备不会被过早释放传统变量加减在并发环境下会导致竞态条件而原子操作正是解决这类问题的银弹。1. 原子操作与引用计数基础原子操作atomic operation指的是不可分割的指令序列要么完整执行要么完全不执行。在Linux内核中atomic_t类型和配套的原子操作函数构成了并发编程的基础设施。让我们先看一个典型的引用计数结构体定义struct my_device { atomic_t refcount; struct cdev cdev; // 其他设备特定字段... };关键原子操作API包括atomic_set(v, i)将原子变量v的值设置为iatomic_read(v)读取原子变量v的当前值atomic_inc(v)原子地增加1无返回值atomic_dec(v)原子地减少1无返回值atomic_inc_and_test(v)增加1后测试是否为0atomic_dec_and_test(v)减少1后测试是否为0注意原子操作的实际实现是架构相关的x86使用lock前缀指令ARM使用LDREX/STREX指令对这些细节被内核完美封装。2. 实现一个完整的设备引用计数器让我们通过一个虚拟字符设备的例子展示引用计数的完整生命周期管理。首先在设备初始化时设置初始值static int mydev_open(struct inode *inode, struct file *filp) { struct my_device *dev container_of(inode-i_cdev, struct my_device, cdev); atomic_inc(dev-refcount); filp-private_data dev; return 0; }在release函数中减少引用计数并在计数归零时释放资源static int mydev_release(struct inode *inode, struct file *filp) { struct my_device *dev filp-private_data; if (atomic_dec_and_test(dev-refcount)) { // 最后一个引用被释放 kfree(dev); printk(KERN_INFO Device memory freed\n); } return 0; }常见错误模式对比错误类型问题表现正确做法忘记增加计数设备可能被提前释放每次获取引用时atomic_inc忘记减少计数内存泄漏每次释放引用时atomic_dec非原子操作计数不准确导致崩溃严格使用原子操作API3. 高级应用场景与性能考量引用计数看似简单但在复杂场景下需要特别注意模块卸载时的处理static void __exit mydev_exit(void) { if (!atomic_dec_and_test(global_ref)) { printk(KERN_WARNING Device still in use!\n); return -EBUSY; } // 安全卸载代码... }内存屏障的使用 在多核系统中有时需要确保内存访问顺序。atomic_inc_return等带返回值的操作隐含了内存屏障// 保证在获取计数值之前的所有内存操作已完成 int count atomic_inc_return(dev-refcount);性能优化技巧对于高频访问的计数器考虑使用percpu变量避免在原子操作周围使用不必要的内存屏障引用计数归零后的资源释放可以延迟到工作队列中执行4. 调试与常见问题排查引用计数问题通常表现为两类症状内存泄漏或非法访问。内核提供了一些调试手段检查计数泄漏# 通过/proc或sysfs暴露当前计数值 cat /sys/class/mydev/refcount内核警告配置#ifdef CONFIG_DEBUG_ATOMIC_SLEEP might_sleep(); // 检测原子上下文中的非法操作 #endif常见陷阱及解决方案计数溢出if (atomic_read(count) INT_MAX) { return -EOVERFLOW; }死锁风险 避免在持有锁时调用可能触发计数字减的操作UAF释放后使用 确保对象释放后所有引用都被置NULL5. 真实案例内核中的引用计数Linux内核自身大量使用引用计数技术。例如在文件系统层struct file { atomic_long_t f_count; // 文件引用计数 // ... };网络协议栈中的sk_buff也使用类似机制struct sk_buff { atomic_t users; // 数据包引用计数 // ... };这些实现给我们的启示引用计数应作为结构体的第一个字段之一提高缓存利用率对于频繁访问的计数器使用更紧凑的数据类型提供明确的引用获取/释放函数隐藏内部实现在实现自己的引用计数时可以借鉴这些成熟模式static inline struct myobj *myobj_get(struct myobj *obj) { if (obj) atomic_inc(obj-refcnt); return obj; } static inline void myobj_put(struct myobj *obj) { if (obj atomic_dec_and_test(obj-refcnt)) kfree(obj); }

更多文章