rk3288 原子操作和原子位操作
一、原子变量内核操作函数
Linux中有2中原子操作: 原子变量、原子位。
原子变量的内核操作函数
原子变量的操作函数在arch/arm/include/asm/atomic.h中
原子变量类型如下,实际上就是个结构体include/linux/types.h
typedef struct {int counter;
} atomic_t
原子操作函数,如下:
函数名 | 作用 |
---|---|
atomic_read(v) | 读出原子变量的值,即v->counter |
atomic_set(v, i) | 设置原子变量的值,即v->counter = i |
atomic_inc(v) | v->counter++ |
atomic_dec(v) | v->counter– |
atomic_add(i, v) | v->counter += i |
atomic_sub(i, v) | v->counter -= i |
atomic_inc_and_test(v) | 先加1,再判断新值是否等于0;等于0的话,返回值为1 |
atomic_dec_and_test(v) | 先减1,再判断新值是否等于0;等于0的话,返回值为1 |
二、原子变量内核实现
在Linux内核文件arch\arm\include\asm\atomic.h中。
执行atomic_read、atomic_set
这些操作都只需要一条汇编指令,所以它们本身就是不可打断的。
需要特别研究的是atomic_inc、atomic_dec
这类读出、修改、写回的函数。
以atomic_inc
为例,在atomic.h文件中,如下定义:
#define atomic_inc(v) atomic_add(1, v)
但是atomic_add在内核中是很难找到的,因为没有这个直接的声明。而是一种宏实现。
首先,在arch\arm\include\asm\atomic.h中有一个__LINUX_ARM_ARCH__判断:
当架构不同时使用不同的原子实现,armv6以下不支持SMP1结构。
所以atomic_add的原型是下面这个宏:
#define ATOMIC_OPS(op, c_op, asm_op) \ATOMIC_OP(op, c_op, asm_op) \ATOMIC_OP_RETURN(op, c_op, asm_op)ATOMIC_OPS(add, +=, add)
在ATOMIC_OPS的实现下面有一个ATOMIC_OPS(add, +=, add)
的声明,把括号里面的内容替换的结果是:
/* 替换后,发现ATOMIC_OPS定义了两个函数 */
#define ATOMIC_OPS(add, +=, , add) \ATOMIC_OP(add, +=, , add) \ATOMIC_OP_RETURN(add, +=, , add)
其中ATOMIC_OP
定义的内容根据armv6架构上下的区别,有不同的实现,这里只看armv6下的:
/* 所以op其实就是atomic_后面的名字, c_op是v->counter的c语言操作符,而asm_op就是汇编操作符,这里没有用到 */
#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); \ //恢复中断
} \
当armv6以上时,不再是关中断(arch\arm\include\asm\atomic.h:
#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"); \
}
① 读出:ldrex %0, [%3]
- %0是result的值它是一个寄存器,并且带&意味着后面会被改变
- %3是&v->counter,它是counter的地址
- ldrex会读取&v->counter的值,放到result中,并且把&v->counter的寄存器标记为“独占访问”
② 修改r0的值
③ 写入:strex %1, %0, [%3] - %1 是temp的值
- strex会去判断&v->counter如果“独占访问”还在,就把result的新值放入&v->counter,并清除“独占访问”标记
- 如果&v->counter标记不存在了,就不会更新内存,并且把temp值设置为1,意味着失败
④ 判断tmp是否成功
⑤ 不成功,再做一次
上面的函数执行时,如果在strex之前被抢占,那么“独占访问”的标记就会被提前清除,那么最后strex执行就不成功。
这就避免的程序A、B同时修改这个变量,并且都自认为成功了。
在ARMv6以上的架构中,原子操作不再需要关闭中断,关闭中断开销太大,并且如果关闭中断,另一个CPU的中断也会运行。
新的原子操作执行过程中是可以被打断的,但是它的效果符合“原子”的定义:
一个完整的“读、修改、写入”原子的,不会被别的程序打断。
实现思路:如果被别的程序打断了,那就重来,最后总会成功的。
三、原子变量使用案例
驱动程序使用原子变量实现:只能有一个APP访问驱动程序:
static atomic_t valid = ATOMIC_INIT(1);static ssize_t gpio_key_drv_open (struct inode *node, struct file *file)
{/* armv6架构之下关中断执行 *//* armv6架构之上可以被打断,如果A中执行原子操作时,B操作了值。那么A会重新执行一次,此时值从0到-1 */if (atomic_dec_and_test(&valid)) {return 0;}atomic_inc(&valid);return -EBUSY;
}static int gpio_key_drv_close (struct inode *node, struct file *file)
{atomic_inc(&valid);return 0;
}
四、原子位
原子位操作函数在Linux内核文件arch\arm\include\asm\bitops.h中,下表中p是一个unsigned long指针。
函数名 | 作用 |
---|---|
set_bit(nr, p) | 设置(*p)的bit nr为1 |
clear_bit(nr, p) | 设置(*p)的bit nr为0 |
change_bit(nr, p) | 改变(*p)的bit nr,从1变为0,或是从0变为1 |
test_and_set_bit(nr, p) | 设置(*p)的bit nr为1,返回该位的老值 |
test_and_clear_bit(nr, p) | 设置(*p)的bit nr为0,返回该位的老值 |
test_and_change_bit(nr, p) | 改变(*p)的bit nr,从1变为0,或是从0变为1;返回该位的老值 |
原子位内核实现
#define set_bit(nr,p) ATOMIC_BITOP(set_bit,nr,p)
#define clear_bit(nr,p) ATOMIC_BITOP(clear_bit,nr,p)
#define change_bit(nr,p) ATOMIC_BITOP(change_bit,nr,p)
#define test_and_set_bit(nr,p) ATOMIC_BITOP(test_and_set_bit,nr,p)
#define test_and_clear_bit(nr,p) ATOMIC_BITOP(test_and_clear_bit,nr,p)
#define test_and_change_bit(nr,p) ATOMIC_BITOP(test_and_change_bit,nr,p)
ATOMIC_BITOP
也有两种实现形式:
/** The __* form of bitops are non-atomic and may be reordered.*/
#define ATOMIC_BITOP(name,nr,p) \(__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p)) //armv6以下
#else
#define ATOMIC_BITOP(name,nr,p) _##name(nr,p) //armv6及以上
#endif
在armv6以下的架构中
static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
{unsigned long flags;unsigned long mask = BIT_MASK(bit);p += BIT_WORD(bit);raw_local_irq_save(flags); //关中断*p |= mask;raw_local_irq_restore(flags); //开中断
}
在armv6及以上的架构中的实现方式(arch\arm\lib\bitops.h)
arch/arm/lib/setbit.S: bitop _set_bit, orr
#if __LINUX_ARM_ARCH__ >= 6.macro bitop, name, instr
ENTRY( \name )
UNWIND( .fnstart )ands ip, r1, #3strneb r1, [ip] @ assert word-alignedmov r2, #1and r3, r0, #31 @ Get bit offsetmov r0, r0, lsr #5add r1, r1, r0, lsl #2 @ Get word offset
#if __LINUX_ARM_ARCH__ >= 7 && defined(CONFIG_SMP).arch_extension mpALT_SMP(W(pldw) [r1])ALT_UP(W(nop))
#endifmov r3, r2, lsl r3
1: ldrex r2, [r1] //如果被别人抢占了\instr r2, r2, r3 //重来一次strex r0, r2, [r1]cmp r0, #0bne 1bbx lr
UNWIND( .fnend )
ENDPROC(\name ).endm
:SMP就是Symmetric Multi-Processors,对称多处理器;UP即Uni-Processor,系统只有一个单核CPU。 ↩︎
rk3288 原子操作和原子位操作相关推荐
- C++`中的原子操作和原子类型
5.2 C++中的原子操作和原子类型 原子操作 是个不可分割的操作. 在系统的所有线程中,你是不可能观察到原子操作完成了一半这种情况的: 它要么就是做了,要么就是没做,只有这两种可能. 如果从对象读取 ...
- c++ 原子操作 赋值_5.2 C++中的原子操作和原子类型
5.2 C++中的原子操作和原子类型 原子操作 是个不可分割的操作. 在系统的所有线程中,你是不可能观察到原子操作完成了一半这种情况的: 它要么就是做了,要么就是没做,只有这两种可能. 如果从对象读取 ...
- 5.2 C++中的原子操作和原子类型
5.2 C++中的原子操作和原子类型 原子操作是一类不可分割的操作,当这样操作在任意线程中进行一半的时候,你是不能查看的:它的状态要么是完成,要不就是未完成.如果从对象中读取一个值的操作是原子的,并且 ...
- 全局变量中断原子操作_原子操作原理分析
原子操作原理分析 概念 原子操作是指不被打断的操作,即它是最小的执行单位.最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令).在 linux 中原子操作对应的 ...
- linux内核同步问题
linux内核同步问题 Linux内核设计与实现 十.内核同步方法 [手把手教Linux驱动5-自旋锁.信号量.互斥体概述]() 基础概念: 并发:多个执行单元同时进行或多个执行单元微观串行执行,宏观 ...
- CVTE 2017 秋季校招一面(C++ 后台)
文章目录 0.前言 1.找出数组中第 k 大的数(手写代码) 2.从n个数中找出最小的k个数(n>>k),最优平均时间复杂度是多少 4.C 如何模拟实现 C++ 的类? 5.TCP 与 U ...
- 原子性操作atomic_t
内核定义了atomic_t 数据类型,作为对整数计数器的原子操作的基础. 各个CPU平台有各自的原子操作实现方式,基本都是通过汇编实现的. 原子操作 原子操作是Linux中提供的一种实现同步的方法,所 ...
- lpop 原子_【concurrent】面试重灾区之原子操作你有必要了解下
概述 在JDK1.5+的版本中,Doug Lea和他的团队还为我们提供了一套用于保证线程安全的原子操作.我们都知道在多线程环境下,对于更新对象中的某个属性.更新基本类型数据.更新数组(集合)都可能产生 ...
- C++11 原子类型与原子操作
文章目录 1.认识原子操作 2.C++11 实现原子操作 3.内存模型:强顺序与弱顺序 参考文献 1.认识原子操作 原子操作是在多线程程序中"最小的且不可并行化的"操作,意味着多个 ...
最新文章
- 麦司机博客项目技术选型-Java后端
- [Java]Thinking in Java 练习2.10
- ubuntu查看版本及检查是否有系统更新的命令
- 容器技术之Dockerk8s知识笔记
- Linux进阶之路————scp指令介绍与演示
- php要懂函数吗,九个你需要知道的PHP函数和功能
- 2个简单shell脚本(if,while,case语句)
- bat自动输入密码登录_【第7期】Teamcenter自动登录改进,对portal.bat中登录密码加密...
- 干货收藏|如何用chrom插件实现U校园自动刷课
- 阿里云等企业主导的龙蜥社区发起“龙腾计划”;OpenInfra 基金会推出 LOKI 标准;GitLab 14.6 发布 | 开源日报
- linux下kegg注释软件,如何使用KAAS进行KEGG注释
- U盘装机大师安装GHOST WIN10系统
- 排序 ---- 快排(C语言)
- Android API统计
- php开源小程序直播,微信小程序直播
- 从1到100怎么做?小红书KOL五大阶段运营增长策略
- STM32 hal库串口空闲中断最新用法
- 有道云笔记markdown上传本地图片的方法
- VS2010版本介绍(转自:http://www.cnblogs.com/Leo_wl/archive/2010/06/02/1750035.html)
- 营销软文的结尾怎样写?营销软文结尾怎样去设计?