为什么在中断中使用 mutex_trylock 不安全?
Why isn't mutex_trylock safe for use in interrupts?
Linux Robert Love 的内核开发声明:
A mutex cannot be acquired by an interrupt handler or bottom half, even with
mutex_trylock()
在http://landley.net/kdocs/htmldocs/kernel-locking.html,它提到
mutex_trylock() does not suspend your task but returns non-zero if it could lock the mutex on the first try or 0 if not. This function cannot be safely used in hardware or software interrupt contexts despite not sleeping.
不明白为什么在不休眠的情况下不能用?
想象一下,如果您有一个平台,其原生低级原始互斥锁没有 "try lock" 操作。在这种情况下,要实现这样的高级互斥锁,您必须使用条件变量和受低级互斥锁保护的布尔值 "is locked" 来指示高级互斥锁已锁定。
因此可以使用低级原始互斥锁(不支持 "trylock" 操作)实现可等待互斥锁来实现高级互斥锁(支持)。 "high-level mutex" 可以只是受低级互斥锁保护的布尔值。
根据该设计,mutex_lock
将实现如下:
- 获取低级互斥锁(这是对原语、实现互斥锁的真正锁操作)。
- 如果持有高级互斥锁,则执行条件等待高级互斥锁。
- 获取高级互斥锁(仅
locked = true;
)。
- 释放低级互斥量。
而mutex_unlock
的实现方式如下:
- 获取低级互斥量。
- 释放高级互斥量(仅
locked = false;
)
- 向条件变量发出信号。
- 释放低级互斥。
在这种情况下,mutex_trylock
将按如下方式实施:
- 获取低级互斥量。
- 检查是否持有高级互斥量。
- 如果是,则释放低级互斥锁并return失败。
- 获取高级互斥。
- 释放低级互斥量。
- Return成功。
想象一下,如果我们在第 2 步之后但在第 3 步之前被打断。
可能是因为在 mutex_trylock 中没有禁用中断。如果我们有这样一种情况,在中断上下文中使用 mutex_trylock 锁定了一个互斥锁,并且另一个中断试图获取相同的互斥锁。它可能会导致死锁的情况。
原因是我们要求互斥语义允许完整 Priority-Inheritance,即使默认实现不这样做(PREEMPT_RT 将互斥切换为 mutex_rt 然后它确实有 PI)。
假设您的中断 mutex_trylock() 成功,那么中断,或者更确切地说,被中断的任务,成为锁的所有者。任何竞争者 mutex_lock() 都会尝试 PI-boost 该所有者。有人可能会争辩说,由于中断是 non-preemptible,上下文实际上是 prio-ceiling,一切都很好。 但是,如果中断命中空闲任务,我们最终会尝试提升空闲任务,这是一个很大的no-no。
另请阅读此处的主题:
https://lkml.kernel.org/r/20191218135047.GS2844@hirez.programming.kicks-ass.net
Linux Robert Love 的内核开发声明:
A mutex cannot be acquired by an interrupt handler or bottom half, even with mutex_trylock()
在http://landley.net/kdocs/htmldocs/kernel-locking.html,它提到
mutex_trylock() does not suspend your task but returns non-zero if it could lock the mutex on the first try or 0 if not. This function cannot be safely used in hardware or software interrupt contexts despite not sleeping.
不明白为什么在不休眠的情况下不能用?
想象一下,如果您有一个平台,其原生低级原始互斥锁没有 "try lock" 操作。在这种情况下,要实现这样的高级互斥锁,您必须使用条件变量和受低级互斥锁保护的布尔值 "is locked" 来指示高级互斥锁已锁定。
因此可以使用低级原始互斥锁(不支持 "trylock" 操作)实现可等待互斥锁来实现高级互斥锁(支持)。 "high-level mutex" 可以只是受低级互斥锁保护的布尔值。
根据该设计,mutex_lock
将实现如下:
- 获取低级互斥锁(这是对原语、实现互斥锁的真正锁操作)。
- 如果持有高级互斥锁,则执行条件等待高级互斥锁。
- 获取高级互斥锁(仅
locked = true;
)。 - 释放低级互斥量。
而mutex_unlock
的实现方式如下:
- 获取低级互斥量。
- 释放高级互斥量(仅
locked = false;
) - 向条件变量发出信号。
- 释放低级互斥。
在这种情况下,mutex_trylock
将按如下方式实施:
- 获取低级互斥量。
- 检查是否持有高级互斥量。
- 如果是,则释放低级互斥锁并return失败。
- 获取高级互斥。
- 释放低级互斥量。
- Return成功。
想象一下,如果我们在第 2 步之后但在第 3 步之前被打断。
可能是因为在 mutex_trylock 中没有禁用中断。如果我们有这样一种情况,在中断上下文中使用 mutex_trylock 锁定了一个互斥锁,并且另一个中断试图获取相同的互斥锁。它可能会导致死锁的情况。
原因是我们要求互斥语义允许完整 Priority-Inheritance,即使默认实现不这样做(PREEMPT_RT 将互斥切换为 mutex_rt 然后它确实有 PI)。
假设您的中断 mutex_trylock() 成功,那么中断,或者更确切地说,被中断的任务,成为锁的所有者。任何竞争者 mutex_lock() 都会尝试 PI-boost 该所有者。有人可能会争辩说,由于中断是 non-preemptible,上下文实际上是 prio-ceiling,一切都很好。 但是,如果中断命中空闲任务,我们最终会尝试提升空闲任务,这是一个很大的no-no。
另请阅读此处的主题:
https://lkml.kernel.org/r/20191218135047.GS2844@hirez.programming.kicks-ass.net