再解析下内核自旋锁和优先级翻转问题

[内核同步]自旋锁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_LOCKBREAK
 unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
 unsigned int magic, owner_cpu;
 void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
 struct 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」获取学习资料网盘链接。

欢迎点赞,关注,转发,在看,您的每一次鼓励,我都将铭记于心~

相关推荐
©️2020 CSDN 皮肤主题: 代码科技 设计师:Amelia_0503 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值