很多朋友对于深入解析嵌入式Linux驱动开发(第五篇)和不太懂,今天就由小编来为大家分享,希望可以帮助到大家,下面一起来看看吧!
【嵌入式牛人入门】相关控件和命令简单介绍
【嵌入式牛鼻子】Linux设备驱动中的并发控制
【嵌入式牛问】什么是并发控制?为什么会出现并发控制呢?
【嵌入牛文】
在研究应用层的时候,我们了解到了多个进程处理共享资源的情况。其实驱动中也有类似的情况,而且相对于应用层来说,并发的情况更多一些。
并发是指多个执行单元同时并行执行,并发执行单元对共享资源。访问(硬件资源和软件全局变量、静态变量等)很容易导致竞争条件。
造成竞态条件的主要原因如下:
1.对称多处理器CPU
2.单个CPU中的进程和抢占它的进程
3. 中断(硬中断、软中断、Tasklet、bottom half)和进程之间
在我们对应用层的研究中,对关键资源的保护主要包括信号量、互斥体等。内核中的并发处理也有类似的机制,除此之外还有其他的机制。让我们仔细看看;
1、中断屏蔽
local_irq_disable() /* 屏蔽中断*/。
临界区/* 临界区*/。
local_irq_enable() /* 使能中断*/
中断屏蔽的使用非常简单。进入临界区时,使用屏蔽中断功能,退出临界区时再开启中断功能。但需要注意的一点是,被屏蔽的中断只是CPU的中断,其他CPU的中断无法被屏蔽,所以在多核CPU中发挥的作用有限。
2、原子操作
原子操作可以保证对一个整型数据(注意只有整型数据)的修改是排他的。 Linux提供了一系列API来实现内核中的原子操作。这些API 分为两类。一是整数数据的操作。一种类型是对位的原子操作。原子操作最终是由硬件来保证的。因此,它与CPU的架构密切相关。在ARM架构中,底层最终使用的是LDREX和STREX指令。
2.1 整型原子操作
使用起来比较简单。内核已经为我们写好了API函数。我们可以将其作为参考。
1.设置原子变量的值
voidatomic_set(v, i) //设置原子变量为i
atomic_t ATOMIC_INIT(i) //定义原子变量v并初始化为0
2.获取原子变量的值
atomic_read(v) //返回原子变量的值
3. 原子变量加/减
voidatomic_add(int i,atomic_t *v) //将i添加到原子变量
voidatomic_sub(int i,atomic_t *v) //原子变量减去i
4. 原子变量递增/递减
voidatomic_inc(atomic_t *v) //原子变量加1
voidatomic_dec(atomic_t *v) //原子变量减1
5. 运行与测试
atomic_sub_and_test(i, v) 原子减i 并测试
atomic_dec_and_test(v) 将原子变量减1 并测试它
atomic_inc_and_test(v) 原子变量加1并测试
上述操作对原子变量进行减i、自减、自增操作后,测试是否为0,如果为0则返回true,否则返回false
6 操作与返回
intatomic_add_return(int i,atomic_t *v);
intatomic_sub_return(int i,atomic_t *v);
intatomic_inc_return(atomic_t *v);
intatomic_dec_return(atomic_t *v);
上述操作对原子变量进行加/减、自增/自减操作并返回新值。
总结一下使用原子操作的步骤(想想在应用层使用信号量的步骤):
1.初始化一个原子变量,一般为0或1,(1表示第一次可以成功获取,0表示等待释放后才能使用,我们初始化为1)
2.运行测试实际上是为了尝试获取关键资源,所以就是使用自减测试或者减i测试。自动添加和合并测试很少使用。
3. 运营关键资源
4. 释放原子变量
我们利用文中的例程,在驱动程序中动态创建设备号和设备节点并添加相应的程序,使得驱动程序同一时间只能打开一次。 (添加的程序用+++++++++++表示,没办法,CSDN的编辑器还是那么烂,某行程序的颜色或者字体大小无法单独显示)
包括
包括
包括
包括
包括
包括
包括
包括
MODULE_LICENSE("GPL");
dev_t devno;
整数专业=0;
int 小数=0;
整数计数=1;
结构体cdev *pdev;
结构体类*pclass;
结构设备* pdevice;
atomic_t v=ATOMIC_INIT(1); //初始化一个原子变量++++++++++++++++++
int demo_open(结构inode * inodep, 结构文件* filep)
{
if(!atomic_sub_and_test(1, v)) //获取原子变量++++++++++++++++++++
{
printk("v:%dn",atomic_read(v));
原子添加(1,v);
返回-EBUSY;
}
printk("%s,%dn", __func__, __LINE__);
返回0;
}
int demo_release(结构inode *inodep, 结构文件*filep)
{
printk("%s,%dn", __func__, __LINE__);
原子增量(v); //释放原子变量++++++++++++++
返回0;
}
结构文件操作fops={
.owner=这个模块,
.open=demo_open,
.release=demo_release,
};
静态int __init demo_init(void)
{
int ret=0;
printk("%s,%dn", __func__, __LINE__);
ret=alloc_chrdev_region(devno,次要,计数,"xxx");
如果(返回)
{
printk("分配_chrdev_region失败。n");
返回ret;
}
printk("devno:d,major:d,minor:dn",devno,MAJOR(devno),MINOR(devno));
pdev=cdev_alloc();
如果(pdev==NULL)
{
printk("cdev_alloc 失败。n");
转到错误1;
}
cdev_init(pdev, fops);
ret=cdev_add(pdev, devno, 计数);
如果(ret 0)
{
printk("cdev_add 失败。");
转到错误2;
}
pclass=class_create(THIS_MODULE, "myclass");
if(IS_ERR(pclass))
{
printk("创建类失败。n");
ret=PTR_ERR(pclass);
转到错误3;
}
pdevice=device_create(pclass, NULL, devno, NULL, "你好");
if(IS_ERR(pdevice))
{
printk("设备创建失败。n");
ret=PTR_ERR(pdevice);
转到错误4;
}
返回0;
错误4:
class_destroy(pclass);
错误3:
cdev_del(pdev);
错误2:
kfree(pdev);
错误1:
unregister_chrdev_region(devno, 计数);
返回ret;
}
静态无效__exit demo_exit(void)
{
printk("%s,%dn", __func__, __LINE__);
device_destroy(pclass, devno);
class_destroy(pclass);
cdev_del(pdev);
kfree(pdev);
unregister_chrdev_region(devno, 计数);
}
module_init(demo_init);
module_exit(demo_exit);
2.2 位原子操作
1. 设置位
无效set_bit(int nr, 易失性无效*addr)
设置addr地址的第nr位并将1写入nr位
2. 清除位
voidclear_bit(int nr, unsigned long *addr)
清除addr的nr位
3. 更改位
无效change_bit(无符号长nr,易失性无效* addr)
反转地址addr 处的第nr 位
4、测试位置
int test_bit(无符号int nr, const 无符号长*addr)
上述操作返回addr地址的第nr位
5. 测试和操作位
int test_and_set_bit(无符号nr,易失性无符号长*addr)
int test_and_clear_bit(无符号nr,易失性无符号长*addr)
int test_and_change_bit(无符号nr,易失性无符号长*addr)
上述操作相当于执行了测试,然后执行操作位相关函数。
3、自旋锁
自旋锁是对关键资源进行互斥访问的典型手段。这个机制很容易从字面上理解。我们可以将其理解为不断轮询某个变量。该变量将持续存在,直至被释放。轮询直到释放变量并获得对关键资源的访问权限。
Linux中与自旋锁相关的操作包括以下几种:
1.定义自旋锁
spinlock_t 锁
2.初始化自旋锁
spin_lock_init(spinlock_t *_lock)
3.获取自旋锁
void spin_lock(spinlock_t *lock) 获取自旋锁。如果不成功,就会被收购,直到成功为止。
void spin_lock_irq(spinlock_t *lock) 获取自旋锁,成功后关闭中断,相当于spin_lock +local_irq_disable
spin_lock_irqsave(lock, flags) 循环等待,直到自旋锁解锁(设置为1),然后锁定自旋锁(设置为0)。关闭中断并将状态寄存器值存储在标志中。
spin_lock_bh(锁)
int spin_trylock(spinlock_t *lock) 循环等待,直到自旋锁解锁(设置为1),然后锁定自旋锁(设置为0)。阻止软中断下半部分的执行。
上面获取自旋锁的过程是如果获取不成功则直接返回FALSE,如果成功则返回TRUE。
4. 释放自旋锁
无效spin_unlock(spinlock_t *lock)
void spin_unlock_irq(spinlock_t *lock) 相当于spin_unlock+local_irq_enable
spin_unlock_irqrestore(lock, flags) 解锁自旋锁(设置为1)。打开中断并将标志中的状态寄存器值存储到状态寄存器中。
s pin_unlock_bh(lock) 解锁自旋锁(设置为1)。打开下半部分的执行。
自旋锁的使用过程
/*定义自旋锁*/
spinlock_t 锁;
/*初始化自旋锁*/
spin_lock_init(锁);
/*获取自旋锁*/
自旋锁(锁);
/*执行关键操作*/
.
/*释放自旋锁*/
spin_unlock(锁定);
当有中断抢占资源时,我们通常会在进程中调用spin_lock_irqsave/spin_unlock_irqrestore与中断中的spin_lock/spin_unlock一起使用。
使用自旋锁时我们必须非常小心,主要是因为以下几点
1、自选锁相当于不断轮询。当等待自旋锁时,当前CPU只是无意义的等待,不能做其他事情。因此,自旋锁内的临界区必须尽可能短。
2.自旋锁可能会导致死锁。例如,获取锁后,如果再次获取锁,CPU就会死锁。
3、自旋锁期间,不能调用可能导致进程调度的函数。如果进程在获得自旋锁后阻塞,可能会导致内核崩溃。
例程:驱动文件不能同时打开
包括
包括
包括
包括
包括
包括
包括
MODULE_LICENSE("GPL");
dev_t devno;
整数专业=0;
int 小数=0;
整数计数=1;
int open_count=0;
结构体cdev *pdev;
结构体类*pclass;
结构设备* pdevice;
静态spinlock_t open_lock; //++++++++++++++++++++++++++
int demo_open(结构inode * inodep, 结构文件* filep)
{
spin_lock(open_lock); //++++++++++++++++++++++++
if(open_count){ //++++++++++++++++++++++
spin_unlock(open_lock);//++++++++++++++++++
返回-EBUSY; //++++++++++++++++++++
} //++++++++++++++++++++++
开放计数++; //++++++++++++++++++++++++
spin_unlock(open_lock); //++++++++++++++++++++++++
printk("%s,%dn", __func__, __LINE__);
返回0;
}
int demo_release(结构inode *inodep, 结构文件*filep)
{
spin_lock(open_lock); //++++++++++++++++++++++
open_count--; //++++++++++++++++++++++
spin_unlock(open_lock); //++++++++++++++++++++
printk("%s,%dn", __func__, __LINE__);
返回0;
}
结构文件操作fops={
.owner=这个模块,
.open=demo_open,
.release=demo_release,
};
静态int __init demo_init(void)
{
int ret=0;
printk("%s,%dn", __func__, __LINE__);
ret=alloc_chrdev_region(devno,次要,计数,"xxx");
如果(返回)
{
printk("分配_chrdev_region失败。n");
返回ret;
}
printk("devno:d,major:d,minor:dn",devno,MAJOR(devno),MINOR(devno));
pdev=cdev_alloc();
如果(pdev==NULL)
{
printk("cdev_alloc 失败。n");
转到错误1;
}
cdev_init(pdev, fops);
ret=cdev_add(pdev, devno, 计数);
如果(ret 0)
{
printk("cdev_add 失败。");
转到错误2;
}
pclass=class_create(THIS_MODULE, "myclass");
if(IS_ERR(pclass))
{
printk("创建类失败。n");
ret=PTR_ERR(pclass);
转到错误3;
}
pdevice=device_create(pclass, NULL, devno, NULL, "你好");
if(IS_ERR(pdevice))
{
printk("设备创建失败。n");
ret=PTR_ERR(pdevice);
转到错误4;
}
返回0;
错误4:
class_destroy(pclass);
错误3:
cdev_del(pdev);
错误2:
kfree(pdev);
错误1:
unregister_chrdev_region(devno, 计数);
返回ret;
}
静态无效__exit demo_exit(void)
{
printk("%s,%dn", __func__, __LINE__);
device_destroy(pclass, devno);
class_destroy(pclass);
cdev_del(pdev);
kfree(pdev);
unregister_chrdev_region(devno, 计数);
}
module_init(demo_init);
module_exit(demo_exit);
4、信号量
信号量的思想和应用层是一样的。原理就不详细解释了。主要是光伏运营。
1.定义信号量
结构信号量sem;
2.初始化信号量
void sema_init(结构信号量*sem, int val)
将信号量值初始化为val
3.获取信号量P操作
void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_trylock(结构信号量*sem)
前两者的区别在于,第一个函数获取信号量失败后,没有信号(不是信号量)中断执行,进入睡眠状态,直到被cup唤醒。没有人可以打扰它。第二个功能不同。获取信号量失败后,如果此时没有信号中断,则会进入休眠。在睡眠期间,它可以被信号中断。我在网上看到一个非常生动的例子。天黑的时候你就去睡觉,天亮的时候你又醒来。这就是向下函数。天黑了,去睡觉吧,如果天还黑,闹钟响了,那就起床。这样做是为了防止信号量死锁和整个程序挂起。例如,极夜现象发生在北极的冬季,但你不能只睡觉而不醒来。您仍然可以被闹钟叫醒。
第三种方法是不会造成阻塞的方法。如果无法获取,则返回非零错误值并继续执行。
4.释放信号量V操作
void up(struct semaphore *sem);
5、互斥体
我们在应用层也使用了互斥。关于这个想法没有什么可说的。让我们直接进入函数吧。
1. 定义互斥锁
结构互斥体my_mutex;
2. 初始化互斥锁
mutex_init(互斥锁)
3. 获取互斥体
void mutex_lock(struct mutex *lock)
mutex_lock_interruptible(struct mutex *lock)
int mutex_trylock(struct mutex *lock)
类似于信号量
4. 释放互斥体
void mutex_unlock(struct mutex *lock)
信号量和互斥体的功能非常相似。为了保护关键资源,我们通常使用互斥体。对于类似的生产/消费问题,我们可以使用信号量来解决。
我们来比较一下原子操作、自旋锁和互斥体之间的区别。
我们可以将这三个过程理解为设置标志位、递增标志位、递减标志位、获取标志位。它们之间最大的区别在于收购不成功时的下一步行动。
如果原子操作不成功,则跳过向下执行,就像if(flag)else过程一样。
如果没有成功获取自旋锁,就会一直等待,即if(flag) --- if(flag) --- if(flag) --- if(flag),或者直接,while(!flag );收购不成功,如果成功了,你就不会离开。
如果互斥获取不成功,就相当于if(flag),else{切换进程}(当然互斥也是存在的,如果互斥获取不成功,就直接返回,执行下面的其他程序,这就很了类似于原子操作)
我们主要区分一下自旋锁和互斥锁选择的原理。
1、当无法获取锁时,使用互斥锁的成本是进程上下文切换时间,而自旋锁则是等待获取自旋锁的时间。如果临界区较小,自旋锁的等待时间是可以接受的。如果临界区很大,最好使用互斥体。
2. 如果临界区包含会导致阻塞的代码,则必须使用互斥体。在自旋锁的情况下,阻塞代码会导致程序死锁。
3.中断或软中断期间不允许休眠,因此如果在中断或软中断期间保护关键资源,请使用自旋锁或不会导致阻塞的mutex_trylock。
————————————————
版权声明:本文为CSDN博主“念念有语”原创文章,遵循CC 4.0 BY-SA版权协议。转载时请附上原文出处链接及本声明。
关于深入解析嵌入式Linux驱动开发(第五篇)的内容到此结束,希望对大家有所帮助。
【深入解析嵌入式Linux驱动开发(第五篇)】相关文章:
2.米颠拜石
3.王羲之临池学书
8.郑板桥轶事十则
用户评论
终于等到这部分内容了!一直想了解Linux驱动是怎么学的。
有5位网友表示赞同!
这篇文章一定很详细吧?
有18位网友表示赞同!
希望可以学到一些实际操作的技巧,做个真正在用到的项目。
有16位网友表示赞同!
嵌入式开发一直是我感兴趣的方向,这个系列文章很有帮助。
有17位网友表示赞同!
看标题感觉内容很专业,能理解一些技术细节吗?
有15位网友表示赞同!
学习一个新的操作系统驱动确实需要不少时间和精力吧。
有7位网友表示赞同!
之前对Linux驱动了解很少,看了这篇文章应该对它有更深入的认识了。
有14位网友表示赞同!
嵌入式行业应用很广泛,掌握驱动开发很有实用价值。
有20位网友表示赞同!
有没有什么好的学习资源可以推荐?
有6位网友表示赞同!
我对Linux内核代码本身也有兴趣,这篇文章能介绍吗?
有8位网友表示赞同!
学嵌入式要理解硬件才能看懂驱动程序吧?
有13位网友表示赞同!
想做个小型的嵌入式系统,驱动开发是必不可少的环节。
有8位网友表示赞同!
希望作者能够用通俗易懂的语言讲解复杂的知识点。
有17位网友表示赞同!
这篇文章能帮助我更好地理解如何将硬件与软件结合?
有7位网友表示赞同!
驱动程序的调试过程应该很麻烦吧? 文章会介绍一些方法吗?
有20位网友表示赞同!
嵌入式开发和传统应用开发有哪些区别呢?
有14位网友表示赞同!
学习Linux驱动需要什么基础知识?
有17位网友表示赞同!
我想知道如何在实际项目中应用这些知识。
有10位网友表示赞同!
期待作者分享更多关于嵌入式开发的经验!
有16位网友表示赞同!