【问题标题】:Is returning while holding a spinlock automatically unsafe?拿着自旋锁自动返回不安全吗?
【发布时间】:2015-12-14 21:32:21
【问题描述】:

受人尊敬的书 Linux Driver Development 这么说

传递给spin_unlock_irqrestoreflags 参数必须与传递给spin_lock_irqsave 的变量相同。 您还必须在同一函数中调用spin_lock_irqsavespin_unlock_irqrestore;否则您的代码可能会在某些架构上中断。

但我找不到the official documentation bundled with the kernel code itself 要求的任何此类限制。我找到了driver code that violates this guidance

显然,从单独的函数调用spin_lock_irqsavespin_unlock_irqrestore 不是一个好主意,因为您应该在持有锁的情况下最大限度地减少完成的工作(禁用中断,不少!)。但是,如果谨慎完成,内核的更改是否可以实现,它实际上从未违反 API 合同,还是仍然禁止这样做?

如果限制在某个时候被删除,它是否适用于版本 3.10.17?

【问题讨论】:

    标签: linux-kernel linux-device-driver interrupt-handling spinlock


    【解决方案1】:

    这只是一个猜测,但如果您尝试对 flags 使用 nonlocal 变量或存储位置,可能会不清楚地指代可能发生的潜在错误。

    基本上,flags 必须对当前执行上下文是私有的,这就是为什么spin_lock_irqsave 是一个采用flags 名称的宏。在保存 flags 时,您还没有自旋锁。

    这与不同功能中的锁定和解锁有何关系:

    考虑一些驱动程序开发人员可能编写的两个函数:

    void my_lock(my_object *ctx)
    {
      spin_lock_irqsave(&ctx->mylock, ctx->myflags);  /* BUG */
    }
    
    void my_unlock(my_object *ctx)
    {
      spin_unlock_irqrestore(&ctx->mylock, ctx->myflags);
    }
    

    这是一个错误,因为在写入ctx->myflags 时,尚未持有锁,它是其他上下文和处理器可见的共享变量。本地标志必须保存到堆栈上的私有位置。然后,当调用者拥有锁时,可以将标志的副本保存到独占拥有的对象中。换句话说,可以这样修复:

    void my_lock(my_object *ctx)
    {
      unsigned long flags;
      spin_lock_irqsave(&ctx->mylock, flag);
      ctx->myflags = flags;
    }
    
    void my_unlock(my_object *ctx)
    {
      unsigned long flags = ctx->myflags; /* probably unnecessary */
      spin_unlock_irqrestore(&ctx->mylock, flags); 
    }
    

    如果不能这样修复,实现需要包装 IRQ 自旋锁的高级原语将非常困难。

    它如何依赖于架构:

    假设spin_lock_irqsave 扩展为将当前标志保存在某个寄存器中的机器代码,然后获取锁,然后然后将该寄存器保存到指定的flags目的地。在这种情况下,错误代码实际上是安全的。如果扩展代码将标志保存到调用者指定的实际flags 对象中,然后尝试获取锁,那么它就坏了。

    【讨论】:

    • 好点。实际上,在2.4.37 kernel 中,标志是在 获取锁之前存储的,但在2.6.32(以及之后)中,情况有所不同。看到2.6.20 会很有趣,因为这本书描述了内核发展的这一时期。
    • 这是对另一个问题的一个很好的回答......不过,我在文档中也找不到任何禁止这样做的内容,所以也许 2.6.32 之前的行为被认为违反了 API合同?在任何情况下,链接代码都会完全按照您的指示做错(或者至少是一个坏主意)
    • @BenVoigt,实际上这个答案倾向于解释您引用的书中的引用:在使用 shared flags 变量时应该非常小心。并且使用与 local flags 变量相同的函数上下文很容易消除这个困难。您作为 shared 标志变量的示例引用的代码实际上看起来很可疑:根据其another line,它预计将在2.6.26 之前在内核上运行。
    • @Kaz:标记为/* probably unnecessary */ 的行是实际需要的:因为spin_unlock_irqrestore,编译器不会强制评估其宏扩展之前的参数。因此,如果没有局部变量,很有可能ctx->myflags 将在 解锁锁定后被评估,这很糟糕。
    • 确实,这可能是一个不再真实的历史问题。我实际上在我正在研究的内核驱动程序中提交了这个错误。我在度假时意识到了这个错误,完全远离代码。我在一个遥远的城市漫步在海边,突然意识到“我用旗帜做的事情是错误的”。 那是在 1998 年
    【解决方案2】:

    除了这本书,我从未见过这种约束。可能是因为书中的信息已经过时了,或者.. 完全是错误的。

    在当前内核中(至少从我开始使用的 2.6.32 开始)实际锁定是通过来自spin_lock_irqsave 的多层嵌套调用完成的(参见,例如__raw_spin_lock_irqsave,在中间)。因此,lockunlock 的不同功能上下文可能很难成为功能错误的原因。

    【讨论】:

    • 我认为约束是关于spin_lock_irqsave 的调用者,而不是内部调用。例如,如果实现(它被记录为一个宏——它必须是为了在不使用指针的情况下覆盖第二个参数)声明了额外的局部变量,这些局部变量将在从调用函数返回期间丢失。目前尚不清楚架构是否可以使用如此丑陋的宏实现——书表明他们可以。
    • 但是当前的高级自旋锁 API 是独立于体系结构的,通过嵌套(内联)函数调用或通过 do {...} while(0) 形式的宏调用依赖于体系结构的函数。所以局部变量无论如何都会丢失。
    • 啊,是的,如果架构不允许替换外部宏,那么您的分析似乎是正确的。
    猜你喜欢
    • 2011-06-12
    • 2011-04-10
    • 1970-01-01
    • 2021-12-14
    • 1970-01-01
    • 1970-01-01
    • 2013-09-27
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多