【问题标题】:Is a spinlock necessary in this Linux device driver code?这个 Linux 设备驱动程序代码中是否需要自旋锁?
【发布时间】:2015-03-04 19:14:48
【问题描述】:

以下 Linux 设备驱动程序代码是否安全,或者我是否需要使用自旋锁保护对 interrupt_flag 的访问?

static DECLARE_WAIT_QUEUE_HEAD(wq_head);
static int interrupt_flag = 0;

static ssize_t my_write(struct file* filp, const char* __user buffer, size_t length, loff_t* offset)
{
   interrupt_flag = 0;   
   wait_event_interruptible(wq_head, interrupt_flag != 0);
}

static irqreturn_t handler(int irq, void* dev_id)
{
   interrupt_flag = 1;
   wake_up_interruptible(&wq_head);
   return IRQ_HANDLED;
}

基本上,我会在my_write() 中启动一些事件并等待中断指示它完成。

如果是这样,我需要使用哪种形式的spin_lock()?我认为spin_lock_irq() 是合适的,但是当我尝试这样做时,我收到了有关 IRQ 处理程序启用中断的警告。

wait_event_interruptible 不评估interrupt_flag != 0 条件吗?这意味着在读取标志时应该持有锁,对吧?

【问题讨论】:

  • 试试。 spin_lock_irqsave()
  • 它几乎肯定需要保护。但是,如果没有更多信息,则无法确定自旋锁或不同的锁定机制是否最合适。

标签: linux-kernel linux-device-driver embedded-linux


【解决方案1】:

在给出的示例中不需要锁。在存储标志之后和加载之前需要内存屏障——以确保标志的可见性——但 wait_event_* 和 wake_up_* 函数提供了这些。请参阅本文档中标题为“睡眠和唤醒功能”的部分:https://www.kernel.org/doc/Documentation/memory-barriers.txt

在添加锁之前,请考虑受保护的内容。如果您要设置两个或更多单独的数据,并且您需要确保另一个 cpu/核心不会看到不完整的中间状态(在您开始之后但在您完成之前),通常需要锁定。在这种情况下,保护标志值的存储/加载毫无意义,因为正确对齐的整数的存储和加载始终是原子的。

因此,根据您的驱动程序正在执行的其他操作,您很可能确实需要一个锁,但您提供的 sn-p 不需要它。

【讨论】:

  • 提到“正确对齐的整数的存储和加载始终是原子的”部分,并且没有 wait_*wake_up_* 函数,这是否意味着 no 在进程上下文和内核上下文之间共享int 时需要锁定吗? SMP 呢?
  • 没错。如果您只共享一个 int,则不需要锁定。这不会随着 SMP 而改变。假设 var 为 0。假设 core0 在某个时候将 var 设置为 1。假设 core1 正在查看 var。在任何时间点,core1 都将 var 视为其中包含 0(更改之前)或 1(更改之后)。在设置它(或检查它)周围抓住一个锁并不会改变这一点。 core1 仍然看到 0 或 1。锁定没有完成任何事情。
  • 我同意这对大多数拱门都是正确的。我浏览了atomic_t(这只是int 的包装)和atomic_set()。在几乎所有的拱门上,atomic_set() 是一个直接将内部int 设置为所需值的宏。除了在 sparc、parisc 和 metag 上,此操作受锁保护,有时还使用 irq 禁用(参见 arch/sparc/lib/atomic32.c 示例)。我不确定这种保护的原因,但我想这使得你的答案对除了这三个之外的所有拱门都是正确的。还是我错过了什么?谢谢。
  • 这样做是为了在没有在硬件中实现它的架构上与其他“原子”操作(如 atomic_xchg)配合得很好。例如,如果您想将一个值设置为 1,但前提是它当前不为零(比如说)并且您没有像 Intel 那样的原子 XCHG 指令(或像 LL/SC 这样的其他等效机制),自旋锁是实现这一点的一种方法,但是您必须确保 atomic_set 也获得锁定。有关示例,请参见 metag 拱门中 atomic_set 处的注释。这是一个不能出现在中间状态的相关操作序列。
【解决方案2】:

是的,您需要一把锁。对于给定的示例(使用int 并且没有提到特定的拱门),进程上下文可能会在访问interrupt_flag 时被中断。从 IRQ 返回后,它可能会继续,interrupt_flag 可能会处于不一致的状态。

试试这个:

static DECLARE_WAIT_QUEUE_HEAD(wq_head);
static int interrupt_flag = 0;
DEFINE_SPINLOCK(lock);

static ssize_t my_write(struct file* filp, const char* __user buffer, size_t length, loff_t* offset)
{
   /* spin_lock_irq() or spin_lock_irqsave() is OK here */
   spin_lock_irq(&lock);
   interrupt_flag = 0;
   spin_unlock_irq(&lock);

   wait_event_interruptible(wq_head, interrupt_flag != 0);
}

static irqreturn_t handler(int irq, void* dev_id)
{
   unsigned long flags;

   spin_lock_irqsave(&lock, flags);
   interrupt_flag = 1;
   spin_unlock_irqrestore(&lock, flags);

   wake_up_interruptible(&wq_head);
   return IRQ_HANDLED;
}

恕我直言,必须在不做任何拱门或编译器相关假设的情况下编写代码(如 Gil Hamilton 答案中的“正确对齐的整数”)。

现在,如果我们可以更改代码并使用atomic_t 而不是int 标志,那么就不需要锁了。

【讨论】:

  • 为什么?锁可以防止什么种族?
  • 不存在运行 Linux 内核的体系结构,其中在文件范围内声明 int 变量会导致未对齐的变量,并且不存在对对齐的 int 的访问不是原子的体系结构。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-09
  • 2015-09-27
  • 2015-10-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多