原子操作
所谓原子操作,就是该操作绝不会在执行完毕前被任何其他任务或事件打断,也就说,它的最小的执行单位,不可能有比它更小的执行单位。因此这里的原子实际是使用了物理学里的物质微粒的概念。

原子操作需要硬件的支持,因此是架构相关的,其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相关推荐

  1. linux 中的中断处理

    谈谈对中断的理解 1.裸板中断处理过程 中断属于异常的一种 它是计算机中处理异步事件的重要机制1.1 中断的触发中断源级配置中断的触发方式 上升沿 下降沿 高 低触发中断使能 (监测到中断信号之后,能 ...

  2. Linux中的中断管理机制

    1.中断相关基础知识介绍 1.1.中断产生背景 假设现在CPU需要去获取一个键盘的时间,如果处理器发出一个请求信号之后一直在轮询键盘的响应,由于键盘响应速度比处理器慢得多并且需要等待用户输入,这对于C ...

  3. linux的swap与memory,【Linux】Linux中Swap与Memory内存简单介绍

    背景介绍 对于Linux来说,其在服务器市场的使用已经占据了绝对的霸主地位,不可动摇.Linux的各种设计思想和使用也被传承(当然不乏各种黑Linux,而且黑的漂亮).Linux的很多独特的设计,对性 ...

  4. Linux中的文件IO

    1.什么是文件IO (1)IO就是input/output,输入/输出.文件IO的意思就是读写文件. 2.linux常用文件IO接口 (1)open.close.write.read.lseek 3. ...

  5. Linux中mknod命令实现原理以及源码分析

    本篇文章以mknod创建字符设备文件进行讲解 字符设备驱动的Demo例子可参考该篇文章 Linux 编写简单驱动并测试 1. mknod 命令 mknod /dev/hello c 520 0 该命令 ...

  6. Redis+在Linux中安装使用

    Redis+在Linux中安装使用 一.安装 (一).上传redis安装包 (二).进入到rpmgcc文件下 (三).解压redis-3.2.5.tar.gz (四).启动redis 二.Redis概 ...

  7. Linux中的线程同步机制-futex

    Linux中的线程同步机制(一) -- Futex 引子 在编译2.6内核的时候,你会在编译选项中看到[*] Enable futex support这一项,上网查,有的资料会告诉你"不选这 ...

  8. c++ 进程快照_如何在 Linux 中找出内存消耗最大的进程

    很多次,你可能遇见过系统消耗了过多的内存.如果是这种情况,那么最好的办法是识别出 Linux 机器上消耗过多内存的进程. -- Magesh Maruthamuthu(作者) 很多次,你可能遇见过系统 ...

  9. linux的tar中ztvf,linux中的tar命令(2)

    实例4:只将 /tar 内的 部分文件解压出来 命令: tar -zxvf /opt/soft/test/log30.tar.gz log2013.log 输出: [root@localhost te ...

  10. centos6.5 php5.2,Linux中PHP安装与配置(CentOS-6.5:php-5.2.13)

    1 PHP简介     PHP(PHP: Hypertext Preprocessor的缩写,中文名:"超文本预处理器")是一种通用开源脚本语言.语法吸收了C语言.Java和Per ...

最新文章

  1. 18怎么确定板子形状_板绘怎么画线条排线?板绘小白画线不稳怎么办?
  2. 自学Zabbix3.0版本以上资产清单inventory
  3. python2.7装饰器使用_python 函数 装饰器的使用方法
  4. QQ协议TEA加密解密代码 C#
  5. c 连接mysql示例 源码_MySQL 连接
  6. 《Oracle Life-DBA的一天》海报下载
  7. 关于JS !!flag 语法
  8. html调用rpst 源码_前段播放 流媒体(RTMP,RTSP,HLS)
  9. 基于线性回归房价预测散点图和折线图
  10. printf 和 puts
  11. thinkphp6学习教程与源码 tp6开源CMS系统源码研究
  12. 中国大学MOOC-陈越、何钦铭-数据结构-2022春期中考试
  13. [vuex] unknown action type: jia1
  14. Python学习,第一课(基础知识,利用urllib库入门)
  15. mysql数据库之备份和恢复
  16. 51单片机计算机设计报告,基于51单片机的计算器设计与制作.docx
  17. 如何实现应用之间的跳转(ios和安卓)
  18. table表格取消显示表头
  19. HDU 3001 Travelling (三进制状压dp)
  20. 鄙视链是一种怪圈现象,那么IT行业也存在鄙视链吗?

热门文章

  1. 凭借这份《2022测试面经》候选者逆袭面试官,offer拿到手软
  2. 计算机控制键功能,电脑ctrl键的作用大全
  3. 服务器硬盘sas速度多少,R710服务器6块硬盘(SAS 300G 15000转)做完Raid5后读写速度没有一台普通台式机硬盘的读写速度快...
  4. weevely生成木马文件上传获取靶机中的flag文件
  5. 分享新作:休闲小游戏『Flying Stone』
  6. 关于前端PDF显示和盖章(vue)
  7. Nero 2014 Platinum 白金版 V 15.0.02200 官方版
  8. 【项目】小餐馆(点餐系统)项目框架
  9. 用C#获取系统中的临时文件夹或windows安装文件夹
  10. (10.13更新--完结)我不是大神(腾讯+京东+网易+阿里+去哪儿网面筋)