版权声明:转载请保留出处:blog.csdn.net/gentleliu。邮箱:shallnew*163.com】
只要并发的多个执行单元存在对共享资源的访问,竞态就可能发生。在 Linux 内核中,主要的竞态发生于如下几种情况
1. 多CPU共享资源,如存储器。解决竞态问题的途径是保证对共享资源的互斥访问,所谓互斥访问是指一个执行单元在访问共享资源的时候,其他的执行单元被禁止访问。访问共享资源的代码区域称为临界区(critical sections),临界区需要被以某种互斥机制加以保护。中断屏蔽、原子操作、自旋锁和信号量等是 Linux 设备驱动中可采用的互斥途径。
1.中断屏蔽(很少单独使用)local_irq_disable() /* 屏蔽中断 */
. . .
critical section /* 临界区*/
. . .
local_irq_enable() /* 开中断*/
由于 Linux 的异步 I/O、进程调度等很多重要操作都依赖于中断,中断对于内核的运行非常重要,在屏蔽中断期间所有的中断都无法得到处理,因此长时间屏蔽中断是很危险的,有可能造成数据丢失乃至系统崩溃等后果。这就要求在屏蔽了中断之后,当前的内核执行路径应当尽快地执行完临界区的代码。
void spin_lock(spinlock_t *lock);
void spin_lock_irq(spinlock_t *lock); //相当于spin_lock() + local_irq_disable()。
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);//禁止中断(只在本地处理器)在获得自旋锁之前; 之前的中断状态保存在 flags里。相当于spin_lock() + local_irq_save()。
void spin_lock_bh(spinlock_t *lock); //获取锁之前禁止软件中断, 但是硬件中断留作打开的,相当于spin_lock() + local_bh_disable()。
也有 4 个方法来释放一个自旋锁; 你用的那个必须对应你用来获取锁的函数.
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);
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 read_unlock(rwlock_t *lock);
void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void read_unlock_irq(rwlock_t *lock);
void read_unlock_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);
int write_trylock(rwlock_t *lock);
void write_unlock(rwlock_t *lock);
void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
void write_unlock_irq(rwlock_t *lock);
void write_unlock_bh(rwlock_t *lock);
struct semaphore sem;
void sema_init(struct semaphore *sem, int val);//初始化信号量,并设置信号量 sem 的值为 val。
#define init_MUTEX(sem) sema_init(sem, 1)//该宏用于初始化一个用于互斥的信号量,它把信号量 sem 的值设置为 1;
下面两个宏是定义并初始化信号量的“快捷方式”:
DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name);
前者定义一个名为 name 的信号量并初始化为1;后者定义一个名为 name 的信号量并初始化为0。
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值。它不会导致调用者睡眠,可以在中断上下文使用。
在使用 down_interruptible()获取信号量时,对返回值一般会进行检查,如果非 0,通常立即返回-ERESTARTSYS,如:
if (down_interruptible(&sem))
return - ERESTARTSYS;
void up(struct semaphore * sem);//该函数释放信号量 sem,唤醒等待者。
信号量一般这样被使用:
/* 定义信号量
DECLARE_MUTEX(mount_sem);
down(&mount_sem);/* 获取信号量,保护临界区
. . .
critical section /* 临界区
. . .
up(&mount_sem);/* 释放信号量
4.互斥体
struct mutex my_mutex;
mutex_init(&my_mutex);
下面的两个函数用于获取互斥体:
void inline _ _sched mutex_lock(struct mutex *lock);
int _ _sched mutex_lock_interruptible(struct mutex *lock);
int _ _sched mutex_trylock(struct mutex *lock);
mutex_lock()与 mutex_lock_interruptible()的区别和 down()与 down_trylock()的区别完全一致,前者引起的睡眠不能被信号打断,而后者可以。mutex_trylock()用于尝试获得 mutex,获取不到mutex 时不会引起进程睡眠。
void __sched mutex_unlock(struct mutex *lock);
mutex 的使用方法和信号量用于互斥的场合完全一样:
struct mutex my_mutex; /* 定义 mutex */
mutex_init(&my_mutex); /* 初始化 mutex */
mutex_lock(&my_mutex); /* 获取 mutex */
* 临界资源*/
mutex_unlock(&my_mutex); /* 释放 mutex */