一.解决竞态的方法
1.中断屏蔽
避免竞态方法之中断屏蔽
特点:1.能够解决进程与进程的抢占引起的竞态问题
2.能够解决中断与进程的抢占引起的竞争问题
3.能够解决中断与中断引起的竞态问题
4.中断屏蔽保护的临界区不能做休眠操作,并且执行速度要快;
长时间的屏蔽中断,会造成跟中断相关的系统机制会失效,严重影响系统的稳定,
有可能会造成数据的丢失,甚至会造成操作系统的崩溃。
5.这里研究的中断屏蔽,是屏蔽的本CPU的中断信号(屏蔽其中某个中断信号)
使用:
1.明确驱动代码哪些是共享资源
2.明确驱动代码哪些是临界区
3.访问临界区之前,屏蔽中断,方法:
unsigned long flags;
local_irq_save (flags);
说明: 屏蔽中断,并且保存中断的状态到flags中
4.访问临界区
临界区执行要快,更不能做休眠操作
不能解决多核的竞态问题
5.访问临界区之后,恢复中断,方法:
local_irq_restore (flags);
说明:恢复中断,并且恢复之前保存的中断状态
案例:要求LED设备同一时刻只能被一个应用软件打开(open)访问操作;
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//全局变量
static int open_cnt = 1; //共享资源
static int led_open(struct inode *inode, struct file *file)
{
unsigned long flags;
//中断屏蔽
local_irq_save(flags);
//不会发生中断
//不会发生进程的抢占
//临界区的代码执行路径具有原子性
//临界区
if (--open_cnt != 0) {
printk("设备已被打开!\n");
open_cnt++;
//恢复中断
local_irq_restore(flags);
return -EBUSY; //返回设备忙错误状态
}
//恢复中断
local_irq_restore(flags);
printk("打开设备成功!\n");
return 0;
}
static int led_close(struct inode *inode, struct file *file)
{
unsigned long flags;
local_irq_save(flags);
open_cnt++; //临界区
local_irq_restore(flags);
printk("关闭设备!\n");
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");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
printf("设备打开失败!\n");
return -1;
}
sleep(10000);
close(fd);
return 0;
}
ARM测试:1.insmod led_drv.ko
2../led_test & //启动A进程,让A进程后台运行
3.ps //查看A进程PID
4../led_test //启动B进程
5.kill A进程的PID
6../led_test //启动B进程
2.自旋锁
避免竞态方法之自旋锁
特点:1.自旋锁在使用时,一般要附加在某个共享资源上;
2.自旋锁保护的临界区代码执行要快,更不能进行休眠操作;
3.没有获取自旋锁的执行单元,将会原地打转进行忙等待,等待获取自旋锁;
4.自旋锁能够解决多核引起的竞态问题;
5.自旋锁能够解决进程与进程之间的抢占引起的竞态问题;
(中断引起的竞态不能解决)
自旋锁的数据类型:spinlock_t (结构体)
利用自旋锁保护临界区:
1.明确驱动中哪些是共享资源;
2.明确临界区中是否有休眠操作并且执行速度是否快;
3.定义初始化自旋锁
4.明确是否有中断参与竞态
如果有中断参与,不能使用自旋锁保护临界区
5.定义初始化自旋锁对象
spinlock_t lock;
spin_lock_init (&lock);
6.访问临界区之前,获取自旋锁
spin_lock (&lock);
说明:
如果获取自旋锁,立即返回
如果没有获取自旋锁,将在此原地忙等待,直到持有自旋锁的任务释放自旋锁
或者:
spin_trylock (&lock);
说明:
如果获取自旋锁,返回true
如果没有获取自旋锁,不会原地忙等待,也立即返回,返回false;
7.访问临界区
不能进行休眠操作
8.释放自旋锁
spin_unlock (&lock);
说明:
释放自旋锁,等待获取自旋锁的任务立即获取自旋锁
3.衍生自旋锁
避免竞态方法之衍生自旋锁
特点:1.衍生自旋锁基于自旋锁实现的;
2.它能够解决普通自旋锁不能解决中断引起的问题;
3.衍生自旋锁能够解决所有的竞态问题;
自旋锁的数据类型:spinlock_t
利用自旋锁保护临界区:
1.明确驱动中哪些是共享资源
2.明确驱动中哪些是临界区
3.明确临界区中是否有休眠操作并且执行速度是否 快;
4.明确是否有中断参与竞态
如果有中断参与,不能使用自旋锁保护临界区
如果没有中断参与,可以采用
5.定义初始化自旋锁对象
spinlock_t lock;
spin_lock_init(&lock);
6..访问临界区之前,获取自旋锁,屏蔽中断
unsigned long flags
spin_lock_irqsave(&lock, flags);
说明:
如果获取自旋锁,屏蔽中断,立即返回
如果没有获取自旋锁,将在此原地忙等待,直到持有自旋锁的任务释放自旋锁
7.访问临界区
不能进行休眠操作
8.访问临界区之后,释放自旋锁
spin_unlock_irqrestore(&lock, flags);
说明:
释放自旋锁,等待获取自旋锁的任务立即获取自旋锁
注意:
普通自旋锁除了中断引起的竞态问题都能解决;
衍生自旋锁能够解决所有的竞态问题;
保护的临界区都不能进行休眠;
案例:利用衍生自旋锁,实现一个LED设备只能被一个应用软件打开访问操作;
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//全局变量
static int open_cnt = 1; //共享资源
//定义自旋锁对象
static spinlock_t lock;
static int led_open(struct inode *inode, struct file *file)
{
unsigned long flags;
//屏蔽中断,获取自旋锁
//所有的竞态问题都能解决
spin_lock_irqsave(&lock, flags);
//临界区
if (--open_cnt != 0) {
printk("设备已被打开!\n");
open_cnt++;
//释放自旋锁,恢复中断
spin_unlock_irqrestore(&lock, flags);
return -EBUSY; //返回设备忙错误状态
}
//释放自旋锁,恢复中断
spin_unlock_irqrestore(&lock, flags);
printk("打开设备成功!\n");
return 0;
}
static int led_close(struct inode *inode, struct file *file)
{
unsigned long flags;
spin_lock_irqsave(&lock, flags);
open_cnt++; //临界区
spin_unlock_irqrestore(&lock, flags);
printk("关闭设备!\n");
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);
//初始化自旋锁对象
spin_lock_init(&lock);
return 0;
}
static void led_exit(void)
{
//卸载混杂设备
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
printf("设备打开失败!\n");
return -1;
}
sleep(10000);
close(fd);
return 0;
}
ARM测试:1.insmod led_drv.ko
2../led_test & //启动A进程,让A进程后台运行
3.ps //查看A进程PID
4../led_test //启动B进程
5.kill A进程的PID
6../led_test //启动B进程
4.信号量
避免竞态方法之信号量
特点:1.信号量又称睡眠锁;
2.信号量就是解决自旋锁保护的临界区不能休眠;
3.如果要获取信号量的进程没有获取信号量,进程将进入休眠状态,直到持有信号量的任务释放信号,并且唤醒休眠的进程
4.信号量一般用于进程
信号量的数据结构:struct semaphore
使用信号量保护临界区:
1.明确哪些是共享资源
2.明确哪些是临界区
3.明确临界区是否需要休眠,如有休眠,必须采用才方法;
4.定义初始化信号量
struct semaphore sema;
sema_init(&sema, 1); //初始化为互斥信号量
5.访问临界区之前,获取信号量
down(&sema);
说明:
1.如果获取信号量,立即返回;
2.如果没有获取信号量,进程将在此进入休眠状态,等待持有信号量的任务释放信号并且唤醒在此休眠的进程;
3.并且进程的休眠状态为不可中断的休眠状态
4. 不可中断的休眠状态是指当休眠进程接收到了信号,此进程不会被唤醒,
也不会立即处理此信号,只能由持有信号量的任务,释放信号量并且唤醒这个休眠的进程;
一旦休眠的进程被唤醒,需要处理之前接收到的信号;
5.此函数不能用于中断上下文
或者:
down_interruptible(&sema);
说明:
1.如果获取信号量,返回0
2.如果没有获取信号量,进程将进入可中断的休眠状态;
直到持有信号量的任务释放信号量并且唤醒在此休眠的进程;
3.可中断的休眠状态:
进程在休眠期间如果接收到了信号,会立即被信号唤醒和处理此信号,当然还能被持有信号量的任务唤醒;
4.如果是由于信号引起的唤醒,返回非0;
5.此函数一般要进行返回值的判断,来判断是由于获取信号量引起的唤醒还是接收到了信号引起的唤醒;
6.此函数不能用于中断上下文;
或者:
down_trylock(&sema);
说明:
1.获取信号量,返回false
2.没有获取信号,不会进行休眠操作,立即返回,返回true
3.可以用于中断上下文;
6.访问临界区
可以进行休眠操作
7.访问临界区之后,释放信号量还要唤醒休眠的进程
up(&sema);
案例:利用信号量,实现一个LED设备只能被一个应用软件打开访问操作;
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//定义信号量
static struct semaphore sema;
static int led_open(struct inode *inode, struct file *file)
{
//获取信号量,执行临界区
//没有获取信号量,立即返回
if (down_trylock(&sema)) {
printk("设备已被打开!\n");
return -EBUSY;
}
printk("打开设备成功!\n");
return 0;
}
static int led_close(struct inode *inode, struct file *file)
{
//释放信号量,唤醒休眠的进程
up(&sema);
printk("关闭设备!\n");
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);
//初始化信号量
sema_init(&sema, 1);
return 0;
}
static void led_exit(void)
{
//卸载混杂设备
misc_deregister(&led_misc);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
printf("设备打开失败!\n");
return -1;
}
sleep(10000);
close(fd);
return 0;
}
5.原子操作
linux内核解决竞态方法之原子操作
原子操作:操作具有原子性,不可再分分类:位原子操作和整形原子操作
位原子操作 = 原子性 + 位操作 =》位操作具有原子性
例如:int data = 0x55; //共享资源
//临界区
data &= ~(1 << 2); //此操作不具备原子性,可以发生CPU资源的切换,可以发生竞态
如果考虑竞态,对以上临界区进行保护;
方案1:采用中断屏蔽
local_irq_save;
data &= ~(1 << 2);
local_irq_restore;
方案2:采用自旋锁
spin_lock_irqsave (...);
data &= ~(1 << 2);
spin_unlock_irqrestore (...);
方案3:采用信号量
down_interruptible (...)
data &= ~(1 << 2);
up (...);
方案4:采用位原子操作
特点:将来如果发现临界区有对共享资源进行位操作,可以考虑采用
内核提供的位原子操作来避免竞态;
采用位原子操作避免竞态其实就是利用内核提供的一组函数即可完成位操作并且避免竞态,函数如下:
void set_bit (int nr, void *addr);
作用:将数据(地址为addr)的第nr位设置为1;
void clear_bit (int nr, void *addr);
作用:将数据(地址为addr)的第nr位设置0;
void chang_bit (int nr, void *addr);
作用:将数据(地址为addr)的第nr位反置;
int test_bit (int nr, void *addr);
作用:获取数据第nr位的值
注意:nr从0开始;
组合函数:
test_and_set_bit/...
案例:在驱动的入口函数,前提不能使用change_bit,将0x5555转换为0xaaaa;
2.2.整形原子操作
整形原子操作 = 整形数据的操作具有原子性
例如:
int open_cnt = 1; //共享资源
//临界区
--open_cnt; //不具备原子性
如果考虑到竞态,可以采用以下方法避免竞态:
1.采用中断屏蔽
2.采用自旋锁
3.采用信号量
4.采用整形原子操作
4.1.整形原子变量的数据类型为atomic_t(本质上是一个结构体)
4.2.编程:
1.定义初始化整形原子变量
atomic_t open_cnt = ATOMIC_INIT(1);
2.内核提供了一组函数对整形原子变量进行整形操作(此操作具有原子性)
atomic_add(2, &open_cnt) //加2
atomic_sub(2, &open_cnt); //减2
atomic_inc(&open_cnt); //++
atomic_dec(&open_cnt); //--
atomic_dec_and_test(&open_cnt); //先--,然后判断其值是否为0,如果为0返回true,否则返回false
...
注意:位原子操作和整形原子操作他们都会使用两条特殊的原子指令:ldrex,strex (原子加载和存储指令)
案例:利用整形原子操作,实现一个设备只能被一个应用软件打开访问操作;
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
//定义整形原子变量
static atomic_t open_cnt = ATOMIC_INIT(1);
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;
}
static int led_close(struct inode *inode, struct file *file)
{
atomic_inc(&open_cnt);
printk("关闭设备!\n");
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");
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(void)
{
int fd;
fd = open("/dev/myled", O_RDWR);
if (fd < 0) {
printf("设备打开失败!\n");
return -1;
}
sleep(10000);
close(fd);
return 0;
}