《[arm驱动]linux并发与竞态—并发操控》触及内核驱动函数五个,内核结构体一个,剖析了内核驱动函数六个;可参阅的相关使用程序模板或内核驱动模板五个,可参阅的相关使用程序模板或内核驱动零个
一、并发与竞态
1、并发:多个碑文单元一起被碑文。例如:同一个test.out可碑文程序被n次一起运转
2、竞态:并发的碑文单元对同享资源(硬件资源和软件上的全局变量,静态变量等)的拜访导致的劣质。
a)静态的列子:
char *p;//全局变量
// 读取函数
module_drv_read(struct file *file, constchar __user *buf, size_t count, loff_t * ppos)
{copy_to_user(buf, p, countt);}
//写入函数
module_drv_write(struct file *file, constchar __user *buf, size_t count, loff_t * ppos)
{copy_from_user(p, buf, countt)}
假如有两个进程一起在运转,一个碑文read,一个碑文write,那么碑文read的或许会读取到第二个程序的写入值或只读取到第二个程序部分写入值。
二、怎么防止竞态:(有同享就有或许产生劣质)
办法:处理竞态的常用技能是加锁或许互斥,对应semaphore(旗标)机制、spin_lock机制
三、信号量(semaphore)机制
1、旗标(信号量)
旗标(信号量):是一个整型值,结合一对函数void down(struct semaphore * sem)、void up(struct semaphore *sem),假如旗标的值大于0,这个值将减一而且持续进程。相反,假如旗标的值是0(或许更小),此进程有必要等候其他进程开释旗标,解锁旗标经过调用up完结,up函数会添加旗标的值。
Tip:Linux内核的信号量在概念和原理上与用户态的信号量是相同的,但它不能在内核之外运用
2、信号量机制编程
a)信号的声明初始化有两种办法:办法1宏界说并初始化;办法2动态初始化
信号的声明初始化办法1)宏界说并初始化信号量的快捷办法
DECLARE_MUTEX(name)
DECLARE_MUTEX_LOCKED(name)
内核代码1)DECLARE_MUTEX和DECLARE_MUTEX_LOCKED他们的内核代码为
#define DECLARE_MUTEX(name) __DECLARE_SEMAPHORE_GENERIC(name, 1)
#define DECLARE_MUTEX_LOCKED(name) __DECLARE_SEMAPHORE_GENERIC(name, 0)
//__DECLARE_SEMAPHORE_GENERIC的内核代码
#define __DECLARE_SEMAPHORE_GENERIC(name,count) \
structsemaphore name = __SEMAPHORE_INITIALIZER (name,count)
模板一)宏界说初始化办法信号量模板
DECLARE_MUTEX(name);//界说信号量
down(&name);//加锁,维护临界区在开释锁前,将拜访本临界区的进程参加等候行列中
//….要修正全局变量在这修正…..
up(&name)//开释锁
信号的声明初始化办法2)动态初始化办法
1、界说信号量结构体 struct semaphore sem;
structsemaphore sem;
结构体一)struct semaphore内核源码
struct semaphore {
atomic_t count;//旗帜数值
intsleepers;
wait_queue_head_t wait;//等候行列
};
2、初始化结构体struct semaphore sem有两种办法,宏界说初始化与函数初始化
初始化结构体sem办法a)用函数sema_init (struct semaphore *sem, int val)初始化结构体struct semaphore sem。
内核源码二)sema_init内核原型代码
static inline void sema_init (structsemaphore *sem, intval)
{
atomic_set(&sem->count, val);
init_waitqueue_head(&sem->wait);
//假如进程调用down时,发现信号量为0时就将进程参加到sem->wait等候行列中
}
模板二)sema_init初始化模板
struct semaphore sem;
sema_init (&sem, 1);一开始就将旗标置1
down(&sem);
up(&sem);
初始化结构体sem办法b)宏初始化,分为将sem的旗标直接初始化为1和0的两种宏初始化办法
1)init_MUTEX (struct semaphore *sem)将semaphore的旗标初始化为1
内核源码三)init_MUTEX内核原型代码
staticinline voidinit_MUTEX (struct semaphore *sem)
{
sema_init(sem, 1);//能够看出调用了上面的sema_init函数
// 该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1
}
2)init_MUTEX_LOCKED (struct semaphore *sem)将semaphore的旗标初始化为0
内核源码四)init_MUTEX_LOCKED内核原型代码
staticinlinevoidinit_MUTEX_LOCKED (structsemaphore *sem)
{
sema_init(sem, 0);
//该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状况。
}
模板三)宏初始化模板变量sem
struct semaphore sem;
init_MUTEX(&sem);一开始就将旗标置1
down(&sem);
up(&sem);
四、自旋锁
1、 概念:自旋锁最多只能被一个可碑文单元持有。自旋锁不会引起调用者睡觉,假如一个碑文线程小事取得一个现已被持有的自旋锁,那么这个线程就会一向进行忙循环,一向等候下去,在那里看是否该自旋锁的坚持者现已开释了锁,“自旋”便是这个意思
2、自旋锁的编程过程a,b,c,d
a)声明一个spinlock_t 变量lock
spinlock_t lock;
b)初始化spinlock_t 变量lock
有两种初始化办法:宏初始化与函数初始化
1)宏初始化办法SPIN_LOCK_UNLOCKED
spinlock_t lock = SPIN_LOCK_UNLOCKED;
2)函数初始化办法spin_lock_init(lock)
spin_lock_init(lock)
内核源码五)spin_lock_init的内核源码
//内核源码spin_lock_init发现运用SPIN_LOCK_UNLOCKED
#define spin_lock_init(lock) do{ *(lock) = SPIN_LOCK_UNLOCKED; } while(0)
c)取得自旋锁
1、取得自旋锁,spin_lock(lock) ;假如成功,当即取得锁,并立刻回来,不然它将一向自旋在那里,直到该自旋锁的坚持者开释。
spin_lock(lock)
//获取自旋锁lock,假如成功,当即取得锁,并立刻回来,不然它将一向自旋在那里,直到该自旋锁的坚持者开释。
内核源码六)spin_lock(lock)内核源码剖析
//源码内核(能够看出来spin_lock没有获取锁,它将一向做while循环)
spin_lock(lock) _spin_lock(lock)
#define _spin_lock(lock) __LOCK(lock)
#define __LOCK(lock) do{ preempt_disable(); __acquire(lock); (void)(lock); } while(0)
#define preempt_disable() do{ } while(0)
//preempt_disable做空循环
2、小事获取自旋锁函数的spin_trylock(lock),假如能当即取得锁,并回来真,不然当即回来假。它不会一向等候被开释。(spin_trylock(lock)不做忙等候)
d)开释自旋锁lock,函数spin_unlock(lock) 它与spin_trylock或spin_lock配对运用。
3、自旋锁spinlock运用模板
模板四)自玄锁模板
spinlock_t lock;
spin_lock_init(&lock);
spin_lock(&lock);
spin_unlock(&lock);
Tip:酿制维护的值(全局变量,静态变量,硬件资源)放在spin_lock(&lock)与spin_unlock(&lock)之间操作
spin_lock(&lock);
//改动要维护的全局变量或硬件资源或静态变量(同享变量)
spin_unlock(&lock);
4、特别运用场景:有些设备只答应被翻开一次,那么就需要一个自旋锁维护一共设备的翻开和封闭状况的变量count。此处count归于临界资源,假如不对count进行维护,当设备翻开频频时,或许呈现过错的count计数。
模板五)只答应被翻开一次的设备驱动模板
intcount = 0;
spinlock_t lock;
intxxx_init(void){
//…其他代码..
spin_lock_init(&lock);
//…其他代码..
}
intxxx_open(structinode *inode, structfile *file){
spin_lock(&lock);//假如相同程序的进程要用open,那么其他进程就进入忙比及while(1)
if(count){
spin_unlock(&lock);
return-EBUSY;
}
count++;
spin_unlock(&lock);
//…..
}
intxxx_release(structinode *inode, structfile *file){
//….
spin_lock(&lock);//封闭时也要进行spin_lock维护,由于count是全局变量,要改动count就要加自旋锁
count–;
spin_unlock(&lock);
//…..
}
Tip:linux自旋锁和信号量所选用的”加锁(down)—拜访临界区(critical section)—开释锁”的办法,被称为”互斥三部曲”。
五、总结:编写对同享资源(硬件资源和软件上的全局变量,静态变量等)的拜访的驱动程序场景,就要考虑防止竞态的产生;防止办法:信号量(或说旗标,semaphore)机制与spin_lock机制(自旋锁)。
六、弥补:信号量与自旋锁比照
信号量或许答应有多个持有者,而自旋锁在任何时候只能答应一个持有者。当然也有信号量叫互斥信号量(只能一个持有者),答应有多个持有者的信号量叫计数信号量。
信号量适合于坚持时刻较长的状况;而自旋锁适合于坚持时刻十分短的状况,在实践使用中自旋锁操控的代码只要几行,而持有自旋锁的时刻也一般不会超越两次上下文切换的时刻,由于线程一旦要进行切换,就至少花费切出切入两次,自旋锁的占用时刻假如远远善于两次上下文切换,咱们就应该挑选信号量。