BKL(Big Kernel Lock)
BKL即全局内核锁,也称大内核锁,它是一个全局自旋锁。大内核锁也是用来保护临界区资源的,避免出现多个处理器上的进程同时访问同一区域,整个内核中只有一个大内核锁。
BKL是一个名为kernel_flag的自旋锁,持有该锁的进程仍可以睡眠,当睡眠时持有的锁将被自动释放,该进程被唤醒时重新持有该锁。Linux允许一个进程可以递归的持有BKL,BKL是一个递归锁。
它的设计思想是,一旦某个内核路径获取了这把锁,那么其他所有的内核路径都不能再获取到这把锁。自旋锁加锁的对象一般是一个全局变量,大内核锁加锁的对象是一段代码,里面可能包含多个全局变量。那么他带来的问题是,虽然A只需要互斥访问全局变量a,但附带锁了全局变量b,从而导致B不能访问b了
mutex(互斥锁)
/linux/include/linux/mutex.h
47struct mutex {
48
49 atomic_t count;
50 spinlock_t wait_lock;
51 struct list_head wait_list;
52#ifdef CONFIG_DEBUG_MUTEXES
53 struct thread_info *owner;
54 const char *name;
55 void *magic;
56#endif
57#ifdef CONFIG_DEBUG_LOCK_ALLOC
58 struct lockdep_map dep_map;
59#endif
60};
一、作用及访问规则:
互斥锁主要用于实现内核中的互斥访问功能。内核互斥锁是在原子API之上实现的,但这对于内核用户是不可见的。对它的访问必须遵循一些规则:同一时间只能有一个任务持有互斥锁,而且只有这个任务可以对互斥锁进行解锁。互斥锁不能进行递归锁定或解锁。一个互斥锁对象必须通过其API初始化,而不能使用memset或复制初始化。一个任务在持有互斥锁的时候是不能结束的。互斥锁所使用的内存区域是不能被释放的。使用中的互斥锁是不能被重新初始化的。并且互斥锁不能用于中断上下文。但是互斥锁比当前的内核信号量选项更快,并且更加紧凑,因此如果它们满足您的需求,那么它们将是您明智的选择。
二、各字段详解:
1、atomic_t count; --指示互斥锁的状态:1没有上锁,可以获得;0被锁定,不能获得;负数被锁定,且可能在该锁上有等待进程初始化为没有上锁。
2、spinlock_t wait_lock;--等待获取互斥锁中使用的自旋锁。在获取互斥锁的过程中,操作会在自旋锁的保护中进行。初始化为为锁定。
3、struct list_head wait_list;--等待互斥锁的进程队列。
四、操作:
1、定义并初始化:
struct mutex mutex;
mutex_init(&mutex);
79# define mutex_init(mutex) \
80do { \
81 static struct lock_class_key __key; \
82 \
83 __mutex_init((mutex), #mutex, &__key); \
84} while (0)
42void
43__mutex_init(struct mutex *lock, const char *name, structlock_class_key *key)
44{
45 atomic_set(&lock-》count, 1);
46 spin_lock_init(&lock-》wait_lock);
47 INIT_LIST_HEAD(&lock-》wait_list);
48
49 debug_mutex_init(lock, name, key);
50}
直接定于互斥锁mutex并初始化为未锁定,己count为1,wait_lock为未上锁,等待队列wait_list为空。
2、获取互斥锁:
(1)具体参见linux/kernel/mutex.c
void inline __sched mutex_lock(struct mutex *lock)
{
might_sleep();
__mutex_fastpath_lock(&lock-》count,__mutex_lock_slowpath);
}
获取互斥锁。实际上是先给count做自减操作,然后使用本身的自旋锁进入临界区操作。首先取得count的值,再将count置为-1,判断如果原来count的值为1,也即互斥锁可以获得,则直接获取,跳出。否则进入循环反复测试互斥锁的状态。在循环中,也是先取得互斥锁原来的状态,再将其置为-1,判断如果可以获取(等于1),则退出循环,否则设置当前进程的状态为不可中断状态,解锁自身的自旋锁,进入睡眠状态,待被在调度唤醒时,再获得自身的自旋锁,进入新一次的查询其自身状态(该互斥锁的状态)的循环。
(2)具体参见linux/kernel/mutex.c
int __sched mutex_lock_interruptible(struct mutex *lock)
{
might_sleep();
return __mutex_fastpath_lock_retval(&lock-》count,__mutex_lock_interruptible_slowpath);
}
和mutex_lock()一样,也是获取互斥锁。在获得了互斥锁或进入睡眠直到获得互斥锁之后会返回0。如果在等待获取锁的时候进入睡眠状态收到一个信号(被信号打断睡眠),则返回_EINIR。
(3)具体参见linux/kernel/mutex.c
int __sched mutex_trylock(struct mutex *lock)
{
return __mutex_fastpath_trylock(&lock-》count,
__mutex_trylock_slowpath);
}
试图获取互斥锁,如果成功获取则返回1,否则返回0,不等待。
3、释放互斥锁:
具体参见linux/kernel/mutex.c
void __sched mutex_unlock(struct mutex *lock)
{
__mutex_fastpath_unlock(&lock-》count,__mutex_unlock_slowpath);
}
释放被当前进程获取的互斥锁。该函数不能用在中断上下文中,而且不允许去释放一个没有上锁的互斥锁。
4.void mutex_destroy(struct mutex *lock) --清除互斥锁,使互斥锁不可用
用mutex_destroy()函数解除由lock指向的互斥锁的任何状态。在调用执行这个函数的时候,lock指向的互斥锁不能在被锁状态。储存互斥锁的内存不被释放。
返回值--mutex_destroy()在成功执行后返回零。其他值意味着错误。在以下情况发生时,函数失败并返回相关值。
EINVAL 非法参数
EFAULT mp指向一个非法地址。
5.static inline int mutex_is_locked(struct mutex *lock)--测试互斥锁的状态
这个调用实际上编译成一个内联函数。如果互斥锁被持有(锁定),那么就会返回1;否则,返回0。
五、使用形式:
struct mutex mutex;
mutex_init(&mutex);
。。。
mutex_lock(&mutex);
。。。
mutex_unlock(&mutex);
顺序锁
顺序锁为写者赋予更高的优先级,写者永远不会等待读者。缺点是读者有时不得不读多次数据以获取正确的结果。
顺序锁的数据结构中除了有spinlock外,还有一个顺序号。如果成功获得锁,顺序锁的顺序号会加1,以便读者能够检查出是否在读期间有写者访问过。读者在读取数据前后两次读顺序值,如果两次值不相同,则说明读取期间有新的写者操作过数据了,那么本次读取就是无效的。
典型使用:
读端:
do {
seqnum = read_seqbegin(&seqlock_a);
//读操作代码块
。。.
} while (read_seqretry(&seqlock_a, seqnum));
写端:
spin_lock(&lock);
write_seqlock(&seqlock_a)
。。.
write_sequnlock(&seqlock_a)
spin_unlock(&lock);
写者通过调用write_seqlock()和write_sequnlock()获取和释放顺序锁。write_seqlock()函数获取seqlock_t数据结构中的自旋锁,然后使顺序计数器sequence加1;write_sequnlock()函数再次增加顺序计数器sequence,然后释放自旋锁。这样可以保证写者在整个写的过程中,计数器sequence的值是奇数,并且当没有写者在改变数据的时候,计数器的值是偶数。
read_seqbegin()返回顺序锁的当前顺序号;如果局部变量seq的值是奇数(写者在read_seqbegin()函数被调用后,正更新数据结构),或seq的值与顺序锁的顺序计数器的当前值不匹配(当读者正执行临界区代码时,写者开始工作),read_seqretry()就返回1,说明本次读取失败,需要重新读取 。
并不是每一种资源都可以使用顺序锁来保护。一般来说,必须在满足下述条件时才能使用顺序锁:
1. 读者的临界区资源不包括被写者修改和被读者取值的指针,否则,写者有可能使指针失效,读者读取时会产生OPPs。
2. 读者的临界区代码没有副作用。
何时使用顺序锁?
读操作远多于写操作、且写操作很紧急时使用顺序锁。
评论
查看更多