解决的问题:
进程A和进程B同时对 XXX(共享资源)进行操作,会产生竞争行为
解决方法:
保证对共享资源的互斥访问(一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问)
访问共享资源的代码区块叫做“临界区”,临界区需要以某种互斥机制加以保护:自旋锁、信号量(进程)等
linux内核中可能引起互斥访问的情形:
1.进程和进程之间 解决方式:信号量
2.进程和其它内核代码 解决方式:自旋锁
3.进程和中断代码 解决方式:自旋锁
信号量:
Linux中的信号量是一种睡眠锁。假如进程A先持有信号量F,然后进程B试图获得已经被进程A持有的信号量F时,信号量会将进程B推入等待队列,然后让其睡眠。当持有信号量的进程A将信号量F释放后,进程B才会被被唤醒,从而获得这个信号量,继续执行进程B的代码
信号量基本使用形式为:
定义信号量
struct semaphore sem;
初始化信号量
void sema_init(struct semaphore * sem, int val);
初始化互斥信号量
void init_MUTEX(struct semaphore *sem);
等同于:sema_init(struct semaphore *sem, 1);
获得信号量
void down(struct semaphore *sem);
此函数用于获得信号量sem,它会导致睡眠,因而不能用于中断上下文
int down_interruptible(struct semaphore *sem);
同down(),不同之处在于,因为down()而进入睡眠状态的进程不能被信
号打断,而因为down_interruptible()而进入睡眠状态的进程能被信号
打断,信号也会导致该函数返回,这时该函数返回值非0
int down_trylock(struct semaphore *sem);
该函数尝试获得信号量sem,如果能够立即获得,它就获得该信号量返回
0,否则,返回非0值,它不会导致调用者睡眠,可以在中断上下文使用
释放信号量
void up(struct semaphore *sem)
该函数释放信号量sem,唤醒等待者
信号量使用注意:
1.可以长期加锁
2.只能用于进程上下文,不能用于中断上下文
3.在持有自旋锁的同时,不能持有信号量
自旋锁:
自旋锁最多只能被一个内核任务持有。如果一个内核任务试图请求一个已经被别的内核任务持有的自旋锁,那么CPU就会一直进行忙循环等待锁重新可用。
在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机制,但是现在中断上下文也参和进来,那些可以导致睡眠的lock就不能使用了,这时候,可以考虑使用spin lock。因为中断上下文不允许睡眠。
自旋锁使用方法:
spin_lock(&mr_lock);//加锁
。。。。。//临界区代码
spin_unlock(&mr_lock);//解锁
初始化自旋锁:
spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
或者在运行时使用:
void spin_lock_init(spinlock_t *lock);
得到自旋锁:
void spin_lock(spinlock_t *lock);
释放自旋锁
void spin_unlock(spinlock_t *lock);
得到锁的代码在执行临界区的时候还可能受到本地中断的影响,为了防止这种影响,需要用到自旋锁的衍生函数
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
获得自选锁之前禁止本地cpu中断,之前的中断状态保存在flags里
void spin_lock_irq(spinlock_t *lock);
获得自选锁之前禁止本地cpu中断,但不保存中断状态
void spin_lock_bh(spinlock_t *lock);
获得自选锁之前禁止软中断,允许硬件中断
相应的释放函数
void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
自旋锁使用注意:
1.不可以长期加锁,短期加锁
2.会有系统开销(cpu忙转),不可滥用
3.在持有自旋锁的同时,不能持有信号量
4.在持有自旋锁的同时,不能再二次持有它(死锁)