[内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析
漫画|Linux 并发、竞态、互斥锁、自旋锁、信号量都是什么鬼?
Linux内核自旋锁
之前写的自旋锁的文章,现在再加一篇,可能单纯的一两次说明不能把问题说清楚。所以再写一篇文章,也希望更多的人参与讨论,这样会让问题更加清晰明了。
自旋锁的特点是在等待锁的过程中不会休眠,会不断的占用CPU轮询锁的状态,一旦发现锁被释放,就会马上获取锁。 基于这样的特点,自旋锁spinlock适用于保护执行时间非常短的临界区。
自旋锁有两个特点
进入临界区后不能调用可能引起系统休眠的函数。
临界区的代码不能被中断函数重入调用。
如果进入临界区后睡眠,会引起这样的问题,如下图
如果临界区的代码在执行的时候,中断重入调用,如下图
上面两种情况下,都出现一个问题,就是在临界区运行时,还没有来得及释放锁,当前进程被动释放了CPU的使用权,然后下次「可能是中断处理函数,可能是CPU调度的其他进程」再进来的时候,情况就会比较复杂,因为之前的程序一直没有释放,导致锁一直获取失败,失败后又一直在等待,而且永远等不到锁的释放,就会导致死锁了。
优先级反转问题
系统运行对时间要求非常严格,如果因为某些问题导致系统时间延迟有误差,可能会导致比较严重的问题,这种情况在实时系统中会更严重。
我描述下优先级反转的问题。
A和C共享一个资源,但是在运行过程中,在某一个时刻,C占有资源的时候,被高于它优先级的进程B抢占了,这时候B就处于一个有利位置,一直会有CPU运行,如果有其他进程优先级高于C的,也会能拿到CPU运行。
这就出现了一个奇怪的现象,低优先级的进程抢占了高优先级的进程,如果A是特斯拉的刹车进程的话,我相信故障就此发生。
如何解决优先级翻转的问题呢?
提升C的优先级,让C的优先级高于B,就不会存在持有锁的情况下被抢占。
但是C的优先级提升到多少合适呢?
假设共享资源R,有5个任务会申请它,我们需要做的是,持有R资源的任务的优先级是这5个任务中最高的,这就叫优先级提升。
spinlock相关代码,基于4.4内核
typedef struct {volatile unsigned int slock;
} arch_spinlock_t;#define __ARCH_SPIN_LOCK_UNLOCKED__ 0
#define __ARCH_SPIN_LOCK_LOCKED__ 1#define __ARCH_SPIN_LOCK_UNLOCKED { __ARCH_SPIN_LOCK_UNLOCKED__ }
#define __ARCH_SPIN_LOCK_LOCKED { __ARCH_SPIN_LOCK_LOCKED__ }
//...typedef struct raw_spinlock {arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAKunsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCKunsigned int magic, owner_cpu;void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOCstruct lockdep_map dep_map;
#endif
} raw_spinlock_t;//...typedef struct spinlock {union {struct raw_spinlock rlock;#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))struct {u8 __padding[LOCK_PADSIZE];struct lockdep_map dep_map;};
#endif};
} spinlock_t;
自旋锁函数调用
static __always_inline void spin_lock(spinlock_t *lock)
{raw_spin_lock(&lock->rlock);
}
//1==================
#define raw_spin_lock(lock) _raw_spin_lock(lock)
//2==================
#ifndef CONFIG_INLINE_SPIN_LOCK
void __lockfunc _raw_spin_lock(raw_spinlock_t *lock)
{__raw_spin_lock(lock);
}
EXPORT_SYMBOL(_raw_spin_lock);
#endif
//3==================
static inline void __raw_spin_lock(raw_spinlock_t *lock)
{preempt_disable();spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
//4==================LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
//5==================
#define LOCK_CONTENDED(_lock, try, lock) \
do { \if (!try(_lock)) { \lock_contended(&(_lock)->dep_map, _RET_IP_); \lock(_lock); \} \lock_acquired(&(_lock)->dep_map, _RET_IP_); \
} while (0)//6==================
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{__acquire(lock);arch_spin_lock(&lock->raw_lock);
}//7==================static inline void arch_spin_lock(arch_spinlock_t *lock)
{unsigned int val;SCOND_FAIL_RETRY_VAR_DEF;smp_mb();__asm__ __volatile__("0: mov %[delay], 1 \n""1: llock %[val], [%[slock]] \n" /*LOCK指令前缀会设置处理器的LOCK#信号(译注:这个信号会使总线锁定,阻止其他处理器接管总线访问内存),直到使用LOCK前缀的指令执行结束,这会使这条指令的执行变为原子操作。在多处理器环境下,设置LOCK#信号能保证某个处理器对共享内存的独占使用。*/" breq %[val], %[LOCKED], 0b \n" /* spin while LOCKED 判断变量是否为0,如果不为0,说明自旋锁已经被获取,当前获取就会失败 */" scond %[LOCKED], [%[slock]] \n" /* acquire */" bz 4f \n" /* done */" \n"SCOND_FAIL_RETRY_ASM: [val] "=&r" (val)SCOND_FAIL_RETRY_VARS: [slock] "r" (&(lock->slock)),[LOCKED] "r" (__ARCH_SPIN_LOCK_LOCKED__) /*获取锁,把变量值加1*/: "memory", "cc");smp_mb();
}
参考
https://blog.csdn.net/longwang155069/article/details/52055876
推荐阅读:
专辑|Linux文章汇总
专辑|程序人生
专辑|C语言
我的知识小密圈
关注公众号,后台回复「1024」获取学习资料网盘链接。
欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~