解决竞态引起异常方法之原子操作
原子操作特点
原子操作能够解决所有的竞态问题。
Linux内核原子操作分为两类:位原子操作和整形原子操作 。
位原子操作
位原子操作 = 位操作的过程具有原子性 = 对共享资源进行位操作的过程中不允许发生CPU资源的切换。
应用场景:如果在代码中发现需要对共享资源进行位操作,可以考虑使用内核提供的位原子操作相关的函数,调用这些内核提供的函数对共享资源进行位操作,整个过程是不会发送CPU资源切换的。(应用场景相对之前其他方式很狭窄)
Linux内核提供的位原子操作的相关宏或者函数:
set_bit/clear_bit/change_bit/test_bit/各种组合函数
void set_bit(int nr, void *addr)
//功能:将addr地址内数据的第nr位置1(nr从0开始)
void clear_bit(int nr, void *addr)
//功能:将addr地址内数据的第nr位清0(nr从0开始)
void change_bit(int nr, void *addr)
//功能:将addr地址内数据的第nr位反转(nr从0开始)
int test_bit(int nr, void *addr)
//功能:获取addr地址内数据的第nr位的值(nr从0开始)
- 切记:以上函数不要被表面给欺骗,它们的内在核心在于在addr 地址内数据进行位操作过程不允许发生CPU资源切换, 以上函数的操作对象一定是共享资源,虽说也可以利用以上函数对非共享资源进行位操作,但是致命的后果就是代码执行效率降低
使用方式比较:
原子操作相对之前其他几种方式,使用场景很小,仅仅是对单个共享资源可用或者是对共享资源进行位操作可用。
例如:我们将一段简单的共享资源操作进行各种方式的修改就能看出使用方式不同的情况:
- 没有竞态问题解决处理的原代码:
int open_cnt = 1; //共享资源
open_cnt &= ~(1 << 1); //位操作减一
- 采用中断屏蔽
local_irq_save
open_cnt &= ~(1 << 1);
local_irq_restore
- 采用衍生自旋锁
spin_lock_irqsave
open_cnt &= ~(1 << 1);
spin_unlock_irqrestore
- 采用信号量
down
open_cnt &= ~(1 << 1);
up
- 采用位原子操作
clear_bit(1, &open_cnt)
整形原子操作
整形原子操作 = 整形数的操作具有原子性 = 对驱动代码中的共享资源进行整形操作的过程中,不允许发生CPU资源的切换。
应用场合:如果将来驱动代码中发现有对共享资源进行整形操作(+/-/++/–等),并且考虑到竞态问题,可以考虑使用linux内核提供的整形原子操作。
linux内核对于整形原子操作专门提供了一个数据类型:atomic_t, 严重类似int型
- atomic_t数据类型定义的变量称之为整形原子变量,例如:atomic_t open_cnt;
如果对整形原子操作进行访问,必须用内核提供的相关操作宏或者函数,调用这写函数才能保证原子性:
//各种组合函数
atomic_add/atomic_sub/atomic_inc/atomic_dec/
atomic_dec_and_test
//对整形变量进行减1操作,然后判断其值是否为0,
//如果为0,返回真,如果非0,返回假
使用方式比较:
- 没有竞态问题解决处理的原代码:
int open_cnt = 1; //共享资源
--open_cnt;
- 采用中断屏蔽
local_irq_save
--open_cnt;
local_irq_restore
- 采用衍生自旋锁
spin_lock_irqsave
--open_cnt;
spin_unlock_irqrestore
- 采用信号量
down(&sema);
--open_cnt;
up(&sema);
- 采用整形原子操作
//定义初始化一个整形原子变量为1
atomic_t open_cnt = ATOMIC_INIT(1);
//类似int open_cnt = 1
atomic_dec(&open_cnt);
//类似--open_cnt,此操作具有原子性
- 切记切记:整形原子操作表面看似是整形操作,但认清本质是对整形操作的过程具有原子性,不会发生CPU资源的切换。
示例代码
- led_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
static atomic_t open_cnt = ATOMIC_INIT(1); //记录LED打开的状态开关
static int led_open(struct inode *inode,
struct file *file)
{
if(!atomic_dec_and_test(&open_cnt)) {
printk("设备已被打开!\n");
atomic_inc(&open_cnt);
return -EBUSY;//返回设备忙错误码
}
printk("设备打开成功!\n");
return 0; //open返回成功
}
static int led_close(struct inode *inode,
struct file *file)
{
atomic_inc(&open_cnt);
return 0;
}
//定义初始化硬件操作接口对象
static struct file_operations led_fops = {
.owner = THIS_MODULE,
.open = led_open,
.release = led_close
};
//定义初始化混杂设备对象
static struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled",
.fops = &led_fops
};
static int led_init(void)
{
misc_register(&led_misc);
return 0;
}
static void led_exit(void)
{
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
-
led_test.c 和 Makefile同前几篇文章中一样,并无改动。
-
执行结果: