1.基本概念
竞态,竞争的状态
竞争共享资源
共享资源
硬件(串口 LCD 声卡 。。。)
多线程可见的全局变量,多线程编程实现的生产者消费者模型中的仓库
进程之间使用的共享内存
临界区,访问共享资源
系统中产生竞态的原因:
1)SMP(对称多处理器)
2)任务和任务之间的抢占
3)任务和中断服务之间的抢占
4)中断服务程序和中断服务程序之间的抢占
练习:希望按键能够实现独占式访问
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
MODULE_LICENSE("GPL");
struct cdev btn_cdev;
dev_t dev;
struct class *cls;
int cat=1;
int btn_open(struct inode *inode,struct file *filp){
if(!cat){printk("<2>" "fail %s!\n",__func__);return -1;}
--cat;
printk("<2>" "open %s\n",__func__);return 0;
}
int btn_close(struct inode *inode,struct file *filp){
++cat;
printk("<2>" "close %s\n",__func__);return 0;
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_close,
};
int __init btn_drv_init(void)
{
/*申请设备号*/
alloc_chrdev_region(&dev,0,1,"mybuttons");
/*初始化cdev*/
cdev_init(&btn_cdev,&btn_fops);
/*注册cdev*/
cdev_add(&btn_cdev,dev,1);
/*创建设备文件*/
cls = class_create(THIS_MODULE,"mybuttons");
device_create(cls,NULL,dev,NULL,"mybuttons");
return 0;
}
void __exit btn_drv_exit(void)
{
/*销毁设备文件*/
device_destroy(cls,dev);
class_destroy(cls);
/*注销cdev*/
cdev_del(&btn_cdev);
/*注销设备号*/
unregister_chrdev_region(dev,1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
2.Linux系统中解决竞态的策略:
2.1中断屏蔽(单核有效)
关中断CPSR.I=1
local_irq_disable();或者
local_irq_save(flags); //先将CPRS.I取值保存到flags,再执行CPSR.I=1
访问共享资源
开中断CPSR.I=0
local_irq_enable();或者
local_irq_restore(flags); //先将CPRS.I取值保存到flags,再执行CPSR.I=0
单核处理器中很好用,能够解决任务和任务之间的抢占,任务和中断,中断和中断之间的抢占
注意,关中断时间一定要短
因为内核中很多重要机制都是通过中断实现
一旦关中断时间过长,很容易造成内核崩
建议实际驱动编程时尽量少使用该方式
××××××××××
2.2原子操作
指的就是整个操作要么全做,要么全不做,不可再分
内核中提供的原子操作有两大类:
1)位原子操作
//将地址addr中的第nr位置1,整个操作过程中间不被打断,保持原子性
set_bit(nr,void *addr);
...
2)整型原子操作
typedef struct{
int counter;
}atomic_t;
//*v -=i;
atomic_sub(int i,atomic_t *v);
//*v =i;
atomic_add(int i,atomic_t *v);
代码案例:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
MODULE_LICENSE("GPL");
struct cdev btn_cdev;
dev_t dev;
struct class *cls;
/*定义原子变量*/
atomic_t btn_tv;
int btn_open(struct inode *inode,struct file *filp){
if(!atomic_dec_and_test(&btn_tv))
{atomic_inc(&btn_tv);return -1;}
return 0;
}
int btn_close(struct inode *inode,struct file *filp){
atomic_inc(&btn_tv);
printk("<2>" "close %s\n",__func__);return 0;
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_close,
};
int __init btn_drv_init(void)
{
/*申请设备号*/
alloc_chrdev_region(&dev,0,1,"mybuttons");
/*初始化cdev*/
cdev_init(&btn_cdev,&btn_fops);
/*注册cdev*/
cdev_add(&btn_cdev,dev,1);
/*创建设备文件*/
cls = class_create(THIS_MODULE,"mybuttons");
device_create(cls,NULL,dev,NULL,"mybuttons");
/*初始化原子变量*/
atomic_set(&btn_tv,1);
return 0;
}
void __exit btn_drv_exit(void)
{
/*销毁设备文件*/
device_destroy(cls,dev);
class_destroy(cls);
/*注销cdev*/
cdev_del(&btn_cdev);
/*注销设备号*/
unregister_chrdev_region(dev,1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
2.3自旋锁
自旋锁,最多只能有一个持有单元,试图获取一把已经被其他执行单元持有的
核心数据结构:
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
使用步骤:
1)定义一把自旋锁
spinlock_t btn_lock;
2)初始化自旋锁
spin_lock_init(&btn_lock);
3)获取自旋锁
//获取自旋锁不成功,原地自旋等待
spin_lock(&btn_lock);
//获取自旋锁不成功,直接返回错误值
spin_trylock(&btn_lock);
4)访问共享资源
5)释放共享资源
spin_unlock(&btn_lock);
注意事项:
1)获取和释放自旋锁要成对出现
2)Linux内核中不希望出现获取自旋锁不成功,原地自选等待的情况
出现该情况会大量消耗cpu资源
内核要求持有自旋锁的时间应该比较短
代码案例:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
MODULE_LICENSE("GPL");
struct cdev btn_cdev;
dev_t dev;
/*定义自旋锁*/
spinlock_t btn_lock;
struct class *cls;
static int cnt = 1;
int btn_open(struct inode *inode,struct file *filp){
/*获取自旋锁*/
spin_lock(&btn_lock);
if(!cnt){spin_unlock(&btn_lock);return -1;}
--cnt;
/*释放自旋锁*/
spin_unlock(&btn_lock);
return 0;
}
int btn_close(struct inode *inode,struct file *filp){
/*获取自旋锁*/
spin_lock(&btn_lock);
++cnt;
/*释放自旋锁*/
spin_unlock(&btn_lock);
return 0;
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_close,
};
int __init btn_drv_init(void)
{
/*申请设备号*/
alloc_chrdev_region(&dev,0,1,"mybuttons");
/*初始化cdev*/
cdev_init(&btn_cdev,&btn_fops);
/*注册cdev*/
cdev_add(&btn_cdev,dev,1);
/*创建设备文件*/
cls = class_create(THIS_MODULE,"mybuttons");
device_create(cls,NULL,dev,NULL,"mybuttons");
/*初始化自旋锁*/
spin_lock_init(&btn_lock);
return 0;
}
void __exit btn_drv_exit(void)
{
/*销毁设备文件*/
device_destroy(cls,dev);
class_destroy(cls);
/*注销cdev*/
cdev_del(&btn_cdev);
/*注销设备号*/
unregister_chrdev_region(dev,1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
问题:用户态能使用内核函数吗?如果能,是如何使用的?
问题:有自旋锁了还要cnt变量干嘛?
答:自旋锁需要尽快解锁,cnt变量是一个共享资源,而信号量可以持续持有,无需尽快交还便可以不用cnt变量。
2.4信号量
信号量,允许有多个持有单元,如果获取信号量不成功,睡眠等待
核心数据结构:
struct semphore
使用步骤:
1)定义一个semaphore变量
struct semaphore btn_sem;
2)初始化信号量
//val的取值决定了信号量可以有几个持有单元
void sema_init(struct semaphore *sem,int val);
3)获取信号量
a)void down(struct semaphore *sem);
b)int down_interruptible(struct semaphore *sem);
c)int down_killable(struct semaphore *sem);
d)int down_timeout(struct semaphore *sem, long jiffies);
4)访问恭喜资源
5)释放信号量
void up(struct semaphore *sem)
代码案例:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
MODULE_LICENSE("GPL");
struct cdev btn_cdev;
dev_t dev;
/*定义信号量*/
struct semaphore btn_sem;
struct class *cls;
static int cnt = 1;
int btn_open(struct inode *inode,struct file *filp){
/*获取信号量*/
down(&btn_sem);
if(!cnt){up(&btn_sem);return -1;}
--cnt;
/*释放信号量*/
up(&btn_sem);
return 0;
}
int btn_close(struct inode *inode,struct file *filp){
down(&btn_sem);
++cnt;
up(&btn_sem);
return 0;
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_close,
};
int __init btn_drv_init(void)
{
/*申请设备号*/
alloc_chrdev_region(&dev,0,1,"mybuttons");
/*初始化cdev*/
cdev_init(&btn_cdev,&btn_fops);
/*注册cdev*/
cdev_add(&btn_cdev,dev,1);
/*创建设备文件*/
cls = class_create(THIS_MODULE,"mybuttons");
device_create(cls,NULL,dev,NULL,"mybuttons");
/*初始化信号量*/
sema_init(&btn_sem,1);
return 0;
}
void __exit btn_drv_exit(void)
{
/*销毁设备文件*/
device_destroy(cls,dev);
class_destroy(cls);
/*注销cdev*/
cdev_del(&btn_cdev);
/*注销设备号*/
unregister_chrdev_region(dev,1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
2.5信号量和自旋锁的对比
1.资源不可用时
自旋锁保护的临界区要求执行速度要尽量快,避免出现获取不成功原地自旋的情况。
信号量保护的临界区执行速度快慢无所谓,获取信号量不成功的进行睡眠等待。
2.一旦资源可用时
自旋锁获取设可以第一时间开始使用共享资源
信号量获取者,需要被唤醒,去竞争cpu,一旦获取到cpu才能继续运行,访问共享资源
3.持有单元
自旋锁只能有一个持有单元
信号量可以有多个持有单元
2.6 down和down_interruptible的区别
down,属于深度睡眠,只有获取信号量成功才会醒来,发信号是唤不醒的
down_interruptible,属于浅度睡眠,获取信号量成功和信号打断的都可以唤醒睡眠的进程
如果不关注down_interruptbile的返回值,代码存在如下BUG
1)insmod btn_drv.ko
2) ./test 打开设备成功
3) ./test 睡眠等待
4)ctrl + c
5) ./test 打开设备成功 因为1号进程还没释放信号量你这却打开成功了是BUG
练习:解决以上BUG