linux同步机制-原子操作
一、原子操作
在之前介绍信号量的实现原理中,我们已经了解到获取信号量的操作会导致进程的休眠,也就是存在进程的切换,这样会带来很大的系统开销。
针对单个变量的独占访问我们可以采用原子锁的方式来实现进程的同步。原子锁采用原子操作来实现。
1.1 什么是原子操作
原子操作,顾名思义,就是说像原子一样不可再细分不可被中途打断。一个操作是原子操作,意思就是说这个操作是以原子的方式被执行,要一口气执行完,执行过程不能够被OS的其他行为打断,是一个整体的过程,在其执行过程中,OS的其它行为是插不进来的。
原子操作可以保证对一个整型数据的修改是排他性的。Linux内核提供了一系列函数来实现内核中的原子操作,这些函数又分为两类,分别针对位和整型变量进行原子操作。
1.2 整型原子操作
设置原子变量的值:
void atomic_set(atomic_t *v, int i); /* 设置原子变量的值为i */ atomic_t v = ATOMIC_INIT(0); /* 定义原子变量v并初始化为0 */
获取原子变量的值:
atomic_read(atomic_t *v); /* 返回原子变量的值*/
原子变量加/减:
void atomic_add(int i, atomic_t *v); /* 原子变量增加i */ void atomic_sub(int i, atomic_t *v); /* 原子变量减少i */
原子变量自增/自减:
void atomic_inc(atomic_t *v); /* 原子变量增加1 */ void atomic_dec(atomic_t *v); /* 原子变量减少1 */
操作并测试:
int atomic_inc_and_test(atomic_t *v); int atomic_dec_and_test(atomic_t *v); int atomic_sub_and_test(int i, atomic_t *v);
上述操作对原子变量执行自增、自减和减操作后(注意没有加),测试其是否为0,为0返回true,否则返回false。
操作并返回:
int atomic_add_return(int i, atomic_t *v); int atomic_sub_return(int i, atomic_t *v); int atomic_inc_return(atomic_t *v); int atomic_dec_return(atomic_t *v);
上述操作对原子变量进行加/减和自增/自减操作,并返回新的值。
1.3 位原子操作
设置位:
void set_bit(nr, void *addr);
上述操作设置addr地址的第nr位, 所谓设置位即是将位写为1。
清除位:
void clear_bit(nr, void *addr);
上述操作清除addr地址的第nr位, 所谓清除位即是将位写为0。
改变位:
void change_bit(nr, void *addr);
上述操作对addr地址的第nr位进行反置。
测试位:
test_bit(nr, void *addr);
上述操作返回addr地址的第nr位。
测试并操作位:
int test_and_set_bit(nr, void *addr); int test_and_clear_bit(nr, void *addr); int test_and_change_bit(nr, void *addr);
上述test_and_xxx_bit(nr, void*addr)操作等同于执行test_bit(nr, void*addr)后再执行xxx_bit(nr, void*addr)。
二、整型原子操作实现源码
原子操作的实现是和CPU架构相关的,最底层是通过汇编代码实现的,不同的架构汇编代码是不一样的。这里我们只关注ARM架构的实现方式。
2.1 ATOMIC_INIT
ATOMIC_INIT在arch/arm/include/asm/atomic.h中定义:
#define ATOMIC_INIT(i) { (i) }
atomic_t在include/linux/types.h文件中定义:
typedef struct { int counter; } atomic_t;
2.2 atomic_add
整型原子操作相关的函数这里我们只介绍atomic_add,其它的实现自己查看源码,在arch/arm/include/asm/atomic.h中,我们定位到:
#define ATOMIC_OPS(op, c_op, asm_op) \ ATOMIC_OP(op, c_op, asm_op) \ ATOMIC_OP_RETURN(op, c_op, asm_op) \ ATOMIC_FETCH_OP(op, c_op, asm_op) ATOMIC_OPS(add, +=, add) ATOMIC_OPS(sub, -=, sub)
这里定义了一个宏ATOMIC_OPS(op, c_op, asm_op),值为ATOMIC_OP(op, c_op, asm_op) ATOMIC_OP_RETURN(op, c_op, asm_op) ATOMIC_FETCH_OP(op, c_op, asm_op)。
我们以ATOMIC_OPS(add, +=, add)为例,使用宏展开后为:
ATOMIC_OP(add, +, add) ATOMIC_OP_RETURN(add, +, add) ATOMIC_FETCH_OP(add, +, add)
我们定位到源码中宏ATOMIC_OP的定义,这里armv6以上指令集实现:
#define ATOMIC_OP(op, c_op, asm_op) \ static inline void atomic_##op(int i, atomic_t *v) \ { \ unsigned long tmp; \ int result; \ \ prefetchw(&v->counter); \ __asm__ __volatile__("@ atomic_" #op "\n" \ "1: ldrex %0, [%3]\n" \ " " #asm_op " %0, %0, %4\n" \ " strex %1, %0, [%3]\n" \ " teq %1, #0\n" \ " bne 1b" \ : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) \ : "r" (&v->counter), "Ir" (i) \ : "cc"); \ }
armv6及以下指令集实现:
#define ATOMIC_OP(op, c_op, asm_op) \ static inline void atomic_##op(int i, atomic_t *v) \ { \ unsigned long flags; \ \ raw_local_irq_save(flags); \ v->counter c_op i; \ raw_local_irq_restore(flags); \ } \
我们把ATOMIC_OP(add, +, add),在armv6以上指令集中展开:
static inline void atomic_add(int i, atomic_t *v) { unsigned long tmp; int result; prefetchw(&v->counter); __asm__ __volatile__("@ atomic_add "\n" @@后是注释 "1: ldrex %0, [%3]\n" " add %0, %0, %4\n" " strex %1, %0, [%3]\n" " teq %1, #0\n" " bne 1b" : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) @输出部分 : "r" (&v->counter), "Ir" (i) @输入部分 : "cc"); @破坏描述部分 }
亲爱的读者和支持者们,自动博客加入了打赏功能,陆陆续续收到了各位老铁的打赏。在此,我想由衷地感谢每一位对我们博客的支持和打赏。你们的慷慨与支持,是我们前行的动力与源泉。
日期 | 姓名 | 金额 |
---|---|---|
2023-09-06 | *源 | 19 |
2023-09-11 | *朝科 | 88 |
2023-09-21 | *号 | 5 |
2023-09-16 | *真 | 60 |
2023-10-26 | *通 | 9.9 |
2023-11-04 | *慎 | 0.66 |
2023-11-24 | *恩 | 0.01 |
2023-12-30 | I*B | 1 |
2024-01-28 | *兴 | 20 |
2024-02-01 | QYing | 20 |
2024-02-11 | *督 | 6 |
2024-02-18 | 一*x | 1 |
2024-02-20 | c*l | 18.88 |
2024-01-01 | *I | 5 |
2024-04-08 | *程 | 150 |
2024-04-18 | *超 | 20 |
2024-04-26 | .*V | 30 |
2024-05-08 | D*W | 5 |
2024-05-29 | *辉 | 20 |
2024-05-30 | *雄 | 10 |
2024-06-08 | *: | 10 |
2024-06-23 | 小狮子 | 666 |
2024-06-28 | *s | 6.66 |
2024-06-29 | *炼 | 1 |
2024-06-30 | *! | 1 |
2024-07-08 | *方 | 20 |
2024-07-18 | A*1 | 6.66 |
2024-07-31 | *北 | 12 |
2024-08-13 | *基 | 1 |
2024-08-23 | n*s | 2 |
2024-09-02 | *源 | 50 |
2024-09-04 | *J | 2 |
2024-09-06 | *强 | 8.8 |
2024-09-09 | *波 | 1 |
2024-09-10 | *口 | 1 |
2024-09-10 | *波 | 1 |
2024-09-12 | *波 | 10 |
2024-09-18 | *明 | 1.68 |
2024-09-26 | B*h | 10 |
2024-09-30 | 岁 | 10 |
2024-10-02 | M*i | 1 |
2024-10-14 | *朋 | 10 |
2024-10-22 | *海 | 10 |
2024-10-23 | *南 | 10 |
2024-10-26 | *节 | 6.66 |
2024-10-27 | *o | 5 |
2024-10-28 | W*F | 6.66 |
2024-10-29 | R*n | 6.66 |
2024-11-02 | *球 | 6 |
2024-11-021 | *鑫 | 6.66 |
2024-11-25 | *沙 | 5 |
2024-11-29 | C*n | 2.88 |

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了