现在的计算机都是多核对称的cpu处理器,本文通过liunx内核2.6.0代码来分析在多核处理器下,如何使用自旋锁和抢占来进行高效的内核运转。 如果正在内核中运行着的任务此时可以抢占另外一个内核执行的任务,比如说有一个优先级很高的任务想去抢占内核中正在运行的任务,在linux2.6之前是没有实现的。 在2.6版本的内核中,加入了抢占相关的信息,在preempt.h头文件里,定义了一个preempt_count如果这个count大于零表示不可以被抢占,如果等于零,表示可以被抢占。
#define preempt_count() (current_thread_info()->preempt_count)//核心变量,每一个任务的线程信息里都有这个值
//对这个变量进行加减操作
#define inc_preempt_count() \
do { \
preempt_count()++; \
} while (0)
#define dec_preempt_count() \
do { \
preempt_count()--; \
} while (0)
#ifdef CONFIG_PREEMPT
//抢占动作,在别的文件中实现,所以是extern
extern void preempt_schedule(void);
//禁用就是让它加1
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \//这是一个编译器屏障
} while (0)
#define preempt_enable_no_resched() \
do { \
barrier(); \
dec_preempt_count(); \
} while (0)
#define preempt_check_resched() \
do { \
if (unlikely(test_thread_flag(TIF_NEED_RESCHED))) \
preempt_schedule(); \
} while (0)
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
preempt_check_resched(); \
} while (0)
#else
#define preempt_disable() do { } while (0)
#define preempt_enable_no_resched() do { } while (0)
#define preempt_enable() do { } while (0)
#define preempt_check_resched() do { } while (0)
#endif
上面的文件可以看出这个preempt变量的重要性。 随后在自旋锁的实战中,用到了这些变量,在linux/spinlock.h文件里可以看到如下的关键代码:
#if defined(CONFIG_SMP) && defined(CONFIG_PREEMPT)//如果在smp下
void __preempt_spin_lock(spinlock_t *lock);//增加的核心函数
void __preempt_write_lock(rwlock_t *lock);//增加的核心函数
#define spin_lock(lock) \
do { \
preempt_disable(); \//先禁止抢占
if (unlikely(!_raw_spin_trylock(lock))) \
__preempt_spin_lock(lock); \//如果尝试上锁失败,就进入这个核心方法
} while (0)
#define write_lock(lock) \
do { \
preempt_disable(); \//先禁止抢占
if (unlikely(!_raw_write_trylock(lock))) \
__preempt_write_lock(lock); \
} while (0)
#else//没有多对称处理器就不会有上面那两个核心方法
#define spin_lock(lock) \
do { \
preempt_disable(); \
_raw_spin_lock(lock); \
} while(0)
#define write_lock(lock) \
do { \
preempt_disable(); \
_raw_write_lock(lock); \
} while(0)
#endif
在kernal/sched.c文件中有这两个独特的函数实现:
void __preempt_spin_lock(spinlock_t *lock)
{
if (preempt_count() > 1) {//这里什么时候会大于一可以看看下面的解释
_raw_spin_lock(lock);//直接去自旋获得锁
return;
}
//如果小于等于1才会进来
do {
preempt_enable();//开启让别的任务可以抢占该任务,这里当然就是当前的这一个cpu里别的任务可以继续进来进行,而不会因为这个任务而卡死在这里
while (spin_is_locked(lock))
cpu_relax();//空循环去判断是否上锁,如果发现可以抢占锁了,那么就会立即禁止抢占,再去获得锁
preempt_disable();
} while (!_raw_spin_trylock(lock));
}
void __preempt_write_lock(rwlock_t *lock)
{
if (preempt_count() > 1) {
_raw_write_lock(lock);
return;
}
do {
preempt_enable();
while (rwlock_is_locked(lock))
cpu_relax();
preempt_disable();
} while (!_raw_write_trylock(lock));
}
什么时候会preempt_count() > 1呢,也就是同一个任务又获得了这把锁,那么就会立即去spin_lock,此时preempt_count就会大于1,因为spin_lock先会disable,而此时该任务在发现可以获得锁的时候,又disable了一次,此时不会让这个任务再去enable了,因为此时已经大于1了,再减一也不是0,本地cpu的其他任务还是抢占不到当前cpu让它工作,所以这也说明了禁止抢占两次。