我对3.10内核中printk()的实现有疑问。我在开始时看到它calls local_irq_save
。然后我看到它calls raw_spin_lock(&logbuf_lock)
。如果在此之前已经禁用了中断,那么logbuf_lock的目的是什么?是因为即使在当前CPU上禁用了中断,其他CPU仍然可以调用printk,因此需要停止写入日志缓冲区吗?
基本上我有三个问题:
我看到printk获取了logbuf_lock并写入日志缓冲区,然后尝试获取控制台信号量并释放logbuf_lock。然后在循环内的console_unlock
内部获取logbuf_lock并禁用中断,然后释放logbuf_lock并调用控制台驱动程序,然后恢复中断。这种锁定/禁用中断序列的目的是什么?
我在printk()中看到有关日志缓冲区可能再次填满的注释,因此缓冲区可能必须再次刷新到控制台。考虑到我在上面#1中提到的所有锁定,这种情况会怎样?
如果在任何给定时刻只有1个CPU上的代码要调用printk(),那么SMP系统中的其他内核是否仍然可以处理中断?我也试图了解printk对中断延迟的影响。
感谢。
一些后续行动:
你能否澄清一下:
local_irq_save()
可防止本地CPU上的中断(并且还避免在使用cpu变量访问每个CPU数据时在另一个CPU上重新安排)
你的意思是调用local_irq_save()
只会在访问每个CPU数据时阻止当前线程在另一个CPU上重新调度,或者它是否阻止当前线程在另一个CPU周期重新调度?在这里printk()的情况下local_irq_save()的目的是什么?我记得在LMKL上读过一个线程,说明中断的禁用是为了确保日志缓冲区中的条目顺序反映了printk()调用的实际顺序。
答案 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_lock
在up()
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()
)来解决,但是这里有更多的东西可以在禁用中断的情况下运行。can_use_console()
调用console_trylock_for_printk()
,问:我们可以在此cpu上实际使用控制台吗?,注意:控制台驱动程序可能会认为已经分配了cpu资源。移动到另一个CPU可能会让人感到困惑。console_sem
与down_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
before和after)中断在调用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_lock
上console_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。