文章目录
原子操作的概念
什么是原子操作呢?
原子操作中的“原子”指的是化学反应中的最小微粒。在Linux中用原子来形容一个操作或者一个函数是最小的执行单位,是不可以被打断的。所以原子操作指的是该操作在执行完之前不会被任何事物打断。
原子操作的应用
原子操作一般用于整形变量或者位的保护。
比如,定义一个变量a,如果程序A正在给变量a赋值,此时程序B也要来操作变量a,这时候就发生了并发与竞争。程序A的操作就有可能被程序B打断。如果我们使用原子操作对变量a进行保护,就可以避免这种问题。
原子整形变量描述
Linux中定义了一个叫做atomic_t
和atomic64_t
的结构体来描述原子变量,其中atomic_t
是用于32位系统,atomic64_t
是用于64位系统。代码如下:
typedef struct {
int counter;
} atomic_t;
#ifdef CONFIG_64BIT
typedef struct {
long counter;
} atomic64_t;
#endif
原子整形操作的API函数
32位API(32位操作系统的计算机)
原子整数操作 | 描述 |
---|---|
ATOMIC_INIT(long i) | 在声明一个atomic_t变量时,将它初始化为 i |
long atomic_read(atomic_t *v) | 原子地读取整数变量 v |
void atomic_set(atomic_t *v, int i) | 原子地设置 v 值为 i |
void atomic_add(int i, atomic_t *v) | 原子地给 v 加 i |
void atomic_sub(int i, atomic_t *v) | 原子地从 v 减 i |
void atomic_inc(atomic_t *v) | 原子地给 v 加 1 |
void atomic_dec(atomic_t *v) | 原子地给 v 减 1 |
int atomic_sub_and_test(int i, atomic_t *v) | 原子地从 v 减 i,如果结果等于 0,返回真;否则返回假 |
int atomic_add_negative(int i, atomic_t *v) | 原子地给 v 加 i,如果结果是负数,返回真;否则返回假 |
long atomic_add_return(int i, atomic_t *v) | 原子地给 v 加 i,且返回结果 |
long atomic_sub_return(int i, atomic_t *v) | 原子地从 v 减 i,且返回结果 |
long atomic_inc_return(int i, atomic_t *v) | 原子地从 v 加 i,且返回结果 |
long atomic_dec_return(int i, atomic_t *v) | 原子地从 v 减 i,且返回结果 |
int atomic_dec_and_test(atomic_t *v) | 原子地从 v 减 1,如果结果等于0,返回真;否则返回假 |
int atomic_inc_and_test(atomic_t *v) | 原子地给 v 加 1,如果结果等于0,返回真;否则返回假 |
64位API(64位操作系统的计算机)
原子整数操作 | 描述 |
---|---|
ATOMIC64_INIT(long i) | 在声明一个atomic64_t变量时,将它初始化为 i |
long atomic64_read(atomic64_t *v) | 原子地读取整数变量 v |
void atomic64_set(atomic64_t *v, int i) | 原子地设置 v 值为 i |
void atomic64_add(int i, atomic64_t *v) | 原子地给 v 加 i |
void atomic64_sub(int i, atomic64_t *v) | 原子地从 v 减 i |
void atomic64_inc(atomic64_t *v) | 原子地给 v 加 1 |
void atomic64_dec(atomic64_t *v) | 原子地给 v 减 1 |
int atomic64_sub_and_test(int i, atomic64_t *v) | 原子地从 v 减 i,如果结果等于 0,返回真;否则返回假 |
int atomic64_add_negative(int i, atomic64_t *v) | 原子地给 v 加 i,如果结果是负数,返回真;否则返回假 |
long atomic64_add_return(int i, atomic64_t *v) | 原子地给 v 加 i,且返回结果 |
long atomic64_sub_return(int i, atomic64_t *v) | 原子地从 v 减 i,且返回结果 |
long atomic64_inc_return(int i, atomic64_t *v) | 原子地从 v 加 i,且返回结果 |
long atomic64_dec_return(int i, atomic64_t *v) | 原子地从 v 减 i,且返回结果 |
int atomic64_dec_and_test(atomic64_t *v) | 原子地从 v 减 1,如果结果等于0,返回真;否则返回假 |
int atomic64_inc_and_test(atomic64_t *v) | 原子地给 v 加 1,如果结果等于0,返回真;否则返回假 |
原子位操作API函数
除原子整数操作外,内核也提供了一组针对位的操作函数,这些位操作函数是对普通的内存地址进行操作的,参数是一个指针和一个位号。
原子位操作 | 描述 |
---|---|
void set_bit(int nr, void *addr) | 原子地设置 addr 所指对象的第 nr 位 |
void clear_bit(int nr, void *addr) | 原子地清空 addr 所指对象的第 nr 位 |
void change_bit(int nr, void *addr) | 原子地翻转 addr 所指对象的第 nr 位 |
void test_and_set_bit(int nr, void *addr) | 原子地设置 addr 所指对象的第 nr 位,并返回原先的值 |
void test_and_clear_bit(int nr, void *addr) | 原子地清空 addr 所指对象的第 nr 位,并返回原先的值 |
void test_and_change_bit(int nr, void *addr) | 原子地翻转 addr 所指对象的第 nr 位,并返回原先的值 |
void test_bit(int nr, void *addr) | 原子地返回 addr 所指对象的第 nr 位 |
原子整形操作举例
atomic64_t v = ATOMIC64_INT(0); // 定义并初始化原子变量v=0
atomic64_set(&v,1); //设置v=1
atomic64_read(&v); //读取v的值,此时v的值是1
atomic64_inc(&v); //v的值加1,此时v的值是2
写代码验证
#include <linux/atomic.h>
#include <asm/atomic.h>
static atomic64_t v = ATOMIC_INIT(1);
...
static int cdev_test_open(struct inode *inode, struct file *file)
{
if(!atomic64_dec_and_test(&v)) // 第一次条件不满足
{
atomic64_inc(&v);
return -EBUSY;
}
...
return 0;
}
static int cdev_test_release(struct inode *inode, struct file *file)
{
atomic64_inc(&v);
return 0;
}
解释:
- 上面的是一个驱动的模块,当程序A调用该驱动时没有问题此时
v=1
(第一次调用if(!atomic64_dec_and_test(&v)) // 第一次条件不满足
)打开成功,此时原子变量v=0
了,在程序A调用期间程序B也调用该模块,此时c=0
条件if(!atomic64_dec_and_test(&v)
会满足会返回-EBUSY
即驱动调用将会不成功! - 程序A成功调用完毕以后再释放驱动模块时会将原子变量加一。这样下一个程序就可以调用成功了。
实例
led.c
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //包含了miscdevice结构的定义及相关的操作函数。
#include <linux/fs.h> //文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/uaccess.h> //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/io.h> //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/kernel.h> //驱动要写入内核,与内核相关的头文件
#include <linux/atomic.h>
#include <asm/atomic.h>
#define GPIO_DR 0xfdd60000 //LED物理地址,通过查看原理图得知
unsigned int *vir_gpio_dr; //存放映射完的虚拟地址的首地址
static atomic64_t v = ATOMIC_INIT(1); // 初始化一个原子变量v等于1
ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
printk("misc_read\n ");
return 0;
}
ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
/*应用程序传入数据到内核空间,然后控制蜂鸣器的逻辑,在此添加*/
// kbuf保存的是从应用层读取到的数据
char kbuf[64] = {
0};
// copy_from_user 从应用层传递数据给内核层
if(copy_from_user(kbuf,ubuf,size)!= 0)
{
// copy_from_user 传递失败打印
printk("copy_from_user error \n ");
return -1;
}
//打印传递进内核的数据
//printk("kbuf is %d\n ",kbuf[0]);
if(kbuf[0]==1) //传入数据为1 ,LED亮
{
*vir_gpio_dr = 0x80008000;
}
else if(kbuf[0]==0) //传入数据为0,LED灭
*vir_gpio_dr = 0x80000000;
return 0;
}
int misc_release(struct inode *inode,struct file *file)
{
atomic64_inc(&v); // 释放的时候给v加1,便于下一个应用打开该设备
printk("hello misc_relaease bye bye \n ");
return 0;
}
int misc_open(struct inode *inode,struct file *file)
{
if(!atomic64_dec_and_test(&v)) // 只会允许被一个设备打开,不会允许多个设备同时打开的逻辑
{
atomic64_inc(&v);
return -EBUSY;
}
printk("hello misc_open\n ");
return 0;
}
//文件操作集
struct file_operations misc_fops={
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_release,
.read = misc_read,
.write = misc_write,
};
//miscdevice结构体
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "hello_misc",
.fops = &misc_fops,
};
static int misc_init(void)
{
int ret;
//注册杂项设备
ret = misc_register(&misc_dev);
if(ret<0)
{
printk("misc registe is error \n");
}
printk("misc registe is succeed \n");
//将物理地址转化为虚拟地址
vir_gpio_dr = ioremap(GPIO_DR,4);
if(vir_gpio_dr == NULL)
{
printk("GPIO_DR ioremap is error \n");
return EBUSY;
}
printk("GPIO_DR ioremap is ok \n");
return 0;
}
static void misc_exit(void){
//卸载杂项设备
misc_deregister(&misc_dev);
iounmap(vir_gpio_dr);
printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
Makefile
obj-m += led.o
KDIR =/home/liefyuan/Linux/rk356x_linux/kernel
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules modules ARCH=arm64 CROSS_COMPILE=/usr/local/arm64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
clean:
rm -rf modules.order *.o workqueue.o Module.symvers *.mod.c *.ko
编译模块:
$ export ARCH=arm64
$ export CROSS_COMPILE=aarch64-linux-gnu-
$ make
测试应用:
app.c:2秒钟闪烁
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
char buf[64] = {
0};//定义buf缓存
char val[1];
//打开设备节点
fd = open("/dev/hello_misc",O_RDWR);
if(fd < 0)
{
//打开设备节点失败
perror("open error \n");
return fd;
}
//把缓冲区数据写入文件中
while(1)
{
val[0] = 1;
write(fd, val, sizeof(val));
sleep(2);
val[0] = 0;
write(fd, val, sizeof(val));
sleep(2);
}
close(fd);
return 0;
}
app2.c:1秒钟闪烁
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
int fd;
char buf[64] = {
0};//定义buf缓存
char val[1];
//打开设备节点
fd = open("/dev/hello_misc",O_RDWR);
if(fd < 0)
{
//打开设备节点失败
perror("open error \n");
return fd;
}
//把缓冲区数据写入文件中
while(1)
{
val[0] = 1;
write(fd, val, sizeof(val));
sleep(1);
val[0] = 0;
write(fd, val, sizeof(val));
sleep(1);
}
close(fd);
return 0;
}
编译:
aarch64-linux-gnu-gcc app.c -o app.armelf
aarch64-linux-gnu-gcc app2.c -o app2.armelf
测试
安装模块,设备节点出现:
[root@RK356X:/opt]# insmod led.ko
[ 57.789237] led: loading out-of-tree module taints kernel.
[root@RK356X:/opt]# [ 57.790784] misc registe is succeed
[ 57.791188] GPIO_DR ioremap is ok
[root@RK356X:/opt]# ls /dev/hello_misc
/dev/hello_misc
先后台运行app.armelf
./app.armelf &
OK,灯已经按2秒频率闪烁起来了。
再在前台运行app2.armelf
[root@RK356X:/opt]# ./app2.armelf
open error
: Device or resource busy
目的达到了,打开出错。
这就是原子操作运用到实际的一个简单应用!