printk中断禁用和locking

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

基本上我有三个问题:

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

  2. 我在printk()中看到有关日志缓冲区的注释可能被再次填满,所以缓冲区可能不得不再次刷新到控制台。 考虑到我在上面#1中提到的所有locking,这种情况会如何发生?

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

谢谢。

一些跟进:

你能澄清一下:

local_irq_save()可以防止本地CPU上的中断(并且避免在使用cpuvariables访问每个CPU数据时在另一个CPU上重新调度

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

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

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

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

是。

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

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

打印内核消息是一个两步过程。 首先将消息放入日志缓冲区,然后将日志缓冲区发送到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之前up()缓冲区可能已经被填满了。 而logbuf_lockup()之前释放console_sem因为up()可能会导致唤醒,需要运行runqueue锁,这可能会产生优先级反转问题, printk()使用runqueue锁来调用( commit 0b5e1c5255 )。

有人提出了修改这个锁定方案的补丁: Jan Kara [PATCH 0/8 v4] printk:清除和softlockup回避 ,其中包括尽量避免在console_unlock()上无限循环的CPU(它发生在大系统上在缓慢的串行控制台上记录许多启动事件),将这些工作交给其他CPU; 并尽量减少中断在printk()上被禁用的时间。

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

后者。 local_irq_save()防止在本地CPU上处理中断,当从ISR返回时,最终可能调用schedule()schedule()也可以从其他地方调用,但是由于printk()应该可以在中断上下文中使用,所以调用schedule() down_trylock()例如:使用down_trylock()代替down()这个原因)。

printk()的情况下,local_irq_save()的用途是什么?

Jan Kara的[PATCH 3/8] printk:在调用之前启用中断console_trylock_for_printk()会尽量减少中断被禁用的时间。 在该补丁之前,中断至少因以下原因而被禁用:

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

所以上面的补丁在后面两种情况下不再有中断被禁用,而是将它们封装在允许中断但不抢占的preempt_disable() / preempt_enable()

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

你能分享一个对该线程的引用吗? 你可能注意到在3.10中有一个cont缓冲区,它保存了最后一个换行符的所有字符。 当一个换行符到达时,或者当一个不同的任务printk() s被刷新到“真正的”日志缓冲区。

在这个线程上,除了这个摘录之外,我没有发现有关日志条目顺序的有效担忧:

那么,我相信有人得到DDetectccctted ed 113223 HHzz CPUCPU

其中,AFAIK,完全是假的。 日志缓冲区的排序是由logbuf_lock来保证的,当在写入到vprintk_emit()的日志缓冲区时,中断已经被禁止了,并且当在console_unlock()上读取中断时使用锁定变量来禁止中断( raw_spin_lock_irqsave()console_unlock() 。 所以,访问日志缓冲区是安全的,不受其他CPU或中断的干扰。

还有一种情况,在较新的内核中,日志行被拆分成多个printk()调用,这种情况被持有部分行的cont缓冲区覆盖,并且在另一个CPU /中断干扰时刷新它们,所以日志行可能被分成几行,在它们之间有不相关的日志行,但没有日志行应该有混合输出。

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

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

在这个线程中提到的一个有趣的事情是,在调用release_console_sem() (这是console_unlock()的前身release_console_sem()之前,重新启用( printk.c 之前和之后的 )中断之前提交a0f1ccfd8d:“lockdep:不要在printk中递归” )。 显然,当启用lockdep(锁定验证程序,能够检测到可能的死锁和其他锁定问题以及打印诊断程序)时,可能会在尝试从printk() 打印时导致锁定。 所以,调用spin_{,un}lock_irq{save,restore}()被分割成禁用/启用中断和获取/释放锁, lockdep_on/off()调用被添加到中间,锁定和中断被禁用扩展到覆盖整个功能。

回到:

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

console_unlock()不仅从vprintk_emit()调用,在注册新的控制台时,调用控制台时,调用具有挂起输出的CPU到控制台时也会调用console_unlock() 。这些地方通常启用了中断。 所以, console_unlock()必须考虑到这一点。

你似乎已经注意到,虽然在console_unlock()调用raw_spin_lock_irqsave() (它调用raw_spin_lock_irqsave() )时中断被禁用,但释放锁( raw_spin_unlock() )时它们不是(可能)重新启用的,它们只有潜在的重新启用( local_irq_console() )在call_console_drivers() 。 我看到调用call_console_drivers()并禁用中断的唯一原因是为了避免CPU在我们的下面改变(控制台驱动程序可能会访问每个CPU的变量)。

一个有趣的数据点是-rt(实时)补丁集在调用console_unlock()之前以及在call_console_drivers() (在-rt受到不允许CPU迁移的call_console_drivers()保护call_console_drivers()之前重新启用中断。 console_unlock() 。 这是为了尽量减少printk()中断延迟。 PREEMPT_RT_FULL支持低中断延迟。 你可以在git.kernel.org的linux-stable-rt树的printk.c中看到它,相关的补丁是printk-rt-aware 。