Linux驱动之 原子操作

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# dmesg
 41296.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# dmesg
 41296.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

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程一时爽Cxx

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值