单核与多核上锁的区别
参考:1、《Linux Kernel Development》3ed_CN p131-p140
2、2.6.34
单核:
//锁的数据类型实现
typedef struct { } arch_spinlock_t; typedef struct raw_spinlock { arch_spinlock_t raw_lock; }raw_spinlock_t; typedef struct spinlock { union { struct raw_spinlock rlock; }; //以我对C的了解,这种定义方式还是第一次见到(以前见到,也没留意过),这个union的联合体的最后竟然没有变量名称,即union {xxxxx} var_name; gcc后指定为c89标准也能正常编译与运行
//还有就是可以通过&lock->rlock,直接获得访问rlock的地址, 原来都是通过&lock->var_name.rlock来操作完成
//此处加个变量名,反而显得累赘,而且如果加上变量明就必须通过变量名访问。 }spinlock_t; //锁是空的
//spin_lock的实现
#define __acquire(x) (void)0 #define __LOCK(lock) \ do {preempt_disable(); __acquire(lock); (void)(lock); } while(0) #define _raw_spin_lock(lock) __LOCK(lock) #define raw_spin_lock(lock) _raw_spin_lock(lock) static inline void spin_lock(spinlock_t *lock) { raw_spin_lock(&lock->rlock); }
//单核是否支持抢占在锁上的区别:
#ifdef CONFIG_PREEMPT #define preempt_disable() \ do{ \ inc_preempt_count(); \ barrier(); \ } while(0) #else #define preempt_disable() do { } while(0)
多核:
//锁的数据类型实现
typedef struct { volatile unsigned int lock; } arch_spinlock_t; typedef struct raw_spinlock { arch_spinlock_t raw_lock; } raw_spinlock_t; typedef struct spinlock { union { struct raw_spinlock rlock; }; } spinlock_t;
//spin_lock的实现
static inline void arch_spin_lock(arch_spinlock_t *lock) { unsigned long tmp; __asm__ __volatile__( "1: ldrex %0, [%1] \n\t" " teq %0, #0 \n\t" " strexeq %0, %2, [%1] \n\t" " teqeq %0, #0 \n\t" " bne 1b " : "=&r" (tmp) : "r" (&lock->lock), "r"(1) : "cc" smp_mb(); } static inline void do_raw_spin_lock(raw_spinlock_t *lock) { arch_spin_lock(&lock->raw_lock); } static inline void __raw_spin_lock(raw_spinlock_t *lock) { preempt_disable(); do_raw_spin_lock(lock); } static inline void spin_lock(spinlock_t *lock) { raw_spin_lock(&lock->rlock); }
附:
1、同步,锁的问题,我认为发生在(1)进程与进程之间; (2)中断与进程之间; (3)中断与中断之间(细分上半部、下半部)
(1)如果进程上下文核一个下半部共享数据,在访问这些数据之前,需要禁止下半部的处理并得到锁的使用权。做这些是为了本地和SMP的保护并且防止死锁的出现。(p_128) (2)如果中断上下文和一个下半部共享数据,在访问数据之前,需要禁止禁止中断并得到锁的使用权。做这些是为了本地和SMP的保护并且防止死锁的出现。(p_128) (3)如若在一段内核代码操作某资源的时候系统产生了一个中断,而该中断的处理程序还要访问这一个资源,这就是一个bug。(p_135最后) 关于(3),经讨论使用出使用spin_lock_irqsave; 其它的也应有拌饭解决,以后会讲明。
2、记下书中提出的几条建议:
(1)最开始设计的时候就要考虑加入锁,而不是事后才想到。如果代码已经写好,再在其中找到需要上锁的部分并向其中追加锁,是非常困难的,结果也往往也不尽如人意。避免这种亡羊补牢的做法是:在编写代码的开始阶段就要设计恰当的锁。(p_136中间) (2)lock contention(锁的争用):是指当锁正在被占用时,有其它线程试图获得该锁。锁处于高度争用的状态是指有多个其他线程在等待获得该锁。(p_138最后) (3)被保护数据的规模描述了锁的粒度,细粒度的锁保护小块数据,过粗的锁保护大块数据。(p_139中间) 当锁争用严重时,加锁太粗会降低可扩展性;而锁争用不明显时,加锁过细会加大系统开销,带来浪费,这两种情况都会造成系统性能下降。(p_139最后)
还是谈下spin_unlock,不然总缺了什么。
单核上:
static inline void spin_unlock(spinlock_t *lock) { raw_spin_unlock(&lock->rlock); } #define raw_spin_unlock(lock) _raw_spin_unlock(lock) #define _raw_spin_unlock(lock) __UNLOCK(lock) #define __UNLOCK(lock) \ do { preempt_enable(); __release(lock); (void)(lock); } while (0) #ifdef CONFIG_PREEMPT #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(); \ barrier(); \ preempt_check_resched(); \ } while (0) #else #define preempt_enable() do { } while (0) #endif
多核:
static inline void spin_unlock(spinlock_t *lock) { raw_spin_unlock(&lock->rlock); } #define raw_spin_unlock(lock) _raw_spin_unlock(lock) #define _raw_spin_unlock(lock) __raw_spin_unlock(lock) static inline void dsb_sev(void) { __asm__ __volatile__ ( "dsb\n" "sev" ); } static inline void arch_spin_unlock(arch_spinlock_t *lock) { smp_mb(); __asm__ __volatile__( " str %1, [%0]\n" : : "r" (&lock->lock), "r" (0) : "cc"); dsb_sev(); } void do_raw_spin_unlock(raw_spinlock_t *lock) { // debug_spin_unlock(lock); arch_spin_unlock(&lock->raw_lock); } static inline void __raw_spin_unlock(raw_spinlock_t *lock) { // spin_release(&lock->dep_map, 1, _RET_IP_); do_raw_spin_unlock(lock); preempt_enable(); //与单核抢占情形相同 }