您的位置:首页 > 运维架构 > Linux

linux设备驱动程序之并发和竞态(一)

2015-06-28 12:59 531 查看
此blog就是总结下驱动中的并发和竞争的使用方法,和linux环境编程之多线程同步类似;

信号量的实现

信号量的头文件在<asm/semaphore.h>,所以要使用信号量就必须包含这个头文件。
声明:struct semaphore sem;
初始化:
1、静态初始化:
DECLARE_MUTEX(&sem); //这个信号量初始化为1,可以马上使用;
DECLARE_MUTEX_LOCKED(&sem); //这个信号量初始化为0,如果要使用要先解锁,打开信号量;

2、动态初始化:
void init_MUTEX(&sem); //这个信号量初始化为1;
void init_MUTEX_LOCK(&sem);//这个信号量初始化为0;

但是在2.6.xx后的内核版本init_MUTEX()好像被废除了,用sema_init()函数替代了。sema_init(&sem, val);其中val是初始化的值。
记得在上《计算机操作系统》的时候,老师老是在讲P、V操作,一直以为还只是个概念,没想到还真有这函数;在linux中P操作函数就是down减少信号量的值,可能会让调用者进入休眠状态(如果信号量为0时,调用了P操作),然后等待别的进程、线程对信号量进行V操作,最后调用者才会醒来锁住资源;

void down(struct semaphore *sem);
int down_interruptible(struct semaphore *sem);
int down_trylock(struct semaphore *sem);
down()函数减少信号量值时,如果达到设定值则会一直睡眠等待,直到信号量可用这是不可以中断的。
down_inerruptible()函数和down()函数类似,但其睡眠是可以中断的。所以调用down_interruptible()函数要注意检查返回值,如果返回非0值,表示为被中断返回,此时没有拥有信号量,不能对资源进行操作;如果返回0值,则表示等到了信号量返回。而down()函数返回时,一定拥有了信号量。
down_trylock()函数不会休眠,如果信号量不可用则立即返回个非零值;

相对的有P操作就有V操作,V操作对应于up()函数,当释放一个信号量时,调用 vvoid up(sruct semaphore *sem);此后不再拥有信号量。

读写信号量

和多线程同步一样,互斥量虽然可以保护资源的,但是降低了并行性能,所以也就出了读写锁这机制。同样的在linux内核中这种读写锁相应的被称为读写信号量。
声明:struct rw_rw_semaphore rwsem;
初始化:void init_rwsem(struct rw_semaphore *sem);

只读函数:
void down_read(struct rw_semaphore *sem);
void down_read_trylock(struct rw_semaphore *sem);
void up_read(struct rw_semaphore *sem);

down_read()可能会让调用进程进入不可中断的睡眠。
down_read_trylock()函数不会睡眠,不管成功与否都会马上返回。成功返回非0,表示获得了互斥量;返回0表示失败。
所有读的互斥量都有up_read()函数来释放;

写入函数:
void down_write(struct rw_semaphore *sem);
int down_write_trylock(struct rw_semaphore *sem);
void up_write(struct rw_semaphore *sem);
void downgrade_write(struct rw_semaphore *sem);
其他函数都和只读函数功能类似,只有downgrade_write()函数例外。如果一个操作只有前面要修改一下,而后面是比较长时间的只读,那么可以先上写锁,当把需要修改的部分修改完后可以调用downgrade_write()函数,来允许其他只读线程来访问;

completion

completion是一种轻量级的机制,它允许一个线程告诉另外一个线程某个工作已经完成了。
头文件 <linux/completion.h>

静态创建completion:DECLARE_COMPLETION(my_completion);
动态创建completion:struct completion my_completion;
init_completion(&my_completion);

等待completion:void wait_for_completion(struct completion *c); //该函数执行一个不可中断的等待,如果没有人去完成这个任务,则将产生一个不可杀死的进程。
触发completion函数:
void complete(struct completion *c); // 只会被使用一次然后被丢弃
void complete_all(struct completion *c); // 可以多次使用,但使用前必须初始化它。可以使用下面的宏来初始化:INIT_COMPLETION(struct completion c);

自旋锁

自旋锁基础

和互斥量类似,自旋锁也可以保护资源,在驱动中使用自旋锁的概率会高点。如果一个代码段是不能休眠的,则只能使用自旋锁,比如中断处理例程;同样的,如果一个代码段是可以休眠的,那么使用互斥量来保护资源。
自旋锁实现就是测试某个整数值中的单个位,并设置它。如果测试可用,则设置该位进入临界区;如果不可用,则循环等待(忙等待),直到该锁可以用,这就是自旋部分(当然这些操作都是原子操作)。这里的循环忙等待要和互斥量中的休眠等待区分开来,循环忙等待是占用CPU的,一直执行检查锁的操作;而休眠是释放掉了CPU的,当条件满足时,会被唤醒的。
头文件 #include<linux/spinlock.h>
定义:spinlock_t my_lock;
静态初始化:在编译时完成
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;

动态初始化:在运行时调用
void spin_lock_init(spinlock_t *my_lock);

获取自旋锁:在进入临界区之前,必须调用下面的函数来得到锁
void spin_lock(spinlock_t *my_lock);
自旋锁等待在本质上都是不可以中断的,一旦调用了spin_lock,在获取到锁之前将一直处于自选状态;

释放自旋锁:void spin_unlock(spinlock_t *my_lock);

自旋锁规则

1、拥有自旋锁代码不能睡眠;
如果驱动程序获取了自旋锁,进入临界区运行,突然该程序丢掉了对处理器的控制权。可能是调用了某些函数进入休眠,或者是被高优先级的进程抢占了。而自旋锁已经被驱动程序获取到了,并且一时间无法释放该自旋锁。如果其他进程想访问该临界区,那么得自旋等待锁的释放。很显然好的情况下只需要等待一会(睡眠结束,驱动程序退出临界区),最坏的情况就是死锁(高优先级进程抢占了驱动程序对CPU的控制权,如果高优先级进程需要访问被锁住的临界区,则会死锁)。
因此,任何拥有自旋锁的代码都必须是原子的,不能休眠,也不能因为任何原因放弃处理器(中断除外);
自旋锁实现代码本身就会禁止相关处理器上的抢占,在加自旋锁时,也就禁止了处理器抢占;
在拥有自旋锁的代码中不能调用会睡眠的函数,copy_from_user(拷贝内容所在的页在磁盘上,则要睡眠等待把该页交换到内存中) 和 kmalloc(无空闲页,则会放弃处理器)函数都可能会进入睡眠。

2、拥有自旋锁代码禁止中断;
如果一个驱动程序已经获取了一个自旋锁,该锁控制着对设备的访问,在拥有锁的期间发生设备中断,导致中断处理程序被调用。而中断处理程序必须先获取锁才能访问设备,这样驱动程序不能释放锁,而中断例程又在自旋等待锁的释放。为了防止这种情况,所以在 拥有 自旋锁时禁止中断。

3、拥有自旋锁代码必须短小 ;
自旋锁必须在可能的最短时间内拥有,拥有自旋锁的时间越长,其他处理器自旋等待锁释放的时间就越长,这会阻止处理器的调度,高优先级进程的也不得不等待。这样会降低系统的性能;

自旋锁函数

void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinock_t *lock);
spin_lock()函数是基本的函数;
spin_lock_irqsave()函数是在获取自旋锁之前禁止中断,只在本地处理器上作用,把先前的中断状态保存在flags中;
spin_lock_irq()函数是在确定没有其他代码禁止中断,则可以不用保存中断状态;
spin_lock_bh()函数在获取锁之前禁止软件中断;

非阻塞自旋锁操作
int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
成功时,返回非零值;失败返回零值;

读写者自旋锁

头文件<linux/spinlock.h>
rwlock_t my_rwlock = RW_LOCK_UNLOCKED;
rwlock_t my_rwlock;
rwlock_init(&my_rwlock);

读者
void read_lock(rwlock_t *lock);

void read_lock_irqsave(rwlock_t *lock, unsigned long flags);

void read_lock_irq(rwlock_t *lock);

void read_lock_bh(rwlock_t *lock);

写者
void write_lock(rwlock_t *lock);

void write_lock_irqsave(rwlock_t *lock, unsigned long flags);

void write_lock_irq(rwlock_t *lock);

void write_lock_bh(rwlock_t *lock);

void write_trylock(rwlock_t *lock);

转载地址:/article/1530576.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: