Linux内核为保证多线程、多核环境下整型数据操作的安全性,专门设计了 atomic_t结构体,用来实现整型数据的原子操作,把普通整型变量升级成“原子变量”,避免操作过程中出现数据混乱。
1. 原子变量的基础:定义与初始化
atomic_t是内核封装的核心结构,本质上是把整型变量包裹起来,确保对其的操作不可中断。结构体的核心就是一个整型成员 counter,所有原子操作都围绕它展开。
实际使用时,我们首先要创建原子变量,有两种方式:
直接定义变量,不过初始值需要后续设置;
定义时直接初始化,借助宏 ATOMIC_INIT给变量赋初始值,比如初始化为0,写法是 atomic_t b = ATOMIC_INIT(0);,简单高效,适合静态场景。
2. 原子变量的常用操作:读、写、增减
定义好原子变量后,内核提供了一系列配套API,专门用于操作原子变量,保证每次操作都是原子性的,不会被其他线程或核心打断。
基础操作:用 atomic_read读取变量当前值,用 atomic_set给变量设置指定数值,完成基本的读写需求。
增减操作:
针对计数场景,内核提供了多种增减方法。想给变量加一个固定值,就用 atomic_add;想减固定值,就用 atomic_sub;单纯做自增,用 atomic_inc,自减则用 atomic_dec,操作直接且安全。
还有带返回值的增减操作,
比如 atomic_inc_return和 atomic_dec_return,执行自增或自减的同时,直接返回操作后的最新值,方便我们实时获取变量状态。
条件判断类操作:这类操作更贴合实际业务,比如 atomic_dec_and_test,会对变量自减1,然后判断自减后的结果是否为0,如果为0就返回真,否则返回假,常用于判断计数器是否归零,以此触发后续逻辑,比如释放资源。
类似的还有 atomic_sub_and_test和 atomic_inc_and_test,分别用于自减指定值、自增后判断是否归零,而 atomic_add_negative则会在操作后判断结果是否为负,满足不同场景的判断需求。
操作时只需用内核提供的API,先定义并初始化原子变量,后续按需调用对应函数即可。比如先初始化变量为0,再用设置函数赋值,读取验证后做自增操作,整个流程清晰可控,不用考虑并发带来的安全问题。
另外要注意,若使用64位处理器,内核还提供了对应的64位原子变量 atomic64_t,所有API函数的用法和32位版本完全一致,只是名称把 atomic_前缀换成 atomic64_,数据类型从int换成long long。但如果用的是32位架构,比如书中提到的Cortex-A7,直接用32位的原子操作函数就足够了。
原子位操作:
直接操控内存中的每一位除了整型数据,对内存中的位进行操作也是内核开发里的高频操作,比如标记设备状态。Linux内核直接提供了一系列原子位操作函数,无需额外定义结构体,直接对内存地址操作,高效又方便。
常用原子位操作函数
基础位操作:想将某个内存地址的第n位设置为1,用 set_bit;想清0,用 clear_bit;想直接翻转该位的状态,用 change_bit。想查看某一位的当前值,用 test_bit,直接返回该位的0或1,操作简单直观。
带返回值的位操作:这类函数在操作位的同时,还会返回操作前该位的原始值,适合需要判断初始状态的场景。
比如 test_and_set_bit,先获取第n位的旧值,再把该位置为1,最后返回旧值;test_and_clear_bit则是先读旧值,再把位清0;test_and_change_bit先读旧值,再翻转该位,同样返回旧值,满足更灵活的业务需求。
原子位操作的典型用法
在实际开发中,原子位操作常用来标记设备状态,比如用一个变量表示设备的忙闲状态。操作时,用 set_bit标记设备为忙,用 test_bit检查设备状态,若需要修改状态,用对应的带返回值函数,既完成操作,又能知晓之前的状态,精准控制业务流程,且全程都是原子操作,不会出现状态混乱的问题。
整体总结
原子操作是内核并发控制的核心,分两种场景适配不同需求:
若需要维护计数类数据,比如引用计数、资源使用次数,就用整型原子变量 atomic_t,配合对应的API操作,保证计数准确。
若只需标记多种布尔状态、管理位图,直接用原子位操作函数,无需额外结构体,直接对内存位操作,轻便高效。
核心原则是,简单计数选整型原子变量,复杂状态标记用原子位操作,同时尽量让原子操作的逻辑简洁,缩小临界区,减少系统负担,这也是内核开发中提升稳定性和性能的关键。