Linux驱动之 原子操作学习记录:
概念:

  1. 原子操作是指在执行过程中不会被别的代码所中断的操作,即它是最小的执行单位。
  2. 最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令)。

在linux中原子操作对应的数据结构为atomic t,定义如下:

typedef struct{int counter;
}atomic_t;

之所以定义这么一个数据类型,是为了让原子操作函数只接受 atomic_t 类型的操作数,如果传入的不是 atomic_t 类型数据,在程序编译阶段就不会通过;另一个原因就是确保编译器不会对相应的值进行访问优化,确保对它的访问都是对内存的访问,而不是对寄存器的访问。
分为 位 和 整型变量 两类原子操作。

主要整型原子操作:

atomic_t v = ATOMIC_INIT(0);            //定义原子变量v, 并初始化为0
atomic_t v = ATOMIC_INIT(0);            //定义原子变量v, 并初始化为0
atomic_set(atomic_t *v, int i);         //赋值操作,设置原子变量v的值为i
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

赋值操作宏:
ARM 处理器有直接对内存地址进行赋值的指令(STR)。

/*** atomic_set - set atomic variable* @v: pointer of type atomic_t* @i: required value* Atomically sets the value of @v to @i.*/
#define atomic_set(v, i) (((v)->counter) = (i))

读操作宏:
用 volatile 来防止编译器对变量访问的优化,确保是对内存的访问,而不是对寄存器的访问。

/*** atomic_read - read atomic variable* @v: pointer of type atomic_t* Atomically reads the value of @v.*/
#define atomic_read(v)  (*(volatile int *)&(v)->counter)

对原子变量执行自增,自减和减操作后 ,测试其是否为0,为 0 则返回 true,否则返回 false :

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); //减i值

对原子变量进行加/减,自增/自减操作,并返回新的值:

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_sub_return(atomic_t *v);

加操作源码实现过程:

/*** atomic_add_return - add integer to atomic variable* @i: integer value to add* @v: pointer of type atomic_t* Atomically adds @i to @v and returns the result*/
static inline int atomic_add_return(int i, atomic_t *v)
{unsigned long flags;int temp;// 通过关闭中断防止其他进程打断代码的执行raw_local_irq_save(flags); /* Don't trace it in an irqsoff handler */temp = v->counter;temp += i;v->counter = temp;// 恢复中断原始的状态  raw_local_irq_restore(flags);return temp;
}
位原子操作:
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位 反置
int test_bit(nr, void *addr);    //返回addr地址的nr位
//先设值,后返回。
int test_and_set_bit(nr, void *addr); //将addr地址的第nr位设置成1,并返回原来这一位的值
int test_and_clear_bit(nr, void *addr);//将addr地址的第nr位请0,并返回原来这一位的值
int test_and_change_bit(nr, void *addr);//将addr地址的第nr位反置,并返回原来这一位的值

原子变量,实现设备单进程使用

demo.c

原子变量定义:

#include <asm/atomic.h>
static atomic_t v = ATOMIC_INIT(1);     //定义原子变量并初始化为1

打开设备函数,加入原子操作

/** @description        : 打开设备* @param - inode     : 传递给驱动的inode* @param - filp   : 设备文件,file结构体有个叫做private_data的成员变量*                       一般在open的时候将private_data指向设备结构体。* @return             : 0 成功;其他 失败*/
static int hello_open (struct inode *inode, struct file *filep)
{printk("hello_open()\n");//第一个进程进来,对原子变量v执行自减1-1 = 0,为 0 则返回 true,取反,if 条件不成立,执行后面程序//第二个进程来打开驱动程序,0-1 = 负1,返回 false ,取反,if 条件成立,运行里面的代码,将原子变量加一恢复到 0,程序返回if (!atomic_dec_and_test(&v)){atomic_inc(&v);  //原子变量+1  return -EBUSY;}printk("hello_open() atomic v = %d \n",v);return 0;
}

关闭/释放设备函数,加入原子操作

/** @description        : 关闭/释放设备* @param - filp   : 要关闭的设备文件(文件描述符)* @return             : 0 成功;其他 失败*/
static int hello_release (struct inode *inode, struct file *filep)
{printk("hello_release()\n");//退出时, 自增,恢复原子变量值为 1:atomic_inc(&v);printk("hello_release() atomic v = %d \n",v);return 0;
}

第二个进程就无法使用这个设备驱动了:

./a.out

dmesg:
root@ubuntu16:/home/cxx/driver/8_atomic# dmesg41296.690268] chrdev_hello init!
[41296.690269] hello_init() atomic v = 1  //初始化时原子变量值
[41296.690271] chrdev_hello major=243,minor=0
[41300.945360] hello_open() //应用程序打开设备
[41300.945362] hello_open()atomic v = 0  //值
[41314.207845] hello_open()
[41323.295260] hello_open()
[41431.191540] hello_release()          //应用程序释放该设备
[41431.191542] hello_release() atomic v = 1  //值恢复
root@ubuntu16:/home/cxx/driver/8_atomic#
root@ubuntu16:/home/cxx/driver/8_atomic# rmmod cdev_hello
root@ubuntu16:/home/cxx/driver/8_atomic# dmesg41296.690268] chrdev_hello init!
[41296.690269] hello_init() atomic v = 1
[41296.690271] chrdev_hello major=243,minor=0
[41300.945360] hello_open()
[41300.945362] hello_open()atomic v = 0
[41314.207845] hello_open()
[41323.295260] hello_open()
[41431.191540] hello_release()
[41431.191542] hello_release() atomic v = 1 42017.464401] chrdev_hello exit!
[42017.464402] hello_exit() atomic v = 1
root@ubuntu16:/home/cxx/driver/8_atomic#

在Armv6开始支持多核,通过ldrex与strex指令来保证数据操作的原子性,比如自旋锁的上锁操作、原子变量操作等。在Armv6之前,都是单核,为保证数据的原子性,需要进行关中断操作。对于多核平台,关中断操作只能关闭本核中断,要想对数据进行原子操作,必须借助ldrex指令与strex。
独占加载和存储寄存器:

LDREX{cond} Rt, [Rn {, #offset}]
STREX{cond} Rd, Rt, [Rn {, #offset}]
LDREXB{cond} Rt, [Rn]        字节加载
STREXB{cond} Rd, Rt, [Rn]      字节存储
LDREXH{cond} Rt, [Rn]        半字加载
STREXH{cond} Rd, Rt, [Rn]      半字存储
LDREXD{cond} Rt, Rt2, [Rn]      双字加载
STREXD{cond} Rd, Rt, Rt2, [Rn]    双字存储
/*
* @cond 是一个可选的条件代码(请参阅条件执行)。
* @Rd 是存放返回状态的目标寄存器。
* @Rt 是要加载或存储的寄存器。
* @Rt2 为进行双字加载或存储时要用到的第二个寄存器。
* @Rn 是内存地址所基于的寄存器。
* @offset 为应用于 Rn 中的值的可选偏移量。offset 只可用于 Thumb-2 指令中。如果省略 offset,则认为偏移量为 0。
*/

参考原文链接:https://blog.csdn.net/daocaokafei/article/details/108278107

Linux驱动之 原子操作相关推荐

  1. 树莓派linux led字符设备驱动(原子操作)

    一.原子操作   linux驱动中为了处理对共享资源的并发访问,引入了原子操作.原子操作就是指不能再进一步分割的操作,一般原子操作用于变量或者位操作. Linux 内核提供了两组原子操作 API 函数 ...

  2. Linux驱动开发必看详解神秘内核(完全转载)

    Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入Lin ...

  3. linux驱动工程面试必问知识点

    linux内核原理面试必问(由易到难) 简单型 1:linux中内核空间及用户空间的区别?用户空间与内核通信方式有哪些? 2:linux中内存划分及如何使用?虚拟地址及物理地址的概念及彼此之间的转化, ...

  4. Linux驱动编程 step-by-step

    第三次看了LDD3了(虽然现在已经是kernel3.0但从这本书商还是能学到很多) 每次都有一些收获 现在终于能够写一写代码了 驱动程序的作用: 简单来说 驱动程序就是使计算机与设备通信的特殊的代码, ...

  5. linux驱动面试题整理

    1.字符型驱动设备你是怎么创建设备文件的,就是/dev/下面的设备文件,供上层应用程序打开使用的文件? 答:mknod命令结合设备的主设备号和次设备号,可创建一个设备文件. 评:这只是其中一种方式,也 ...

  6. Linux驱动快速入门

    本公众号分享的所有技术仅用于学习交流,请勿用于其他非法活动,如果错漏,欢迎留言指正 应用层:<LUNIX环境高级编程第二版> <Linux程序设计(第四版)> 内核层:< ...

  7. 正点原子linux驱动教程,正点原子 手把手教你学Linux之驱动开发篇

    简 介 该课程是正点原子手把手教你学Linux系列课程,该课程配套开发板为正点原子alpha/mini Linux开发板. 手把手教你学Linux之驱动开发篇: 第1讲 Linux驱动开发与裸机开发区 ...

  8. Linux驱动笔试知识

    Linux驱动笔试知识 1.Linux设备中字符设备与块设备有什么主要的区别?请分别列举一些实际的设备说出它们是属于哪 一类设备 字符设备字符设备是个能够像字节流(类似文件)一样被访问的设备,字符设备 ...

  9. linux驱动系列学习之OLED(i2c接口)(八)

    一.OLED简介 本次使用的开发板正点原子Linux阿波罗.屏幕是i2c接口的四针.分辨率为128×64的oled液晶屏.通信接口为i2c.具体的i2c框架使用请参考前面的文章.oled的详细简介请参 ...

最新文章

  1. java中sql去除游标_java.sql.SQLException:-ORA-01000:已超过最大打开游标
  2. jdbc详解:1、创建数据库connection连接
  3. php fwrite服务器上写不进去_使用PHP来简单的创建一个RPC服务
  4. 苹果6s最大屏幕尺寸_iPhone 6s:经典的小屏旗舰,百元价位也能做苹果党
  5. 服务器系统格式,服务器系统编码格式
  6. C++读取WINDOWS注册表
  7. 软件测试学习之悟空CRM项目测试用例编写
  8. 阿里云 python_阿里云python sdk
  9. 基因组数据质控中:先进行SNP缺失质控还是样本缺失质控?
  10. 戴维斯大学计算机排名,加利福尼亚大学戴维斯分校计算机科学专业排名第37(2020年USNEWS美国排名)...
  11. vue-baidu-map 百度地图(定位替换图标,添加标签)
  12. Gartner曾劭清:云计算市场依然存在太多变局
  13. 快速高效入门3D建模学习教程,让你最快从小白到建模大师!
  14. 金启孮:普通话其实是满州人的蹩脚汉语
  15. 循环彩灯c语言程序,PLC彩灯实例,一个简单的顺序控制
  16. FT232H USB转串口,I2C,JTAG高速芯片
  17. Elasticsearch5.0 安装 以及 问题集锦
  18. QQ合并的聊天记录可以通过什么方式转换成链接,点开链接就能看合并的聊天记录?
  19. 通达信资金净流入公式_资金净流入选股公式源码-通达信公式 -程序化交易(CXH99.COM)...
  20. intel万兆以太网网卡吞吐量测试

热门文章

  1. 2022年全国职业院校技能大赛 网络搭建与应用赛项 公开赛卷 (十套合卷)
  2. Windows7+Ubuntu10.04双系统安装指南
  3. 最小二乘法的原理及python实现
  4. 学习笔记-CCS-MSP430F5529[快速入门篇二]
  5. VBA多条件选择及自动填表及计算汇报
  6. 庆祝JavaCard技术领导SmartCard技术超过五年
  7. Mask Scoring RCNN训练自己的数据
  8. “师创杯”山东理工大学第九届ACM程序设计竞赛 正式赛 F.校赛~校赛~【思维+规律题】
  9. banner设圆角_illustrator大气、科技感的Banner设计教程
  10. python datetime日期时间去掉 时分秒