驱动:7.1内核中的竞态与并发

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值