printk中断禁用和锁定

时间:2014-09-16 16:16:51

标签: linux linux-kernel scheduler

我对3.10内核中printk()的实现有疑问。我在开始时看到它calls local_irq_save。然后我看到它calls raw_spin_lock(&logbuf_lock)。如果在此之前已经禁用了中断,那么logbuf_lock的目的是什么?是因为即使在当前CPU上禁用了中断,其他CPU仍然可以调用printk,因此需要停止写入日志缓冲区吗?

基本上我有三个问题:

  1. 我看到printk获取了logbuf_lock并写入日志缓冲区,然后尝试获取控制台信号量并释放logbuf_lock。然后在循环内的console_unlock内部获取logbuf_lock并禁用中断,然后释放logbuf_lock并调用控制台驱动程序,然后恢复中断。这种锁定/禁用中断序列的目的是什么?

  2. 我在printk()中看到有关日志缓冲区可能再次填满的注释,因此缓冲区可能必须再次刷新到控制台。考虑到我在上面#1中提到的所有锁定,这种情况会怎样?

  3. 如果在任何给定时刻只有1个CPU上的代码要调用printk(),那么SMP系统中的其他内核是否仍然可以处理中断?我也试图了解printk对中断延迟的影响。

  4. 感谢。

    一些后续行动:

    你能否澄清一下:

      

    local_irq_save()可防止本地CPU上的中断(并且还避免在使用cpu变量访问每个CPU数据时在另一个CPU上重新安排)

    你的意思是调用local_irq_save()只会在访问每个CPU数据时阻止当前线程在另一个CPU上重新调度,或者它是否阻止当前线程在另一个CPU周期重新调度?在这里printk()的情况下local_irq_save()的目的是什么?我记得在LMKL上读过一个线程,说明中断的禁用是为了确保日志缓冲区中的条目顺序反映了printk()调用的实际顺序。

1 个答案:

答案 0 :(得分:6)

  

如果在此之前已经禁用了中断,那么logbuf_lock的目的是什么?是因为即使在当前CPU上禁用了中断,其他CPU仍然可以调用printk,因此需要停止写入日志缓冲区吗?

是。 local_irq_save()可以防止本地CPU上的中断(并且还可以避免在使用cpu变量访问每个CPU数据时在另一个CPU上重新安排),而自旋锁可以防止其他CPU。

  

如果在任何给定时刻只有1个CPU上的代码要调用printk(),那么中断是否仍然可以在SMP系统的其他内核上处理?

  

我看到printk获取了logbuf_lock并写入日志缓冲区,然后尝试获取控制台信号量并释放logbuf_lock。然后在循环内部的console_unlock中获取logbuf_lock并禁用中断,然后释放logbuf_lock并调用控制台驱动程序,然后恢复中断。这个锁定/禁用中断序列的目的是什么?

要保护两件事:日志缓冲区和控制台驱动程序。 logbuf_lock保护日志缓冲区,console_sem保护对控制台驱动程序列表和实际控制台本身的访问。

打印内核消息是一个两步过程。首先将消息放入日志缓冲区,然后将日志缓冲区发送到console_unlock()中的控制台。这两个步骤不需要在printk()的同一次调用中发生。此外,在注册/启动/恢复/ ...控制台时,可能会将日志缓冲区刷新到控制台。

将消息放入日志缓冲区后,printk()尝试获取console_sem。它甚至可以在中断上下文中执行此操作,因为down_trylock()不会睡眠。如果它获取了信号量,它可以继续将日志缓冲区内容发送到控制台。如果它没有获取信号量,则控制台信号量持有者的责任是将日志缓冲区内容发送到console_unlock()上的控制台。                                                                                                                             当console_unlock()发送到控制台时,可能有其他CPU调用printk()。所以console_unlock()循环,直到没有更多内容发送到控制台。在每个循环中,它获取指向日志缓冲区部分的指针,以发送到logbuf_lock下的控制台,然后,不再在logbuf_lock下,将输出发送到控制台。在向控制台发送内容时,由于未采用logbuf_lock,因此其他CPU可以继续向日志缓冲区添加内容。

  

我在printk()中看到有关日志缓冲区可能再次填满的注释,因此缓冲区可能必须再次刷新到控制台。考虑到我在上面#1中提到的所有锁定,这种情况会怎样?

在释放logbuf_lock之后up() console_sem之前,缓冲区可能已被填满。并且logbuf_lockup() console_sem之前被释放,因为up()可能导致唤醒,需要采取runqueue锁定,这可能会导致针对printk()的优先级反转问题使用runqueue lock(commit 0b5e1c5255)调用。

有一些建议的补丁来改变这种锁定方案:Jan Kara [PATCH 0/8 v4] printk: Cleanups and softlockup avoidance,其中包括试图避免在console_unlock()上无限期地循环CPU(它发生在记录大量启动事件的大系统上)通过慢速串行控制台),将该工作移交给其他CPU;并尝试最小化printk()上禁用中断的时间。

  

你的意思是调用local_irq_save()会阻止当前线程在另一个CPU上重新调度,只要它访问每个CPU的数据,或者它阻止当前线程在另一个CPU周期重新调度吗?

后者。 local_irq_save()可防止在本地CPU上处理中断,从ISR返回时可能最终调用schedule()schedule()也可以从其他地方调用,但由于printk()应该可以在中断上下文中使用,所以它调用的任何内容都不应该调用schedule()(例如:down_trylock()因此,使用而不是down())。

  

如果是printk(),local_irq_save()的目的是什么?

Jan Kara'} [PATCH 3/8] printk: Enable interrupts before calling console_trylock_for_printk()尝试最小化中断被禁用的时间。在该补丁之前,至少出于以下原因禁用中断:

  • 保护logbuf_lock - 受保护的数据免受中断。这种情况通常使用自旋锁的IRQ变体(例如:raw_spin_lock_irqsave())来解决,但是这里有更多的东西可以在禁用中断的情况下运行。
  • 防止当前CPU突然改变。从can_use_console()调用console_trylock_for_printk(),问:我们可以在此cpu上实际使用控制台吗?,注意:控制台驱动程序可能会认为已经分配了cpu资源。移动到另一个CPU可能会让人感到困惑。
  • 虽然console_semdown_trylock()一起使用,但如果中断也尝试printk(),并且在按住console_sem()时被抢占,则不应该成为问题会阻止其他人打印到控制台。

因此上面的补丁不再为后两种情况禁用中断,而是将它们包含在preempt_disable() / preempt_enable()中,这允许中断,但不是抢占。

  

我记得在LMKL上读过一个线程,说明禁止中断是为了确保日志缓冲区中条目的顺序反映了printk()调用的实际顺序。

你能分享对该线程的引用吗?您可能会在3.10中注意到有一个cont缓冲区,它包含上一个换行符中的所有字符。它会被冲到真实的"当换行符到达时,或者当另一个任务printk()时,记录缓冲区。

在这个帖子中,除了这段摘录之外,我没有找到关于日志条目排序的任何有效关注:

  嗯,我相信有人得到了   DDetetccctted ed 113223 HHzz CPUCPU

其中,AFAIK,完全是虚假的。 logbuf_lock保证对日志缓冲区进行排序, 这是在写入vprintk_emit()中的日志缓冲区时已经禁用的中断,并且是在raw_spin_lock_irqsave()上从中读取时禁用中断(console_unlock())的锁定变量。因此,访问日志缓冲区不会受到其他CPU或中断的干扰。

在较新的内核中,仍存在将日志行拆分为多个printk()调用的情况,该情况由cont缓冲区覆盖,该缓冲区包含部分行,并在另一个CPU时刷新它们/ interrupt干扰,因此日志行可以分成几行,并且它们之间有不相关的日志行,但没有日志行应该有混合输出。

剩下的另一个可能的腐败原因是,由于日志缓冲区是一个环形缓冲区,理论上它可能会溢出,这意味着会覆盖以前的消息。

该线程中的有效关注是及时输出日志缓冲区。这是通过尝试在每个console_unlock()调用中调用vprintk_emit()(调用控制台驱动程序)来实现的。如果无法获取控制台信号量,则消息已在日志缓冲区中,当前信号量所有者将其输出到控制台。

该线程中提到的一个有趣的事情是,在commit a0f1ccfd8d: "lockdep: do not recurse in printk"之前,(printk.c beforeafter)中断在调用release_console_sem()之前重新启用(是console_unlock())的前一个版本。显然,当启用lockdep(锁定验证器,能够检测可能的死锁和其他锁定问题以及打印诊断)时,它可能会在尝试从printk()打印时导致锁定。因此,对spin_{,un}lock_irq{save,restore}()的调用被分为禁用/启用中断和获取/释放锁,在它们之间添加了lockdep_on/off()个调用,并且扩展了lockdep和中断的禁用以覆盖整个功能。

回到:

  

我看到printk获取了logbuf_lock并写入日志缓冲区,然后尝试获取控制台信号量并释放logbuf_lock。然后在循环内部的console_unlock中获取logbuf_lock并禁用中断,然后释放logbuf_lock并调用控制台驱动程序,然后恢复中断。这个锁定/禁用中断序列的目的是什么?

console_unlock()不仅从vprintk_emit()调用,它还在注册新控制台时调用,在恢复控制台时,在将具有待处理输出的CPU热插拔到控制台时,...这些地方通常启用中断。因此,console_unlock()必须考虑到这一点。

您似乎已经注意到,虽然在logbuf_lockconsole_unlock()(它调用raw_spin_lock_irqsave())时禁用了中断,但在释放锁时它们(可能)没有被重新启用({ {1}}),它们只能在raw_spin_unlock()之后重新启用(local_irq_console())。我看到禁用中断调用call_console_drivers()的唯一原因是避免CPU在我们下面发生变化(控制台驱动程序可能访问每个CPU变量)。

一个有趣的数据点是-rt(实时)补丁集在调用call_console_drivers()之前以及console_unlock()之前(在-rt中受call_console_drivers()保护的情况下重新启用中断,这不允许migrate_disable()/migrate_enable()中的CPU迁移)。这样做是为了最小化console_unlock()期间的中断延迟。 printk()支持低中断延迟。您可以在printk.c in the linux-stable-rt tree at git.kernel.org看到它,相关的补丁是printk-rt-aware