linux中原子操作atomic_read、atomic_set、atomic_add、atomic_sub
原子操作
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。因此这里的原子实际是使用了物理学里的物质微粒的概念。
原子操作需要硬件的支持,因此是架构相关的,其API和原子类型的定义都定义在内核源码树的include/asm/atomic.h文件中,它们都使用汇编语言实现,因为C语言并不能实现这样的操作。
原子操作主要用于实现资源计数,很多引用计数(refcnt)就是通过原子操作实现的。原子类型定义如下:
typedef struct
{ volatile int counter;
}
atomic_t;
volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,而不是对寄存器的访问。
atomic_inc(&v)对变量v用锁定总线的单指令进行不可分解的"原子"级增量操作,
避免v的值由于中断或多处理器同时操作造成不确定状态。typedef struct { volatile int counter; } atomic_t;volatile修饰字段告诉gcc不要对该类型的数据做优化处理,对它的访问都是对内存的访问,
而不是对寄存器的访问。 原子操作API包括: atomic_read(atomic_t * v);该函数对原子类型的变量进行原子读操作,它返回原子类型的变量v的值。 atomic_set(atomic_t * v, int i);该函数设置原子类型的变量v的值为i。 void atomic_add(int i, atomic_t *v);该函数给原子类型的变量v增加值i。 atomic_sub(int i, atomic_t *v);该函数从原子类型的变量v中减去i。 int atomic_sub_and_test(int i, atomic_t *v);该函数从原子类型的变量v中减去i,并判断结果是否为0,如果为0,返回真,否则返回假。 void atomic_inc(atomic_t *v);该函数对原子类型变量v原子地增加1。 void atomic_dec(atomic_t *v);该函数对原子类型的变量v原子地减1。 int atomic_dec_and_test(atomic_t *v);该函数对原子类型的变量v原子地减1,并判断结果是否为0,如果为0,返回真,否则返回假。 int atomic_inc_and_test(atomic_t *v);该函数对原子类型的变量v原子地增加1,并判断结果是否为0,如果为0,返回真,否则返回假。 int atomic_add_negative(int i, atomic_t *v);该函数对原子类型的变量v原子地增加I,并判断结果是否为负数,如果是,返回真,否则返回假。 int atomic_add_return(int i, atomic_t *v);该函数对原子类型的变量v原子地增加i,并且返回指向v的指针。 int atomic_sub_return(int i, atomic_t *v);该函数从原子类型的变量v中减去i,并且返回指向v的指针。 int atomic_inc_return(atomic_t * v);该函数对原子类型的变量v原子地增加1并且返回指向v的指针。 int atomic_dec_return(atomic_t * v);该函数对原子类型的变量v原子地减1并且返回指向v的指针。
原子操作通常用于实现资源的引用计数,在TCP/IP协议栈的IP碎片处理中,就使用了引用计数,碎片队列结构struct ipq描述了一个IP碎片,字段refcnt就是引用计数器,它的类型为atomic_t,当创建IP碎片时(在函数ip_frag_create中),使用atomic_set函数把它设置为1,当引用该IP碎片时,就使用函数atomic_inc把引用计数加1。
当不需要引用该IP碎片时,就使用函数ipq_put来释放该IP碎片,ipq_put使用函数atomic_dec_and_test把引用计数减1并判断引用计数是否为0,如果是就释放IP碎片。函数ipq_kill把IP碎片从ipq队列中删除,并把该删除的IP碎片的引用计数减1(通过使用函数atomic_dec实现)。
static inline int atomic_add_return(int i, atomic_t *v)
{unsigned long tmp;int result;__asm__ __volatile__("@ atomic_add_return/n"
"1: ldrex %0, [%2]/n"
" add %0, %0, %3/n"
" strex %1, %0, [%2]/n"
" teq %1, #0/n"
" bne 1b": "=&r" (result), "=&r" (tmp): "r" (&v->counter), "Ir" (i): "cc");return result;
Linux中的基本原子操作
宏或者函数 | 说明 |
atomic_read | 返回原子变量的值。 |
atomic_set | 设置原子变量的值。 |
atomic_add | 原子的递增计数的值。 |
atomic_sub | 原子的递减计数的值。 |
atomic_cmpxchg | 原子比较并交换计数值。 |
atomic_clear_mask | 原子的清除掩码。 |
除此以外,还有一组操作64位原子变量的变体,以及一些位操作宏及函数。这里不再罗列。
/*** 返回原子变量的值。* 这里强制将counter转换为volatile int并取其值。目的就是为了避免编译优化。*/
#define atomic_read(v) (*(volatile int *)&(v)->counter)/*** 设置原子变量的值。*/
#define atomic_set(v,i) (((v)->counter) = (i))原子递增的实现比较精妙,理解它的关键是需要明白ldrex、strex这一对指令的含义。/*** 原子的递增计数的值。*/
static inline void atomic_add(int i, atomic_t *v)
{unsigned long tmp;int result;/*** __volatile__是为了防止编译器乱序。与"#define atomic_read(v) (*(volatile int *)&(v)->counter)"中的volatile类似。*/__asm__ __volatile__("@ atomic_add\n"/*** ldrex是arm为了支持多核引入的新指令,表示"排它性"加载。与mips的ll指令一样的效果。* 它与"排它性"存储配对使用。*/
"1: ldrex %0, [%3]\n"/*** 原子变量的值已经加载到寄存器中,这里对寄存器中的值减去指定的值。*/
" add %0, %0, %4\n"/*** strex是"排它性"的存储寄存器的值到内存中。类似于mips的sc指令。*/
" strex %1, %0, [%3]\n"/*** 关键代码是这里的判断。如果在ldrex和strex之间,其他核没有对原子变量变量进行加载存储操作,* 那么寄存器中值就是0,否则非0.*/
" teq %1, #0\n"/*** 如果其他核与本核冲突,那么寄存器值为非0,这里跳转到标号1处,重新加载内存的值并递增其值。*/
" bne 1b": "=&r" (result), "=&r" (tmp), "+Qo" (v->counter): "r" (&v->counter), "Ir" (i): "cc");
}
atomic_add_return递增原子变量的值,并返回它的新值。它与atomic_add的最大不同,在于在原子递增前后各增加了一句:smp_mb();
这是由linux原子操作函数的语义规定的:所有对原子变量的操作,如果需要向调用者返回结果,那么就需要增加多核内存屏障的语义。通俗的说,就是其他核看到本核对原子变量的操作结果时,本核在原子变量前的操作对其他核也是可见的。
理解了atomic_add,其他原子变量的实现也就容易理解了。这里不再详述。
atomic_cmpxchg()函数实现了一个比较+交换的原子操作(原子就是说cpu要不就不做,要做就一定要做完某些操作才能干别的事情,对应这里就是比较和交换要一次过做完)。
atomic_cmpxchg()比较kgdb_active->count的值是否等用-1,如果是则把cpu的值赋给kgdb_active->count,否则不修改它的值atomic_cmpxchg返回kgdb_active->count赋值前的值。kgdb_active是一个全局原子变量,定义在kernel/kgdb.c中,用来记录当前正在执行kgdb代码的cpu号,它起到一个锁的作用,因为同一时间只能有一个cpu执行kgdb的代码,这是可以想象得到的,如果两个cpu在两个不同断点被触发,那究竟是谁和远端gdb通信呢?前一条命令被 cpu1拿了,后一条却去了cpu2那里,那还得了。
kgdb_active的初始值为-1,-1表示当前kgdb的处理函数并没有被触发,相反如果kgdb已经在运行,那么kgdb_active就有它自己的值,这些处理都是针对多cpu的,如果只有一个cpu,这个世界就简单多了。这里是防止多个kgdb的实例在不同cpu被触发引起互相干扰。考虑这种情况,在cpu1上有一个断点让kgdb起来,这时,kgdb_active还是-1,cpu1很顺利就给kgdb_active赋值然后进入后面的操作。这时cpu2中kgdb也被触发。它也想进入后面的操作,但是这时候kgdb_active已经不再是-1,cpu2只能不断地比较kgdb_active的值和执行cpu_relax(),宏cpu_relax()可以简化为一条pause汇编,通过引入一个很短的延迟,加快了紧跟在锁后面的代码的执行并减少能源的消耗,实际上就是让cpu2等。当cpu1在退出kgdb_handle_exception()前会把 kgdb_active赋回-1, 这样cpu2就可以进行后面的操作了。kgdb使用大量的原子操作来完成锁的功能,后面还会看到。atomic操作加上cpu_relax()跟一个自旋锁很相似。
linux中原子操作atomic_read、atomic_set、atomic_add、atomic_sub相关推荐
- linux 中的中断处理
谈谈对中断的理解 1.裸板中断处理过程 中断属于异常的一种 它是计算机中处理异步事件的重要机制1.1 中断的触发中断源级配置中断的触发方式 上升沿 下降沿 高 低触发中断使能 (监测到中断信号之后,能 ...
- Linux中的中断管理机制
1.中断相关基础知识介绍 1.1.中断产生背景 假设现在CPU需要去获取一个键盘的时间,如果处理器发出一个请求信号之后一直在轮询键盘的响应,由于键盘响应速度比处理器慢得多并且需要等待用户输入,这对于C ...
- linux的swap与memory,【Linux】Linux中Swap与Memory内存简单介绍
背景介绍 对于Linux来说,其在服务器市场的使用已经占据了绝对的霸主地位,不可动摇.Linux的各种设计思想和使用也被传承(当然不乏各种黑Linux,而且黑的漂亮).Linux的很多独特的设计,对性 ...
- Linux中的文件IO
1.什么是文件IO (1)IO就是input/output,输入/输出.文件IO的意思就是读写文件. 2.linux常用文件IO接口 (1)open.close.write.read.lseek 3. ...
- Linux中mknod命令实现原理以及源码分析
本篇文章以mknod创建字符设备文件进行讲解 字符设备驱动的Demo例子可参考该篇文章 Linux 编写简单驱动并测试 1. mknod 命令 mknod /dev/hello c 520 0 该命令 ...
- Redis+在Linux中安装使用
Redis+在Linux中安装使用 一.安装 (一).上传redis安装包 (二).进入到rpmgcc文件下 (三).解压redis-3.2.5.tar.gz (四).启动redis 二.Redis概 ...
- Linux中的线程同步机制-futex
Linux中的线程同步机制(一) -- Futex 引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这 ...
- c++ 进程快照_如何在 Linux 中找出内存消耗最大的进程
很多次,你可能遇见过系统消耗了过多的内存.如果是这种情况,那么最好的办法是识别出 Linux 机器上消耗过多内存的进程. -- Magesh Maruthamuthu(作者) 很多次,你可能遇见过系统 ...
- linux的tar中ztvf,linux中的tar命令(2)
实例4:只将 /tar 内的 部分文件解压出来 命令: tar -zxvf /opt/soft/test/log30.tar.gz log2013.log 输出: [root@localhost te ...
- centos6.5 php5.2,Linux中PHP安装与配置(CentOS-6.5:php-5.2.13)
1 PHP简介 PHP(PHP: Hypertext Preprocessor的缩写,中文名:"超文本预处理器")是一种通用开源脚本语言.语法吸收了C语言.Java和Per ...
最新文章
- 18怎么确定板子形状_板绘怎么画线条排线?板绘小白画线不稳怎么办?
- 自学Zabbix3.0版本以上资产清单inventory
- python2.7装饰器使用_python 函数 装饰器的使用方法
- QQ协议TEA加密解密代码 C#
- c 连接mysql示例 源码_MySQL 连接
- 《Oracle Life-DBA的一天》海报下载
- 关于JS !!flag 语法
- html调用rpst 源码_前段播放 流媒体(RTMP,RTSP,HLS)
- 基于线性回归房价预测散点图和折线图
- printf 和 puts
- thinkphp6学习教程与源码 tp6开源CMS系统源码研究
- 中国大学MOOC-陈越、何钦铭-数据结构-2022春期中考试
- [vuex] unknown action type: jia1
- Python学习,第一课(基础知识,利用urllib库入门)
- mysql数据库之备份和恢复
- 51单片机计算机设计报告,基于51单片机的计算器设计与制作.docx
- 如何实现应用之间的跳转(ios和安卓)
- table表格取消显示表头
- HDU 3001 Travelling (三进制状压dp)
- 鄙视链是一种怪圈现象,那么IT行业也存在鄙视链吗?
热门文章
- 凭借这份《2022测试面经》候选者逆袭面试官,offer拿到手软
- 计算机控制键功能,电脑ctrl键的作用大全
- 服务器硬盘sas速度多少,R710服务器6块硬盘(SAS 300G 15000转)做完Raid5后读写速度没有一台普通台式机硬盘的读写速度快...
- weevely生成木马文件上传获取靶机中的flag文件
- 分享新作:休闲小游戏『Flying Stone』
- 关于前端PDF显示和盖章(vue)
- Nero 2014 Platinum 白金版 V 15.0.02200 官方版
- 【项目】小餐馆(点餐系统)项目框架
- 用C#获取系统中的临时文件夹或windows安装文件夹
- (10.13更新--完结)我不是大神(腾讯+京东+网易+阿里+去哪儿网面筋)