Linux驱动之 原子操作学习记录:
概念:
- 原子操作是指在执行过程中不会被别的代码所中断的操作,即它是最小的执行单位。
- 最简单的原子操作就是一条条的汇编指令(不包括一些伪指令,伪指令会被汇编器解释成多条汇编指令)。
在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