[TOC]
kernel/printk.c 内核打印(Kernel Printing) 内核信息输出的基础设施 历史与背景 这项技术是为了解决什么特定问题而诞生的? kernel/printk.c
及其核心功能 printk()
的诞生,是为了解决一个对于操作系统内核来说最根本的问题:如何从一个没有标准输出(stdout)、没有文件系统、甚至可能没有正常运行环境的受限上下文中,可靠地输出诊断信息。
用户空间的程序可以简单地使用 printf()
将信息打印到终端,但内核无法这样做。内核是所有用户空间程序运行的基础,它需要一个独立于任何用户进程的日志记录机制,以应对以下场景:
系统启动早期 :在 init
进程启动之前,甚至在控制台驱动初始化之前,就需要有方法来报告硬件探测、内存初始化等关键步骤的状态。
中断和异常上下文 :当内核正在处理一个硬件中断或CPU异常时,它处于一个不能睡眠、不能调用大部分内核函数的受限上下文中。此时需要一个足够安全、不会导致死锁的打印函数。
系统崩溃(Kernel Panic) :当系统遭遇无法恢复的致命错误时,printk
通常是内核在“死亡”前留下最后“遗言”的唯一方式,这些信息对于事后分析崩溃原因至关重要。
常规诊断与调试 :为内核开发者和系统管理员提供一个标准的、无处不在的接口来记录驱动程序的状态、警告和错误信息。
它的发展经历了哪些重要的里程碑或版本迭代? printk
是内核最古老、最核心的组件之一,其发展历程反映了内核自身的成熟过程。
基本实现 :最初的 printk
非常简单,可能只是直接将字符写入一个硬编码的串行端口。
环形缓冲区(Ring Buffer)的引入 :这是一个决定性的里程碑。内核实现了一个固定大小的内存环形缓冲区(log buffer),printk
将消息写入此缓冲区,而不是直接发送到硬件。这实现了生产者(printk
调用者)和消费者(控制台驱动)的解耦 。即使没有活动的控制台,消息也能被保存下来,供日后通过 dmesg
命令读取。
日志级别(Log Levels) :引入了 KERN_EMERG
, KERN_INFO
等日志级别。这允许根据消息的重要性进行过滤,例如,可以配置控制台只显示 KERN_WARNING
及以上级别的严重消息。
控制台抽象(Console Abstraction) :内核创建了 struct console
抽象层。任何可以显示字符的驱动(如串行端口驱动 ttyS
、VGA文本模式驱动 fgconsole
、网络控制台 netconsole
)都可以将自己注册为一个“控制台”。当 printk
唤醒消费者时,所有注册的活动控制台都会从环形缓冲区中读取并显示新消息。
并发与性能优化 :在多核(SMP)系统上,多个CPU可能同时调用 printk
。为了处理并发,引入了自旋锁 (logbuf_lock
)。后续为了减少锁竞争,又引入了更复杂的机制,如 per-CPU 缓冲区,以提高性能。
速率限制(Rate Limiting) :为了防止有缺陷的驱动程序疯狂打印日志(log flood)导致系统性能下降和日志被冲刷,内核引入了 printk_ratelimit()
机制。
结构化与字典压缩 :较新的内核版本正在尝试引入结构化日志和字典压缩技术,以减少日志的体积,并使其更易于被机器解析。
目前该技术的社区活跃度和主流应用情况如何? printk
是Linux内核中最基础、最稳定、使用最广泛 的功能,没有之一。
主流应用 :它是所有内核代码(核心、驱动、文件系统等)输出日志信息的标准方式 。dmesg
命令是每个Linux系统管理员必备的诊断工具,其内容就直接来自 printk
的环形缓冲区。journald
, rsyslog
等用户空间日志服务也会从内核读取 printk
的输出。它是内核开发“Hello, World!”的第一步。
核心原理与设计 它的核心工作原理是什么? printk
的工作流程可以概括为“生产者-消费者”模型:
生产者(printk
调用) :
内核代码调用 printk(KERN_INFO "Device initialized with IRQ %d\n", irq);
。
该函数首先会解析格式化字符串和参数,生成最终的日志消息。
它会为消息添加一个前缀,包含日志级别、时间戳等元数据。
然后,它会获取保护环形缓冲区的锁(logbuf_lock
)。
消息被原子地 写入到内核的全局环形日志缓冲区 (log_buf
) 中。
写入完成后,释放锁。
唤醒消费者 :
写入新消息后,printk
会唤醒所有已注册的控制台(consoles)。
消费者(控制台驱动) :
被唤醒的控制台驱动(例如,串口驱动)会再次获取锁,检查环形缓冲区中是否有自己尚未打印的新消息。
如果有,它会读取这些消息,并将其输出到它所管理的物理硬件上(如通过串口发送出去,或显示在屏幕上)。
用户空间接口 :
用户空间程序(如 dmesg
)可以通过 /dev/kmsg
接口或 syslog()
系统调用,直接从内核的环形缓冲区中读取所有日志消息,无论它们是否曾被打印到物理控制台。
它的主要优势体现在哪些方面?
可靠性和健壮性 :printk
被设计为在内核几乎任何状态下都能工作,包括中断处理、系统恐慌等极端情况。
简单性 :其 printf
-like 的API对C程序员来说非常熟悉,使用门槛极低。
解耦设计 :通过环形缓冲区,printk
的调用者无需关心消息最终将如何、以及在何处显示。
通用性 :控制台抽象层使得 printk
的输出可以被重定向到多种物理设备。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
性能开销 :printk
不是一个“免费”的操作。它涉及字符串格式化、获取全局锁、唤醒其他任务等,开销相对较大。在性能极其敏感的路径(如网络数据包处理路径)中频繁调用 printk
会严重影响系统性能。
日志风暴(Log Flood) :一个有 bug 的驱动程序可能会在循环中不停地调用 printk
,导致环形缓冲区被迅速填满,淹没掉其他有用的信息,并消耗大量CPU。
信息泄露风险 :开发者可能会不慎将内核的敏感信息(如内存地址、数据结构内容)打印出来,构成安全隐患。
非结构化 :传统的 printk
输出是纯文本,不利于自动化工具的解析和分析。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案? printk
是用于内核向外界报告异步事件、状态和错误 的首选和标准方案,特别是对于频率不高的事件。
驱动初始化和探测 :报告硬件是否被成功识别,资源(IRQ, I/O地址)是否分配成功。
错误和警告报告 :报告硬件故障、非法的操作请求、资源耗尽等异常情况。
重要的状态变更 :例如,网络接口的 UP
/DOWN
,磁盘的挂载/卸载。
调试 :在开发和调试阶段,用于追踪代码执行路径和变量值。为了避免在生产环境中产生不必要的输出,通常会使用 pr_debug()
或 dev_dbg()
等宏,它们在内核编译时若未定义DEBUG
则会变为空操作。
是否有不推荐使用该技术的场景?为什么?
性能敏感的热路径(Hot Path) :在每秒需要执行数百万次的代码路径中(如网络驱动的核心收发包函数、调度器的核心决策逻辑),应绝对避免 使用 printk
。这种场景应使用Tracepoints ,它在关闭时几乎没有开销。
高频事件 :不要为每个成功处理的数据包或每个发生的硬件中断调用 printk
。
用户空间与内核的数据交换 :printk
是单向的日志通道,不应用作用户空间和内核之间的双向通信机制。应使用 ioctl
, netlink
, sysfs
或 procfs
。
对比分析 请将其 与 其他相似技术 进行详细对比。
特性
printk
Tracepoints / trace_printk
pr_debug
/ dev_dbg
procfs
/sysfs
主要用途
通用日志记录 (错误、警告、信息)
高性能追踪和调试
条件编译的调试打印
导出状态/配置接口
性能开销
中到高 。总是会格式化字符串并尝试获取锁。
极低 (当关闭时)。开启时,写入高效的二进制追踪缓冲区。
零 (当DEBUG
未定义时)。宏展开为空,代码被编译掉。
read
/write
时才有开销。
输出目标
内核环形缓冲区 (dmesg
) 和控制台。
ftrace/perf 的二进制追踪缓冲区。
与printk
相同,但受编译条件限制。
文件系统中的文件。
数据格式
非结构化文本。
结构化二进制数据。
非结构化文本。
结构化或非结构化文本。
运行时控制
可通过控制台日志级别过滤输出。
可通过 ftrace
接口动态开启/关闭/过滤。
编译时决定,运行时无法开启。
总是可读/写。
适用场景
报告低频、重要的系统事件和错误。
调试性能问题,追踪高频事件。
在开发阶段添加调试代码,发布时移除。
向用户空间暴露内核对象的状态和配置。
include/linux/kern_levels.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #define KERN_SOH "\001" #define KERN_SOH_ASCII '\001' #define KERN_EMERG KERN_SOH "0" #define KERN_ALERT KERN_SOH "1" #define KERN_CRIT KERN_SOH "2" #define KERN_ERR KERN_SOH "3" #define KERN_WARNING KERN_SOH "4" #define KERN_NOTICE KERN_SOH "5" #define KERN_INFO KERN_SOH "6" #define KERN_DEBUG KERN_SOH "7" #define KERN_DEFAULT "" #define KERN_CONT KERN_SOH "c"
include/linux/printk.h printk 等级 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define pr_emerg(fmt, ...) \ printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__) #define pr_alert(fmt, ...) \ printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__) #define pr_crit(fmt, ...) \ printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__) #define pr_err(fmt, ...) \ printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__) #define pr_warn(fmt, ...) \ printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__) #define pr_notice(fmt, ...) \ printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__) #define pr_info(fmt, ...) \ printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__) #define pr_cont(fmt, ...) \ printk(KERN_CONT fmt, ##__VA_ARGS__) #ifdef DEBUG #define pr_devel(fmt, ...) \ printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #else #define pr_devel(fmt, ...) \ no_printk(KERN_DEBUG pr_fmt(fmt), ##__VA_ARGS__) #endif
printk_get_level 获取日志级别
1 2 3 4 5 6 7 8 9 10 11 static inline int printk_get_level (const char *buffer) { if (buffer[0 ] == KERN_SOH_ASCII && buffer[1 ]) { switch (buffer[1 ]) { case '0' ... '7' : case 'c' : return buffer[1 ]; } } return 0 ; }
printk_deferred_enter 和printk_deferred_exit 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void __printk_safe_enter(void ){ this_cpu_inc(printk_context); } void __printk_safe_exit(void ){ this_cpu_dec(printk_context); } void __printk_deferred_enter(void ){ cant_migrate(); __printk_safe_enter(); } void __printk_deferred_exit(void ){ cant_migrate(); __printk_safe_exit(); } #define printk_deferred_enter() __printk_deferred_enter() #define printk_deferred_exit() __printk_deferred_exit()
kernel/printk/internal.h printk_get_console_flush_type 确定使用哪些控制台刷新方法
have_nbcon_console
和have_legacy_console
在register_console
时设置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 static inline void printk_get_console_flush_type (struct console_flush_type *ft) { memset (ft, 0 , sizeof (*ft)); switch (nbcon_get_default_prio()) { case NBCON_PRIO_NORMAL: if (have_nbcon_console && !have_boot_console) { if (printk_kthreads_running) ft->nbcon_offload = true ; else ft->nbcon_atomic = true ; } if (have_legacy_console || have_boot_console) { if (!is_printk_legacy_deferred()) ft->legacy_direct = true ; else ft->legacy_offload = true ; } break ; case NBCON_PRIO_EMERGENCY: if (have_nbcon_console && !have_boot_console) ft->nbcon_atomic = true ; if (have_legacy_console || have_boot_console) { if (!is_printk_legacy_deferred()) ft->legacy_direct = true ; else ft->legacy_offload = true ; } break ; case NBCON_PRIO_PANIC: if (have_nbcon_console && !have_boot_console) ft->nbcon_atomic = true ; if (have_legacy_console || have_boot_console) { if (!is_printk_legacy_deferred()) ft->legacy_direct = true ; if (ft->nbcon_atomic && !legacy_allow_panic_sync) ft->legacy_direct = false ; } break ; default : WARN_ON_ONCE(1 ); break ; } }
printk_info_flags 1 2 3 4 5 6 7 enum printk_info_flags { LOG_FORCE_CON = 1 , LOG_NEWLINE = 2 , LOG_CONT = 8 , };
console_is_usable 检查控制台是否可用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static inline bool console_is_usable (struct console *con, short flags, bool use_atomic) { if (!(flags & CON_ENABLED)) return false ; if ((flags & CON_SUSPENDED)) return false ; if (flags & CON_NBCON) { if (use_atomic && !con->write_atomic) return false ; } else { if (!con->write) return false ; } if (!cpu_online(raw_smp_processor_id()) && !(flags & CON_ANYTIME)) return false ; return true ; }
include/linux/console.h for_each_console 遍历控制台列表 1 2 3 4 5 6 7 8 9 10 11 12 13 HLIST_HEAD(console_list); #define for_each_console(con) \ lockdep_assert_console_list_lock_held(); \ hlist_for_each_entry(con, &console_list, node)
cons_flags 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 enum cons_flags { CON_PRINTBUFFER = BIT(0 ), CON_CONSDEV = BIT(1 ), CON_ENABLED = BIT(2 ), CON_BOOT = BIT(3 ), CON_ANYTIME = BIT(4 ), CON_BRL = BIT(5 ), CON_EXTENDED = BIT(6 ), CON_SUSPENDED = BIT(7 ), CON_NBCON = BIT(8 ), };
console_srcu_read_flags 无锁读取可能已注册控制台的标志 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static inline short console_srcu_read_flags (const struct console *con) { WARN_ON_ONCE(!console_srcu_read_lock_is_held()); return data_race(READ_ONCE(con->flags)); }
console_is_registered_locked 控制台是否已注册 1 2 3 4 5 6 static inline bool console_is_registered_locked (const struct console *con) { lockdep_assert_console_list_lock_held(); return !hlist_unhashed(&con->node); }
kernel/printk/printk_ringbuffer.h 内核打印环形缓冲区实现 prb_desc 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 enum desc_state { desc_miss = -1 , desc_reserved = 0x0 , desc_committed = 0x1 , desc_finalized = 0x2 , desc_reusable = 0x3 , }; struct prb_desc { atomic_long_t state_var; struct prb_data_blk_lpos text_blk_lpos ; };
_DEFINE_PRINTKRB 定义 ringbuffer 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 #define _DATA_SIZE(sz_bits) (1UL << (sz_bits)) #define _DESCS_COUNT(ct_bits) (1U << (ct_bits)) #define DESC_SV_BITS BITS_PER_LONG #define DESC_FLAGS_SHIFT (DESC_SV_BITS - 2) #define DESC_FLAGS_MASK (3UL << DESC_FLAGS_SHIFT) #define DESC_STATE(sv) (3UL & (sv >> DESC_FLAGS_SHIFT)) #define DESC_SV(id, state) (((unsigned long)state << DESC_FLAGS_SHIFT) | id) #define DESC_ID_MASK (~DESC_FLAGS_MASK) #define DESC_ID(sv) ((sv) & DESC_ID_MASK) #define BLK0_LPOS(sz_bits) (-(_DATA_SIZE(sz_bits))) #define DESC0_ID(ct_bits) DESC_ID(-(_DESCS_COUNT(ct_bits) + 1)) #define DESC0_SV(ct_bits) DESC_SV(DESC0_ID(ct_bits), desc_reusable) #define FAILED_LPOS 0x1 #define EMPTY_LINE_LPOS 0x3 #define FAILED_BLK_LPOS \ { \ .begin = FAILED_LPOS, \ .next = FAILED_LPOS, \ } #define _DEFINE_PRINTKRB(name, descbits, avgtextbits, text_buf) \ static struct prb_desc _##name##_descs[_DESCS_COUNT(descbits)] = { \ \ [_DESCS_COUNT(descbits) - 1] = { \ \ .state_var = ATOMIC_INIT(DESC0_SV(descbits)), \ \ .text_blk_lpos = FAILED_BLK_LPOS, \ }, \ }; \ static struct printk_info _##name ##_infos [_DESCS_COUNT (descbits )] = { \ \ [0 ] = { \ \ .seq = -(u64)_DESCS_COUNT(descbits), \ }, \ \ [_DESCS_COUNT(descbits) - 1 ] = { \ \ .seq = 0 , \ }, \ }; \ static struct printk_ringbuffer name = { \ .desc_ring = { \ .count_bits = descbits, \ .descs = &_##name##_descs[0], \ .infos = &_##name##_infos[0], \ .head_id = ATOMIC_INIT(DESC0_ID(descbits)), \ .tail_id = ATOMIC_INIT(DESC0_ID(descbits)), \ .last_finalized_seq = ATOMIC_INIT(0), \ }, \ .text_data_ring = { \ .size_bits = (avgtextbits) + (descbits), \ .data = text_buf, \ .head_lpos = ATOMIC_LONG_INIT(BLK0_LPOS((avgtextbits) + (descbits))), \ .tail_lpos = ATOMIC_LONG_INIT(BLK0_LPOS((avgtextbits) + (descbits))), \ }, \ .fail = ATOMIC_LONG_INIT(0), \ }
kernel/printk/printk_ringbuffer.c 内核打印环形缓冲区实现 macros 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #define DATA_SIZE(data_ring) _DATA_SIZE((data_ring)->size_bits) #define DATA_SIZE_MASK(data_ring) (DATA_SIZE(data_ring) - 1) #define DESCS_COUNT(desc_ring) _DESCS_COUNT((desc_ring)->count_bits) #define DESCS_COUNT_MASK(desc_ring) (DESCS_COUNT(desc_ring) - 1) #define DATA_INDEX(data_ring, lpos) ((lpos) & DATA_SIZE_MASK(data_ring)) #define DESC_INDEX(desc_ring, n) ((n) & DESCS_COUNT_MASK(desc_ring)) #define DATA_WRAPS(data_ring, lpos) ((lpos) >> (data_ring)->size_bits) #define LPOS_DATALESS(lpos) ((lpos) & 1UL) #define BLK_DATALESS(blk) (LPOS_DATALESS((blk)->begin) && \ LPOS_DATALESS((blk)->next)) #define DATA_THIS_WRAP_START_LPOS(data_ring, lpos) \ ((lpos) & ~DATA_SIZE_MASK(data_ring)) #define DESC_ID_PREV_WRAP(desc_ring, id) \ DESC_ID((id) - DESCS_COUNT(desc_ring))
desc_reopen_last 尝试将最新的描述符从 committed 转换回 reserved 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static struct prb_desc *desc_reopen_last (struct prb_desc_ring *desc_ring, u32 caller_id, unsigned long *id_out) { unsigned long prev_state_val; enum desc_state d_state ; struct prb_desc desc ; struct prb_desc *d ; unsigned long id; u32 cid; id = atomic_long_read(&desc_ring->head_id); d_state = desc_read(desc_ring, id, &desc, NULL , &cid); if (d_state != desc_committed || cid != caller_id) return NULL ; d = to_desc(desc_ring, id); prev_state_val = DESC_SV(id, desc_committed); if (!atomic_long_try_cmpxchg(&d->state_var, &prev_state_val, DESC_SV(id, desc_reserved))) { return NULL ; } *id_out = id; return d; }
prb_reserve_in_last 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 bool prb_reserve_in_last (struct prb_reserved_entry *e, struct printk_ringbuffer *rb, struct printk_record *r, u32 caller_id, unsigned int max_size) { struct prb_desc_ring *desc_ring = &rb->desc_ring; struct printk_info *info ; unsigned int data_size; struct prb_desc *d ; unsigned long id; local_irq_save(e->irqflags); d = desc_reopen_last(desc_ring, caller_id, &id); if (!d) { local_irq_restore(e->irqflags); goto fail_reopen; } info = to_info(desc_ring, id); e->rb = rb; e->id = id; if (caller_id != info->caller_id) goto fail; if (BLK_DATALESS(&d->text_blk_lpos)) { if (WARN_ON_ONCE(info->text_len != 0 )) { pr_warn_once("wrong text_len value (%hu, expecting 0)\n" , info->text_len); info->text_len = 0 ; } if (!data_check_size(&rb->text_data_ring, r->text_buf_size)) goto fail; if (r->text_buf_size > max_size) goto fail; r->text_buf = data_alloc(rb, r->text_buf_size, &d->text_blk_lpos, id); } else { if (!get_data(&rb->text_data_ring, &d->text_blk_lpos, &data_size)) goto fail; if (WARN_ON_ONCE(info->text_len > data_size)) { pr_warn_once("wrong text_len value (%hu, expecting <=%u)\n" , info->text_len, data_size); info->text_len = data_size; } r->text_buf_size += info->text_len; if (!data_check_size(&rb->text_data_ring, r->text_buf_size)) goto fail; if (r->text_buf_size > max_size) goto fail; r->text_buf = data_realloc(rb, r->text_buf_size, &d->text_blk_lpos, id); } if (r->text_buf_size && !r->text_buf) goto fail; r->info = info; e->text_space = space_used(&rb->text_data_ring, &d->text_blk_lpos); return true ; fail: prb_commit(e); fail_reopen: memset (r, 0 , sizeof (*r)); return false ; }
to_blk_size 分配对齐的区域并添加指针所需的空间 1 2 3 4 5 6 7 8 9 10 11 12 static unsigned int to_blk_size (unsigned int size) { struct prb_data_block *db = NULL ; size += sizeof (*db); size = ALIGN(size, sizeof (db->id)); return size; }
data_check_size 检查数据大小是否可以存入 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static bool data_check_size (struct prb_data_ring *data_ring, unsigned int size) { struct prb_data_block *db = NULL ; if (size == 0 ) return true ; size = to_blk_size(size); if (size > DATA_SIZE(data_ring) - sizeof (db->id)) return false ; return true ; }
get_desc_state 查询描述符的状态。 1 2 3 4 5 6 7 8 9 static enum desc_state get_desc_state (unsigned long id, unsigned long state_val) { if (id != DESC_ID(state_val)) return desc_miss; return DESC_STATE(state_val); }
desc_read 获取指定描述符的副本并返回其查询状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 static enum desc_state desc_read (struct prb_desc_ring *desc_ring, unsigned long id, struct prb_desc *desc_out, u64 *seq_out, u32 *caller_id_out) { struct printk_info *info = to_info(desc_ring, id); struct prb_desc *desc = to_desc(desc_ring, id); atomic_long_t *state_var = &desc->state_var; enum desc_state d_state ; unsigned long state_val; state_val = atomic_long_read(state_var); d_state = get_desc_state(id, state_val); if (d_state == desc_miss || d_state == desc_reserved) { goto out; } smp_rmb(); if (desc_out) { memcpy (&desc_out->text_blk_lpos, &desc->text_blk_lpos, sizeof (desc_out->text_blk_lpos)); } if (seq_out) *seq_out = info->seq; if (caller_id_out) *caller_id_out = info->caller_id; smp_rmb(); state_val = atomic_long_read(state_var); d_state = get_desc_state(id, state_val); out: if (desc_out) atomic_long_set(&desc_out->state_var, state_val); return d_state; }
data_push_tail 将数据环形缓冲区的尾部推进到至少 @lpos 的位置。此函数会将与尾部推进超出其关联数据块的描述符置为可重用状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static bool data_push_tail (struct printk_ringbuffer *rb, unsigned long lpos) { struct prb_data_ring *data_ring = &rb->text_data_ring; unsigned long tail_lpos_new; unsigned long tail_lpos; unsigned long next_lpos; if (LPOS_DATALESS(lpos)) return true ; tail_lpos = atomic_long_read(&data_ring->tail_lpos); while ((lpos - tail_lpos) - 1 < DATA_SIZE(data_ring)) { if (!data_make_reusable(rb, tail_lpos, lpos, &next_lpos)) { smp_rmb(); tail_lpos_new = atomic_long_read(&data_ring->tail_lpos ); if (tail_lpos_new == tail_lpos) return false ; tail_lpos = tail_lpos_new; continue ; } if (atomic_long_try_cmpxchg(&data_ring->tail_lpos, &tail_lpos, next_lpos)) { break ; } } return true ; }
desc_push_tail 推进描述符环的尾部
读取最后一个节点的状态
判断最后一个节点的状态.
如果最后一个节点的状态是desc_miss,则判断ID是否恰好比预期 ID 晚 1 个换行,则它正在由另一个写入器保留,并且必须被视为保留。
如果最后一个节点的状态是desc_reserved,则返回false,表示描述符正在由编写器
如果最后一个节点的状态是desc_committed,则返回false,表示描述符已提交,可以重新打开
如果最后一个节点的状态是desc_finalized,则将描述符设置为可重用状态
如果最后一个节点的状态是desc_reusable,则表示free,尚未被任何作者使用
desc_finalized 和 desc_reusable 状态的描述符可以被推送到下一个描述符。
进行数据块的失效操作,使其关联的描述符可供回收。
在将 tail 推向 next 之前,请检查 @tail_id 之后的下一个 Descriptors,
如果下一个描述符小于或等于 @head_id因此不存在将尾部推过头部的风险。可以进行推进操作。
否则,则重新检查尾部 ID。@tail_id后面的描述符未处于允许的尾部状态。但是,如果尾部此后被另一个 CPU 移动,则无关紧要。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 static bool desc_push_tail (struct printk_ringbuffer *rb, unsigned long tail_id) { struct prb_desc_ring *desc_ring = &rb->desc_ring; enum desc_state d_state ; struct prb_desc desc ; d_state = desc_read(desc_ring, tail_id, &desc, NULL , NULL ); switch (d_state) { case desc_miss: if (DESC_ID(atomic_long_read(&desc.state_var)) == DESC_ID_PREV_WRAP(desc_ring, tail_id)) { return false ; } return true ; case desc_reserved: case desc_committed: return false ; case desc_finalized: desc_make_reusable(desc_ring, tail_id); break ; case desc_reusable: break ; } if (!data_push_tail(rb, desc.text_blk_lpos.next)) return false ; d_state = desc_read(desc_ring, DESC_ID(tail_id + 1 ), &desc, NULL , NULL ); if (d_state == desc_finalized || d_state == desc_reusable) { atomic_long_cmpxchg(&desc_ring->tail_id, tail_id, DESC_ID(tail_id + 1 )); } else { smp_rmb(); if (atomic_long_read(&desc_ring->tail_id) == tail_id) return false ; } return true ; }
desc_reserve 返回新描述符,必要时使最旧的描述符失效。
原子读取描述符环的头部 ID。
判断上一次环绕索引的 ID 是否与当前尾部 ID 一致,则尝试将尾部 ID 向前推进以为新描述符腾出空间。
尝试xchg写入head_id为新值
这时候分配了未被使用的desc
判断desc->state_var不是未使用状态,则证明程序出现了问题,发出警告
写入desc->state_var为DESC_SV(id, desc_reserved) 也就是将描述符的状态设置为保留状态。
给出id_out可以被进行使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 static bool desc_reserve (struct printk_ringbuffer *rb, unsigned long *id_out) { struct prb_desc_ring *desc_ring = &rb->desc_ring; unsigned long prev_state_val; unsigned long id_prev_wrap; struct prb_desc *desc ; unsigned long head_id; unsigned long id; head_id = atomic_long_read(&desc_ring->head_id); do { id = DESC_ID(head_id + 1 ); id_prev_wrap = DESC_ID_PREV_WRAP(desc_ring, id); smp_rmb(); if (id_prev_wrap == atomic_long_read(&desc_ring->tail_id )) { if (!desc_push_tail(rb, id_prev_wrap)) return false ; } } while (!atomic_long_try_cmpxchg(&desc_ring->head_id, &head_id, id)); desc = to_desc(desc_ring, id); prev_state_val = atomic_long_read(&desc->state_var); if (prev_state_val && get_desc_state(id_prev_wrap, prev_state_val) != desc_reusable) { WARN_ON_ONCE(1 ); return false ; } if (!atomic_long_try_cmpxchg(&desc->state_var, &prev_state_val, DESC_SV(id, desc_reserved))) { WARN_ON_ONCE(1 ); return false ; } *id_out = id; return true ; }
get_next_lpos 确定数据块的结尾 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 static unsigned long get_next_lpos (struct prb_data_ring *data_ring, unsigned long lpos, unsigned int size) { unsigned long begin_lpos; unsigned long next_lpos; begin_lpos = lpos; next_lpos = lpos + size; if (DATA_WRAPS(data_ring, begin_lpos) == DATA_WRAPS(data_ring, next_lpos)) return next_lpos; return (DATA_THIS_WRAP_START_LPOS(data_ring, next_lpos) + size); }
data_alloc 分配一个新的数据块,如果有必要会使最旧的数据块失效 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 static char *data_alloc (struct printk_ringbuffer *rb, unsigned int size, struct prb_data_blk_lpos *blk_lpos, unsigned long id) { struct prb_data_ring *data_ring = &rb->text_data_ring; struct prb_data_block *blk ; unsigned long begin_lpos; unsigned long next_lpos; if (size == 0 ) { blk_lpos->begin = EMPTY_LINE_LPOS; blk_lpos->next = EMPTY_LINE_LPOS; return NULL ; } size = to_blk_size(size); begin_lpos = atomic_long_read(&data_ring->head_lpos); do { next_lpos = get_next_lpos(data_ring, begin_lpos, size); if (!data_push_tail(rb, next_lpos - DATA_SIZE(data_ring))) { blk_lpos->begin = FAILED_LPOS; blk_lpos->next = FAILED_LPOS; return NULL ; } } while (!atomic_long_try_cmpxchg(&data_ring->head_lpos, &begin_lpos, next_lpos)); blk = to_block(data_ring, begin_lpos); blk->id = id; if (DATA_WRAPS(data_ring, begin_lpos) != DATA_WRAPS(data_ring, next_lpos)) { blk = to_block(data_ring, 0 ); blk->id = id; } blk_lpos->begin = begin_lpos; blk_lpos->next = next_lpos; return &blk->data[0 ]; }
prb_commit 将数据状态设置为提交 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 static void _prb_commit(struct prb_reserved_entry *e, unsigned long state_val){ struct prb_desc_ring *desc_ring = &e->rb->desc_ring; struct prb_desc *d = to_desc(desc_ring, e->id); unsigned long prev_state_val = DESC_SV(e->id, desc_reserved); if (!atomic_long_try_cmpxchg(&d->state_var, &prev_state_val, DESC_SV(e->id, state_val))) { WARN_ON_ONCE(1 ); } local_irq_restore(e->irqflags); } void prb_commit (struct prb_reserved_entry *e) { struct prb_desc_ring *desc_ring = &e->rb->desc_ring; unsigned long head_id; _prb_commit(e, desc_committed); head_id = atomic_long_read(&desc_ring->head_id); if (head_id != e->id) desc_make_final(e->rb, e->id); }
prb_reserve 在环形缓冲区中保留空间 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 bool prb_reserve (struct prb_reserved_entry *e, struct printk_ringbuffer *rb, struct printk_record *r) { struct prb_desc_ring *desc_ring = &rb->desc_ring; struct printk_info *info ; struct prb_desc *d ; unsigned long id; u64 seq; if (!data_check_size(&rb->text_data_ring, r->text_buf_size)) goto fail; local_irq_save(e->irqflags); if (!desc_reserve(rb, &id)) { atomic_long_inc(&rb->fail); local_irq_restore(e->irqflags); goto fail; } d = to_desc(desc_ring, id); info = to_info(desc_ring, id); seq = info->seq; memset (info, 0 , sizeof (*info)); e->rb = rb; e->id = id; if (seq == 0 && DESC_INDEX(desc_ring, id) != 0 ) info->seq = DESC_INDEX(desc_ring, id); else info->seq = seq + DESCS_COUNT(desc_ring); if (info->seq > 0 ) desc_make_final(rb, DESC_ID(id - 1 )); r->text_buf = data_alloc(rb, r->text_buf_size, &d->text_blk_lpos, id); if (r->text_buf_size && !r->text_buf) { prb_commit(e); goto fail; } r->info = info; e->text_space = space_used(&rb->text_data_ring, &d->text_blk_lpos); return true ; fail: memset (r, 0 , sizeof (*r)); return false ; }
prb_rec_init_wr 初始化用于写入记录的缓冲区。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static inline void prb_rec_init_wr (struct printk_record *r, unsigned int text_buf_size) { r->info = NULL ; r->text_buf = NULL ; r->text_buf_size = text_buf_size; }
prb_first_seq 获取尾部描述符的序列号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 u64 prb_first_seq (struct printk_ringbuffer *rb) { struct prb_desc_ring *desc_ring = &rb->desc_ring; enum desc_state d_state ; struct prb_desc desc ; unsigned long id; u64 seq; for (;;) { id = atomic_long_read(&rb->desc_ring.tail_id); d_state = desc_read(desc_ring, id, &desc, &seq, NULL ); if (d_state == desc_finalized || d_state == desc_reusable) break ; smp_rmb(); } return seq; }
__ulseq_to_u64seq 将 32 位序列号转换为 64 位序列号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static inline u64 __ulseq_to_u64seq(struct printk_ringbuffer *rb, u32 ulseq){ u64 rb_first_seq = prb_first_seq(rb); u64 seq; seq = rb_first_seq - (s32)((u32)rb_first_seq - ulseq); return seq; }
desc_last_finalized_seq 获取最后一个已完成的序列号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static u64 desc_last_finalized_seq (struct printk_ringbuffer *rb) { struct prb_desc_ring *desc_ring = &rb->desc_ring; unsigned long ulseq; ulseq = atomic_long_read_acquire(&desc_ring->last_finalized_seq ); return __ulseq_to_u64seq(rb, ulseq); }
desc_read_finalized_seq 获取指定描述符的副本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static int desc_read_finalized_seq (struct prb_desc_ring *desc_ring, unsigned long id, u64 seq, struct prb_desc *desc_out) { struct prb_data_blk_lpos *blk_lpos = &desc_out->text_blk_lpos; enum desc_state d_state ; u64 s; d_state = desc_read(desc_ring, id, desc_out, &s, NULL ); if (d_state == desc_miss || d_state == desc_reserved || d_state == desc_committed || s != seq) { return -EINVAL; } if (d_state == desc_reusable || (blk_lpos->begin == FAILED_LPOS && blk_lpos->next == FAILED_LPOS)) { return -ENOENT; } return 0 ; }
get_data 给定@blk_lpos,从数据块返回指向写入器数据的指针,并计算数据部分的大小 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 static const char *get_data (struct prb_data_ring *data_ring, struct prb_data_blk_lpos *blk_lpos, unsigned int *data_size) { struct prb_data_block *db ; if (BLK_DATALESS(blk_lpos)) { if (blk_lpos->begin == EMPTY_LINE_LPOS && blk_lpos->next == EMPTY_LINE_LPOS) { *data_size = 0 ; return "" ; } return NULL ; } if (DATA_WRAPS(data_ring, blk_lpos->begin) == DATA_WRAPS(data_ring, blk_lpos->next) && blk_lpos->begin < blk_lpos->next) { db = to_block(data_ring, blk_lpos->begin); *data_size = blk_lpos->next - blk_lpos->begin; } else if (DATA_WRAPS(data_ring, blk_lpos->begin + DATA_SIZE(data_ring)) == DATA_WRAPS(data_ring, blk_lpos->next)) { db = to_block(data_ring, 0 ); *data_size = DATA_INDEX(data_ring, blk_lpos->next); } else { WARN_ON_ONCE(1 ); return NULL ; } if (WARN_ON_ONCE(blk_lpos->begin != ALIGN(blk_lpos->begin, sizeof (db->id))) || WARN_ON_ONCE(blk_lpos->next != ALIGN(blk_lpos->next, sizeof (db->id)))) { return NULL ; } if (WARN_ON_ONCE(*data_size < sizeof (db->id))) return NULL ; *data_size -= sizeof (db->id); return &db->data[0 ]; }
copy_data 给定@blk_lpos,将预期的数据@len复制到提供的缓冲区中 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static bool copy_data (struct prb_data_ring *data_ring, struct prb_data_blk_lpos *blk_lpos, u16 len, char *buf, unsigned int buf_size, unsigned int *line_count) { unsigned int data_size; const char *data; if ((!buf || !buf_size) && !line_count) return true ; data = get_data(data_ring, blk_lpos, &data_size); if (!data) return false ; if (data_size < (unsigned int )len) return false ; if (line_count) *line_count = count_lines(data, len); if (!buf || !buf_size) return true ; data_size = min_t (unsigned int , buf_size, len); memcpy (&buf[0 ], data, data_size); return true ; }
prb_read 将带有 @seq 的记录中的 ringbuffer 数据复制到提供的 @r 缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static int prb_read (struct printk_ringbuffer *rb, u64 seq, struct printk_record *r, unsigned int *line_count) { struct prb_desc_ring *desc_ring = &rb->desc_ring; struct printk_info *info = to_info(desc_ring, seq); struct prb_desc *rdesc = to_desc(desc_ring, seq); atomic_long_t *state_var = &rdesc->state_var; struct prb_desc desc ; unsigned long id; int err; id = DESC_ID(atomic_long_read(state_var)); err = desc_read_finalized_seq(desc_ring, id, seq, &desc); if (err || !r) return err; if (r->info) memcpy (r->info, info, sizeof (*(r->info))); if (!copy_data(&rb->text_data_ring, &desc.text_blk_lpos, info->text_len, r->text_buf, r->text_buf_size, line_count)) { return -ENOENT; } return desc_read_finalized_seq(desc_ring, id, seq, &desc); }
prb_next_reserve_seq 获取最近保留记录之后的序列号。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 u64 prb_next_reserve_seq (struct printk_ringbuffer *rb) { struct prb_desc_ring *desc_ring = &rb->desc_ring; unsigned long last_finalized_id; atomic_long_t *state_var; u64 last_finalized_seq; unsigned long head_id; struct prb_desc desc ; unsigned long diff; struct prb_desc *d ; int err; try_again: last_finalized_seq = desc_last_finalized_seq(rb); head_id = atomic_long_read(&desc_ring->head_id); d = to_desc(desc_ring, last_finalized_seq); state_var = &d->state_var; last_finalized_id = DESC_ID(atomic_long_read(state_var)); err = desc_read_finalized_seq(desc_ring, last_finalized_id, last_finalized_seq, &desc); if (err == -EINVAL) { if (last_finalized_seq == 0 ) { if (head_id == DESC0_ID(desc_ring->count_bits)) return 0 ; last_finalized_id = DESC0_ID(desc_ring->count_bits) + 1 ; } else { goto try_again; } } diff = head_id - last_finalized_id; return (last_finalized_seq + diff + 1 ); }
_prb_read_valid 非阻塞读取记录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 static bool _prb_read_valid(struct printk_ringbuffer *rb, u64 *seq, struct printk_record *r, unsigned int *line_count) { u64 tail_seq; int err; while ((err = prb_read(rb, *seq, r, line_count))) { tail_seq = prb_first_seq(rb); if (*seq < tail_seq) { *seq = tail_seq; } else if (err == -ENOENT) { (*seq)++; } else { if (this_cpu_in_panic() && ((*seq + 1 ) < prb_next_reserve_seq(rb))) (*seq)++; else return false ; } } return true ; }
desc_update_last_finalized 将 @last_finalized_seq 更新为这些记录中的最新记录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static void desc_update_last_finalized (struct printk_ringbuffer *rb) { struct prb_desc_ring *desc_ring = &rb->desc_ring; u64 old_seq = desc_last_finalized_seq(rb); unsigned long oldval; unsigned long newval; u64 finalized_seq; u64 try_seq; try_again: finalized_seq = old_seq; try_seq = finalized_seq + 1 ; while (_prb_read_valid(rb, &try_seq, NULL , NULL )) { finalized_seq = try_seq; try_seq++; } if (finalized_seq == old_seq) return ; oldval = __u64seq_to_ulseq(old_seq); newval = __u64seq_to_ulseq(finalized_seq); if (!atomic_long_try_cmpxchg_release(&desc_ring->last_finalized_seq, &oldval, newval)) { old_seq = __ulseq_to_u64seq(rb, oldval); goto try_again; } }
prb_final_commit 将(以前保留的)数据提交并完成到环形缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 void prb_final_commit (struct prb_reserved_entry *e) { _prb_commit(e, desc_finalized); desc_update_last_finalized(e->rb); }
kernel/printk/printk_safe.c 安全打印 force_con 强制输出到控制台 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static atomic_t force_con;void printk_force_console_enter (void ) { atomic_inc (&force_con); } void printk_force_console_exit (void ) { atomic_dec (&force_con); } bool is_printk_force_console (void ) { return atomic_read (&force_con); }
kernel/printk/nbcon.c Non-Blocking Console 非阻塞控制台 nbcon_get_cpu_emergency_nesting 获取每个 CPU 的 nbcon 紧急嵌套计数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static DEFINE_PER_CPU (unsigned int , nbcon_pcpu_emergency_nesting) ;static unsigned int early_nbcon_pcpu_emergency_nesting __initdata;static __ref unsigned int *nbcon_get_cpu_emergency_nesting (void ) { if (!printk_percpu_data_ready()) return &early_nbcon_pcpu_emergency_nesting; return raw_cpu_ptr(&nbcon_pcpu_emergency_nesting); }
nbcon_get_default_prio 获取nbcon 默认优先级
初始化setup_log_buf之前为NBCON_PRIO_EMERGENCY
,之后为NBCON_PRIO_NORMAL
。
1 2 3 4 5 6 7 8 9 10 11 12 13 enum nbcon_prio nbcon_get_default_prio (void ) { unsigned int *cpu_emergency_nesting; if (this_cpu_in_panic()) return NBCON_PRIO_PANIC; cpu_emergency_nesting = nbcon_get_cpu_emergency_nesting(); if (*cpu_emergency_nesting) return NBCON_PRIO_EMERGENCY; return NBCON_PRIO_NORMAL; }
非阻塞控制台(nbcon)的所有权获取机制 此代码片段是Linux内核非阻塞控制台(nbcon)子系统的心脏 , 它实现了一套极其精巧、健壮、且分级的所有权获取(acquire)机制 。这套机制的根本原理是允许多个不同优先级、不同上下文(包括可休眠的、中断、NMI、系统恐慌等)的代码, 能够安全、高效地竞争同一个物理控制台(如UART)的”打印权” 。
这是一个为解决最极端并发情况而设计的微型状态机, 其核心是围绕一个原子的nbcon_state
变量进行转换, 以确保在任何时刻只有一个执行上下文可以向硬件写入数据。
核心概念: struct nbcon_state
这是一个打包在单个原子变量中的状态机。它包含了所有权的关键信息:
prio
: 当前所有者的优先级 (最高的是PANIC
, 最低的是NONE
)。
req_prio
: 请求 所有权的、更高优先级等待者的优先级。
unsafe
: 一个标志, 表示控制台硬件可能处于不一致的状态(例如, 在一个原子写操作的中间被抢占)。
cpu
: 当前所有者所在的CPU核心。
unsafe_takeover
: 一个标志, 表示发生过一次”不安全的强制接管”。
nbcon_context_try_acquire
: 所有权获取的总入口此函数是所有打印操作的入口点。它按顺序尝试三种不同的策略来获取控制台的所有权。
原理与工作流程: 它是一个三级瀑布模型: 尝试直接获取 -> 失败则尝试礼貌地接管 -> 再失败则尝试强制抢占 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static bool nbcon_context_try_acquire (struct nbcon_context *ctxt, bool is_reacquire) { struct console *con = ctxt->console; struct nbcon_state cur ; int err; nbcon_state_read(con, &cur); try_again: err = nbcon_context_try_acquire_direct(ctxt, &cur, is_reacquire); if (err != -EBUSY) goto out; err = nbcon_context_try_acquire_handover(ctxt, &cur); if (err == -EAGAIN) goto try_again; if (err != -EBUSY) goto out; err = nbcon_context_try_acquire_hostile(ctxt, &cur); out: if (err) return false ; if (atomic_read (&panic_cpu) == cpu) ctxt->pbufs = &panic_nbcon_pbufs; else ctxt->pbufs = con->pbufs; ctxt->seq = nbcon_seq_read(ctxt->console); return true ; }
策略 2: nbcon_context_try_acquire_handover
(礼貌的交接) 当直接获取因控制台繁忙而失败时, 此函数被调用。它适用于一个高优先级上下文需要从一个正在”不安全”区域内打印的低优先级上下文中接管控制台 的场景。
原理: “敲门并等待”。它不会粗暴地抢占, 而是先原子地设置req_prio
字段, 向当前所有者发出一个”交接请求”。然后它进入一个带超时的自旋等待循环, 等待当前所有者在退出”不安全”区域时检查到这个请求, 并主动释放所有权。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static int nbcon_context_try_acquire_handover (struct nbcon_context *ctxt, struct nbcon_state *cur) { new.atom = cur->atom; new.req_prio = ctxt->prio; if (!nbcon_state_try_cmpxchg(con, cur, &new)) return -EAGAIN; for (timeout = ctxt->spinwait_max_us; timeout >= 0 ; timeout--) { request_err = nbcon_context_try_acquire_requested(ctxt, cur); if (!request_err) return 0 ; if (request_err == -EPERM) break ; udelay(1 ); nbcon_state_read(con, cur); } return request_err; }
nbcon_context_try_acquire_requested
(交接的助手) : 此函数在handover
的等待循环中被反复调用。它检查当前所有者是否已经释放了锁(cur->prio == NBCON_PRIO_NONE
), 并且自己是否仍然是合法的等待者。如果条件满足, 它就执行最终的原子交换来获取所有权。
策略 3: nbcon_context_try_acquire_hostile
(强制抢占) 这是最后的、最极端的手段, 只有在系统panic
时才被允许。
原理: “破门而入”。它完全无视当前的所有者和状态, 使用一个do-while
循环来强行 用cmpxchg
将状态机设置为自己拥有。它会继承并设置unsafe
和unsafe_takeover
标志, 明确记录下这次粗暴的抢占, 并警告系统后续的打印可能是在一个不一致的硬件状态下进行的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static int nbcon_context_try_acquire_hostile (struct nbcon_context *ctxt, struct nbcon_state *cur) { if (!ctxt->allow_unsafe_takeover || WARN_ON_ONCE(ctxt->prio != NBCON_PRIO_PANIC)) return -EPERM; do { new.atom = cur->atom; new.cpu = cpu; new.prio = ctxt->prio; new.unsafe |= cur->unsafe_takeover; new.unsafe_takeover |= cur->unsafe; } while (!nbcon_state_try_cmpxchg(con, cur, &new)); return 0 ; }
printk
日志序列号的64位到32位转换与重建此代码片段展示了Linux内核printk
子系统中一个非常精巧的优化设计, 它解决了如何在只存储一个32位”进度标记”的情况下, 可靠地重建出完整的64位日志序列号的问题。
核心原理: 内核的主日志环形缓冲区(printk ring buffer
, prb
)使用一个永不回绕的64位序列号(u64seq
)来唯一标识每一条日志记录。然而, 为每一个控制台(console)都维护一个64位的原子变量来记录其打印进度是非常昂贵的, 尤其是在32位系统上。为了优化, 内核为每个非阻塞控制台(nbcon)只存储了一个32位的原子进度标记(ulseq
)。
此代码的核心原理就是基于一个”就近原则”的滑动窗口算法, 从一个32位的局部进度标记, 可靠地推断出它在64位全局序列号空间中的确切位置 。这个算法的基石是以下这个非常合理的假设: 任何一个控制台的处理进度, 相对于printk
主缓冲区的当前内容, 其滞后或超前的记录数都不会超过2^31 (约21亿条) 。
__ulseq_to_u64seq
: 32位到64位序列号的重建引擎这是实现上述原理的核心算法。它利用有符号和无符号整数算术的特性来巧妙地解决歧义。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 static inline u64 __ulseq_to_u64seq(struct printk_ringbuffer *rb, u32 ulseq){ u64 rb_first_seq = prb_first_seq(rb); u64 seq; seq = rb_first_seq - (s32)((u32)rb_first_seq - ulseq); return seq; }
算法解析: seq = rb_first_seq - (s32)((u32)rb_first_seq - ulseq);
(u32)rb_first_seq
: 取出缓冲区中最老记录的64位序列号的低32位 。
((u32)rb_first_seq - ulseq)
: 计算这两个32位无符号整数的差值。由于无符号算术的回绕特性, 这个差值正确地表示了它们之间的距离, 即使发生了32位的回绕。
(s32)(...)
: 这是最关键的一步 。它将上一步得到的无符号差值强制转换为一个有符号的32位整数 。
如果ulseq
稍微落后于rb_first_seq
的低32位, 这个差值会是一个很大的正数, 转换后会成为一个小的负数(代表”落后了少量”)。
如果ulseq
稍微领先于rb_first_seq
的低32位, 这个差值会是一个小的正数(代表”领先了少量”)。
rb_first_seq - (s32)(...)
: 从完整的64位基准序列号中减去这个有符号的32位”偏移量”。
减去一个负数等于加上一个正数, 这就将落后的序列号正确地向前调整。
减去一个正数, 这就将领先的序列号正确地向后调整。
通过这种方式, 函数总能找到距离rb_first_seq
“最近”的那个64位序列号, 其低32位与ulseq
完全匹配, 从而完成了精确的重建。
nbcon_seq_read
: 安全地读取控制台进度这是一个上层的API函数, 负责从一个给定的控制台安全地读取其32位的进度标记, 并调用重建函数将其转换为完整的64位序列号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 u64 nbcon_seq_read (struct console *con) { unsigned long nbcon_seq = atomic_long_read(&ACCESS_PRIVATE(con, nbcon_seq)); return __ulseq_to_u64seq(prb, nbcon_seq); }
其他辅助宏 1 2 3 4 5 6 7 8 9 10 11 #define __u64seq_to_ulseq(u64seq) ((u32)u64seq) #define ULSEQ_MAX(rb) __u64seq_to_ulseq(prb_first_seq(rb) + 0x80000000UL)
nbcon_context_release: 安全地释放非阻塞控制台的所有权 此函数是Linux内核非阻塞控制台(nbcon
)所有权机制的另一半, 是nbcon_context_try_acquire
的配对操作。它的核心原理是提供一个原子性的、有条件的、并且健壮的方法来 relinquishment (放弃) 一个先前获取到的控制台”打印权” 。
这个函数的设计核心是安全, 它必须能正确处理一种关键的并发情况: 在一个上下文正准备释放所有权时, 一个更高优先级的上下文(如NMI或系统恐慌)可能会突然”抢占”这个所有权。此函数通过一个比较并交换(Compare-and-Swap, CAS)的循环来优雅地处理这种情况。
工作流程详解:
读取当前状态 : 函数首先调用nbcon_state_read
来获取控制台当前最新的原子状态cur
。
进入CAS循环 : 函数进入一个do-while
循环。这个循环的目的是保证状态更新的原子性, 如果在循环体内, 当我们准备写入新状态时, 发现原子状态已经被其他上下文修改了, cmpxchg
就会失败, 循环会重试。
验证所有权 : 在循环内部, 最关键的第一步 是调用nbcon_owner_matches
。这个检查会验证”当前这个执行上下文(ctxt
)是否仍然是cur
状态所记录的那个合法所有者”。
如果不是(返回false
), 这意味着在我们读取cur
之后, 已经有一个更高优先级的上下文抢占了所有权。在这种情况下, 我们已经无权可放, 也没有必要再做任何操作, 函数会立即break
跳出循环。
准备新状态 : 如果所有权被验证, 函数会准备一个新的目标状态new
。
最重要的改变是 new.prio = NBCON_PRIO_NONE;
。这将优先级设置为”无所有者”, 从而向系统宣告该控制台现在可用了。
保留”不安全”标记 : 它会执行 new.unsafe |= cur.unsafe_takeover;
。这是一个关键的”污点”传播机制。如果此控制台之前经历过一次”不安全的强制接管”, unsafe_takeover
标志就会被设置。此代码确保了这个标志一旦被设置, 就会被永久地保留在unsafe
标志中。这意味着, 一个曾被粗暴对待过的控制台, 会被永久性地标记为”不安全”, 以防止后续的常规打印上下文在可能不一致的硬件状态上进行操作。
原子更新 : 函数调用nbcon_state_try_cmpxchg(con, &cur, &new)
来尝试原子地将控制台的状态从cur
更新为new
。只有当控制台的当前状态仍然是cur
时, 这个操作才会成功。
清理上下文 : 在循环结束后(无论是否成功更新), 函数会将上下文中的打印缓冲区指针pbufs
清空(ctxt->pbufs = NULL;
), 这是一个良好的实践, 可以防止调用者在此上下文被释放后, 仍然错误地通过它访问缓冲区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 static void nbcon_context_release (struct nbcon_context *ctxt) { unsigned int cpu = smp_processor_id(); struct console *con = ctxt->console; struct nbcon_state cur ; struct nbcon_state new ; nbcon_state_read(con, &cur); do { if (!nbcon_owner_matches(&cur, cpu, ctxt->prio)) break ; new.atom = cur.atom; new.prio = NBCON_PRIO_NONE; new.unsafe |= cur.unsafe_takeover; } while (!nbcon_state_try_cmpxchg(con, &cur, &new)); ctxt->pbufs = NULL ; }
nbcon_context_can_proceed
: 非阻塞控制台(nbcon)所有权检查与礼让机制此函数是Linux内核非阻塞控制台(nbcon
)子系统中实现”协作式多任务”(Cooperative Multitasking)的核心决策逻辑 。它在nbcon
打印流程的各个关键检查点被调用, 其核心原理是回答一个关键问题: “我这个当前的打印上下文, 是否还有权继续向前执行?” 。
这个函数不仅仅是一个简单的所有权检查, 它还内置了主动礼让(yield)给更高优先级等待者 的机制, 这是实现nbcon
在复杂抢占场景下既能高效工作又不会死锁的关键。
nbcon_context_can_proceed
: 核心决策逻辑原理与工作流程: 函数按照一个精心设计的决策树来判断是否可以继续:
基础所有权检查 : if (!nbcon_owner_matches(cur, cpu, ctxt->prio))
问题: “我还是不是记录在案的那个所有者?”
逻辑: 这是最基础的检查。如果nbcon_state
中记录的所有者CPU和优先级与当前上下文不匹配, 意味着所有权已经被(通常是更高优先级的)上下文强制抢占 了。此时必须立即返回false
, 表示”停止一切”。
无等待者检查 : if (cur->req_prio == NBCON_PRIO_NONE)
问题: “有人在等我吗?”
逻辑: 如果通过了所有权检查, 并且req_prio
字段为NONE
, 表示没有其他上下文正在请求”交接”(handover)。在这种最常见的情况下, 当前所有者可以安全地继续执行, 函数返回true
。
“不安全区域”特权 : if (cur->unsafe)
问题: “我是否正处于一个’不安全’的操作序列中?”
逻辑: 即使有更高优先级的上下文在等待(req_prio != NONE
), 如果当前所有者正处于”不安全区域”(cur->unsafe == true
), 它也被允许继续执行 。
原理: 这是为了防止活锁(livelock)。如果不允许在不安全区内继续, 那么一个高优先级请求者可能会导致低优先级所有者永远无法完成其原子操作序列(例如, 无法完成一次完整的日志发射), 从而永远无法退出不安全区来释放锁。此规则保证了当前所有者至少能完成其最小的原子工作单元。当它尝试退出不安全区时(nbcon_context_exit_unsafe
), can_proceed
会被再次调用, 届时它将因为cur->unsafe
为false
而进入下面的礼让逻辑。
主动礼让 (Yielding) :
场景: 如果代码执行到这里, 意味着: (a)我们是合法所有者, (b)有更高优先级的上下文在等待, (c)我们当前处于”安全”状态。
决策: 这是”礼貌”的体现。在这种情况下, 我们不应该 继续执行任何耗时的操作。函数会主动调用nbcon_context_release(ctxt)
来释放自己持有的锁 , 为更高优先级的等待者让路。
返回值: 在主动释放锁之后, 函数返回false
。这告知调用者”你不再拥有所有权, 必须中止当前操作并从头开始”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 static bool nbcon_context_can_proceed (struct nbcon_context *ctxt, struct nbcon_state *cur) { unsigned int cpu = smp_processor_id(); if (!nbcon_owner_matches(cur, cpu, ctxt->prio)) return false ; if (cur->req_prio == NBCON_PRIO_NONE) return true ; if (cur->unsafe) return true ; WARN_ON_ONCE(cur->req_prio <= cur->prio); nbcon_context_release(ctxt); return false ; }
nbcon_can_proceed
: 便捷的API封装这是一个上层的、导出的API函数, 它为nbcon
的使用者(主要是驱动的回调函数)提供了一个简洁的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 bool nbcon_can_proceed (struct nbcon_write_context *wctxt) { struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); struct console *con = ctxt->console; struct nbcon_state cur ; nbcon_state_read(con, &cur); return nbcon_context_can_proceed(ctxt, &cur); } EXPORT_SYMBOL_GPL(nbcon_can_proceed);
nbcon
上下文的”不安全区域”进出机制此代码片段揭示了Linux内核非阻塞控制台(nbcon
)子系统中一个极其精细的内部同步原语。它的核心原理是为nbcon
的打印操作定义一个”不安全区域”(unsafe section)的边界, 并提供一个原子性的、可被抢占的进入和退出该区域的方法 。
“不安全区域”是什么? 在nbcon_emit_next_record
函数中, 从获取下一条日志记录开始, 到最终调用驱动的write
回调函数并将日志输出到硬件为止, 这个过程构成了一个关键的操作序列。在这个序列的执行过程中, 硬件的状态(例如UART的FIFO填充状态)和软件的状态(例如指向缓冲区的指针)可能是暂时不一致的。如果这个序列在中间被打断(被更高优先级的上下文抢占), 那么整个控制台就处于一个”不安全”的状态。
nbcon_context_enter_unsafe
和nbcon_context_exit_unsafe
这两个宏所封装的__nbcon_context_update_unsafe
函数, 就如同这个”不安全区域”的”门卫”。它不仅负责开关门(设置/清除状态位), 更重要的是, 它在开关门的每一步都会检查是否有人(更高优先级的上下文)正在”敲门”要求进入。
__nbcon_context_update_unsafe
: 核心实现此函数是这个”门卫”的核心逻辑。它负责原子地更新nbcon_state
中的unsafe
位, 并且在每一步都检查当前上下文是否仍然有权继续操作。
原理与工作流程: 它的核心是一个带有抢占检查的比较并交换(Compare-and-Swap, CAS)循环 。
读取当前状态 : 函数首先获取控制台最新的原子状态cur
。
CAS循环 : 进入一个do-while
循环以保证更新的原子性。
“污点”检查 (Latch Mechanism) : if (!unsafe && cur.unsafe_takeover)
这是进入循环后的第一道检查。它实现了一个”污点”或”闩锁”机制。
!unsafe
为真意味着我们正尝试退出 不安全区(即调用exit_unsafe
)。
但如果cur.unsafe_takeover
为真(表示此控制台曾被粗暴地强制接管过), 那么此函数拒绝清除unsafe
标志 。
原理: 一旦发生过强制接管, 控制台的硬件状态就可能已永久性地损坏或不一致, 内核会将其永久标记为”不安全”, 不再允许任何常规的打印操作进入, 以免造成更大的破坏。
抢占检查 : if (!nbcon_context_can_proceed(ctxt, &cur))
这是最关键的抢占检测点。在尝试修改状态之前, 它会检查: “我这个上下文, 是否仍然是合法的所有者, 并且没有更高优先级的上下文正在请求交接?”
如果can_proceed
返回false
, 意味着所有权已经被(或即将被)抢占。函数必须立即返回false
, 通知调用者(“你已经丢失所有权, 马上停止你正在做的一切!”)。
原子更新 : 如果通过了所有检查, 函数会准备一个新的状态new
, 其中new.unsafe
被设置为期望的值(进入时为true
, 退出时为false
), 然后调用nbcon_state_try_cmpxchg
尝试原子地更新状态。如果失败(因为状态被其他上下文改变了), CAS循环会重试。
最终检查 : 即使成功更新了unsafe
位, 在函数返回前, 它再一次 调用nbcon_context_can_proceed
并返回其结果。这提供了双重保险, 确保即使在CAS操作成功的瞬间, 所有权也未被抢占。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static bool __nbcon_context_update_unsafe(struct nbcon_context *ctxt, bool unsafe){ struct console *con = ctxt->console; struct nbcon_state cur ; struct nbcon_state new ; nbcon_state_read(con, &cur); do { if (!unsafe && cur.unsafe_takeover) goto out; if (!nbcon_context_can_proceed(ctxt, &cur)) return false ; new.atom = cur.atom; new.unsafe = unsafe; } while (!nbcon_state_try_cmpxchg(con, &cur, &new)); cur.atom = new.atom; out: return nbcon_context_can_proceed(ctxt, &cur); }
便捷宏封装 这两个宏的作用是提供清晰、易读的API, 让调用代码(nbcon_emit_next_record
)的意图一目了然, 就像使用锁一样。
1 2 3 4 5 6 7 8 9 10 #define nbcon_context_enter_unsafe(c) __nbcon_context_update_unsafe(c, true) #define nbcon_context_exit_unsafe(c) __nbcon_context_update_unsafe(c, false)
printk_get_next_message: printk
环形缓冲区的格式化读取器 此函数是Linux内核日志系统(printk
)的核心组成部分, 它的根本原理是充当一个从内核主日志环形缓冲区(printk ring buffer
, prb
)到各个控制台驱动程序之间的”格式化桥梁” 。它负责根据请求, 安全地从环形缓冲区中取出下一条有效的原始日志记录, 并根据不同的需求(如是否为扩展格式、是否需要过滤日志级别)将其转换成一段人类可读的、准备发送到硬件的字符串。
这是一个纯粹的软件逻辑函数, 不涉及任何硬件访问, 但它实现了printk
流程中几个至关重要的功能:
详细工作流程与原理:
缓冲区策略 (Buffer Strategy) : 函数首先根据is_extended
标志决定其工作缓冲区。
标准格式 (is_extended
为false
) : 它直接将环形缓冲区中的原始日志文本读入最终的输出缓冲区(outbuf
)。后续的格式化(如添加时间戳)将以**原地(in-place)**的方式完成。
扩展格式 (is_extended
为true
) : 由于扩展格式的头部信息更复杂, 为了避免在构建头部时覆盖原始文本, 它首先将原始日志文本读入一个临时的”草稿缓冲区”(scratchbuf
), 然后再将格式化的头部和来自草稿缓冲区的文本组合成最终结果, 放入outbuf
中。
读取并处理间隙 (prb_read_valid
) : 这是核心的数据检索步骤。它调用prb_read_valid
并传入一个期望的序列号seq
。
原理: prb_read_valid
非常健壮, 它会尝试读取序列号为seq
的记录。如果seq
指向的记录因为环形缓冲区被覆盖而已经不存在了, 它会自动向前搜索并返回第一条仍然有效的记录 。
如果没有任何有效的记录可读, 函数返回false
, 表示读取结束。
计算丢弃数 (pmsg->dropped
) : pmsg->dropped = r.info->seq - seq;
这是一个非常精妙的计算。它用实际读到的记录序列号 减去期望读取的序列号 , 其差值就精确地等于因为缓冲区被覆盖而丢失的记录数量。nbcon_emit_next_record
等上层函数会利用这个值来打印[... dropped ...]
信息。
日志级别过滤 (suppress_message_printing
) : 如果may_suppress
标志为真(意味着调用者允许过滤), 并且记录本身没有被标记为LOG_FORCE_CON
(强制输出到控制台), 函数就会调用suppress_message_printing
来检查记录的日志级别是否低于当前控制台配置的日志级别。如果需要被过滤, 函数会直接goto out
, 导致最终输出的字符串长度为0, 从而有效地跳过了这条消息。
格式化输出 : 如果消息未被过滤, 函数会根据is_extended
标志, 调用相应的格式化函数(info_print_ext_header
, msg_print_ext_body
, record_print_text
)来构建最终的输出字符串, 包括时间戳、日志级别、设备信息和消息正文。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 bool printk_get_next_message (struct printk_message *pmsg, u64 seq, bool is_extended, bool may_suppress) { struct printk_buffers *pbufs = pmsg->pbufs; const size_t scratchbuf_sz = sizeof (pbufs->scratchbuf); const size_t outbuf_sz = sizeof (pbufs->outbuf); char *scratchbuf = &pbufs->scratchbuf[0 ]; char *outbuf = &pbufs->outbuf[0 ]; struct printk_info info ; struct printk_record r ; size_t len = 0 ; bool force_con; if (is_extended) prb_rec_init_rd(&r, &info, scratchbuf, scratchbuf_sz); else prb_rec_init_rd(&r, &info, outbuf, outbuf_sz); if (!prb_read_valid(prb, seq, &r)) return false ; pmsg->seq = r.info->seq; pmsg->dropped = r.info->seq - seq; force_con = r.info->flags & LOG_FORCE_CON; if (!force_con && may_suppress && suppress_message_printing(r.info->level)) goto out; if (is_extended) { len = info_print_ext_header(outbuf, outbuf_sz, r.info); len += msg_print_ext_body(outbuf + len, out_sz - len, &r.text_buf[0 ], r.info->text_len, &r.info->dev_info); } else { len = record_print_text(&r, console_msg_format & MSG_FORMAT_SYSLOG, printk_time); } out: pmsg->outbuf_len = len; return true ; }
nbcon_emit_next_record: 发射单条非阻塞控制台(nbcon)日志记录 此函数是Linux内核非阻塞控制台(nbcon
)子系统中负责实际打印工作的引擎 。它的核心原理是在一个已获取所有权的上下文中, 安全地从主printk
环形缓冲区中取出下一条日志记录, 对其进行必要的格式化(如添加”丢弃”或”重放”标记), 然后调用底层控制台驱动提供的.write_atomic()
或.write_thread()
回调函数将其发送到硬件, 最后再安全地更新控制台的打印进度 。
这是一个极其健壮和复杂的函数, 因为它被设计为在任何时刻都可能被更高优先级的上下文”抢占”打印权。因此, 它的每一步操作都充满了防御性检查, 以确保它不会在丢失所有权后继续进行任何操作。
工作流程详解:
进入”不安全”区域 & 获取消息 : 函数首先调用nbcon_context_enter_unsafe()
。这会检查是否有更高优先级的上下文请求交接, 如果有, 函数会立即放弃并返回false
。如果安全, 它会调用printk_get_next_message()
从printk
环形缓冲区中获取下一条格式化的日志记录。如果缓冲区中没有更多可处理的记录, 它会正常退出并返回true
。
添加”丢弃”标记 : 如果printk
核心为了到达下一条有效记录而跳过了一些损坏或丢失的记录, 或者此控制台本身之前就丢弃过消息, 此函数会计算出总共丢弃的数量, 并调用console_prepend_dropped()
在当前要打印的消息前加上一个类似"[... 42 dropped messages ...]
的前缀。这对于用户理解日志的不连续性至关重要。
处理”重放”情况 : 这是一个精妙的容错机制。如果一个低优先级的打印上下文被高优先级的抢占, 打印了一条消息, 然后低优先级上下文又重新获得了所有权, 它可能会尝试打印同一条消息。为了避免重复输出, 函数会检查当前要打印的记录序列号是否与”上一个被打印的序列号”(nbcon_prev_seq
)相同。如果相同, 它会调用console_prepend_replay()
为消息加上"[replay]"
前缀, 而不是重复打印完整消息。
调用驱动回调 : 在完成所有前缀添加后, 函数会根据上下文(use_atomic
)决定是调用驱动提供的原子write_atomic()
函数, 还是可休眠的write_thread()
函数, 将最终的日志内容传递给驱动程序, 由驱动程序负责将其写入物理硬件(如UART的FIFO)。
所有权丢失检测 : 在调用驱动之后, 它会检查wctxt->outbuf
是否被驱动清空。这是驱动向nbcon
核心发信号的一种方式, 表示驱动在write
回调内部自己检测到所有权丢失并重新获取了它。如果发生这种情况, 此函数也会认为所有权已丢失, 并立即中止。
更新状态 : 如果消息被成功发送(或被跳过), 函数会再次进入一个”不安全”区域, 来原子地更新此控制台的两个关键状态:
重置con->dropped
计数器, 因为丢弃信息已经被打印出去了。
调用nbcon_seq_try_update()
将控制台的进度标记(nbcon_seq
)更新为下一条记录的序列号。这是整个流程中最重要的”推进”步骤。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 static bool nbcon_emit_next_record (struct nbcon_write_context *wctxt, bool use_atomic) { struct nbcon_context *ctxt = &ACCESS_PRIVATE(wctxt, ctxt); struct console *con = ctxt->console; bool is_extended = console_srcu_read_flags(con) & CON_EXTENDED; struct printk_message pmsg = { .pbufs = ctxt->pbufs, }; unsigned long con_dropped; struct nbcon_state cur ; unsigned long dropped; unsigned long ulseq; if (WARN_ON_ONCE((use_atomic && !con->write_atomic) || !(console_srcu_read_flags(con) & CON_NBCON))) { nbcon_context_release(ctxt); return false ; } if (!nbcon_context_enter_unsafe(ctxt)) return false ; ctxt->backlog = printk_get_next_message(&pmsg, ctxt->seq, is_extended, true ); if (!ctxt->backlog) return nbcon_context_exit_unsafe(ctxt); con_dropped = data_race(READ_ONCE(con->dropped)); dropped = con_dropped + pmsg.dropped; if (dropped && !is_extended) console_prepend_dropped(&pmsg, dropped); ulseq = atomic_long_read(&ACCESS_PRIVATE(con, nbcon_prev_seq)); if (__ulseq_to_u64seq(prb, ulseq) == pmsg.seq) { console_prepend_replay(&pmsg); } else { nbcon_state_read(con, &cur); if (!nbcon_context_can_proceed(ctxt, &cur)) return false ; atomic_long_try_cmpxchg(&ACCESS_PRIVATE(con, nbcon_prev_seq), &ulseq, __u64seq_to_ulseq(pmsg.seq)); } if (!nbcon_context_exit_unsafe(ctxt)) return false ; if (pmsg.outbuf_len == 0 ) goto update_con; nbcon_write_context_set_buf(wctxt, &pmsg.pbufs->outbuf[0 ], pmsg.outbuf_len); if (use_atomic) con->write_atomic(con, wctxt); else con->write_thread(con, wctxt); if (!wctxt->outbuf) { nbcon_context_release(ctxt); return false ; } update_con: if (!nbcon_context_enter_unsafe(ctxt)) return false ; if (dropped != con_dropped) { WRITE_ONCE(con->dropped, dropped); } nbcon_seq_try_update(ctxt, pmsg.seq + 1 ); return nbcon_context_exit_unsafe(ctxt); }
非阻塞控制台(nbcon)的原子刷新: 单个控制台实现 此代码片段深入到Linux内核非阻塞控制台(nbcon)原子刷新机制的最底层 。它包含了负责刷新单个 指定控制台的核心逻辑。其根本原理是一个高度健壮、具备所有权协商和循环处理能力的微型状态机, 专门用于在不可休眠的原子上下文中, 将日志从内核缓冲区驱动到硬件 。
这套机制是为应对最严苛的系统环境(如panic
, NMI)而设计的, 因此其复杂性体现在对所有权、并发和日志追赶问题的处理上。
__nbcon_atomic_flush_pending_con
: 核心执行逻辑此函数是实际执行刷新工作的”引擎”。它尝试获取控制台的所有权, 然后在一个循环中逐条记录地输出日志。
原理与工作流程:
上下文初始化 : 创建并初始化一个nbcon_write_context
。这个结构体包含了刷新操作所需的所有状态, 如目标控制台、优先级、超时等。
获取所有权 : 调用nbcon_context_try_acquire
尝试获取该控制台的”打印权”。这是一个非阻塞的锁获取 操作。如果失败(返回false
), 意味着另一个上下文(可能是更高优先级的NMI, 或另一个CPU核心)正在使用此控制台, 函数会立即返回-EPERM
(权限错误), 表示本次尝试失败。
打印循环 : 如果成功获取所有权, 函数进入一个while
循环, 只要当前控制台的处理进度还没达到目标序列号stop_seq
, 循环就会继续。
单条记录发射 : 在循环内部, 它调用nbcon_emit_next_record
。这个函数负责:
从printk
环形缓冲区读取下一条可用的日志记录。
调用控制台驱动提供的.write_atomic()
回调函数, 将记录内容发送到硬件。
如果在执行过程中, 打印权被更高优先级的上下文”抢占”(takeover)了, nbcon_emit_next_record
会返回false
。此时, 循环必须终止, 函数返回-EAGAIN
, 告知上层”请重试或放弃, 因为其他人接管了”。
处理”空洞” : 如果printk
缓冲区中没有更多可读的记录了(!ctxt->backlog
), 但我们仍未达到目标序列号stop_seq
, 这意味着有其他CPU核心预留 了一个日志槽位但还没有完全写入数据 。在原子上下文中, 我们绝不能等待它完成。因此, 函数会设置-ENOENT
(无此条目)错误并跳出循环。
释放所有权 : 无论循环是如何退出的(成功完成、被抢占、遇到空洞), 最后都必须调用nbcon_context_release
来释放之前获取的打印权。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 static int __nbcon_atomic_flush_pending_con(struct console *con, u64 stop_seq, bool allow_unsafe_takeover) { struct nbcon_write_context wctxt = { }; struct nbcon_context *ctxt = &ACCESS_PRIVATE(&wctxt, ctxt); int err = 0 ; ctxt->console = con; ctxt->spinwait_max_us = 2000 ; ctxt->prio = nbcon_get_default_prio(); ctxt->allow_unsafe_takeover = allow_unsafe_takeover; if (!nbcon_context_try_acquire(ctxt, false )) return -EPERM; while (nbcon_seq_read(con) < stop_seq) { if (!nbcon_emit_next_record(&wctxt, true )) return -EAGAIN; if (!ctxt->backlog) { if (nbcon_seq_read(con) < stop_seq) err = -ENOENT; break ; } } nbcon_context_release(ctxt); return err; }
nbcon_atomic_flush_pending_con
: 带安全封装和重试逻辑的包装器此函数是__nbcon_atomic_flush_pending_con
的一个包装器, 它增加了两个至关重要的特性: 中断安全 和日志追赶(tail-chasing) 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 static void nbcon_atomic_flush_pending_con (struct console *con, u64 stop_seq, bool allow_unsafe_takeover) { struct console_flush_type ft ; unsigned long flags; int err; again: local_irq_save(flags); err = __nbcon_atomic_flush_pending_con(con, stop_seq, allow_unsafe_takeover); local_irq_restore(flags); if (err) return ; printk_get_console_flush_type(&ft); if (!ft.nbcon_offload && prb_read_valid(prb, nbcon_seq_read(con), NULL )) { stop_seq = prb_next_reserve_seq(prb); goto again; } }
非阻塞控制台(nbcon)的原子刷新机制 此代码片段展示了Linux内核中一种现代化、高性能的控制台输出机制——非阻塞控制台(Non-Blocking Console, nbcon)的原子刷新功能。它的核心原理是 提供一种在原子上下文(atomic context, 如中断处理程序、持有自旋锁的代码、系统恐慌panic等)中, 能够安全、快速地将内核日志缓冲区中的待处理消息(backlog)强制输出到硬件的方法 。
这与传统的、可能休眠的控制台输出机制形成鲜明对比。在紧急情况下(如系统即将崩溃), 内核不能调用任何可能导致睡眠的函数, 因此需要一套完全不同的、”原子”的输出路径。nbcon
为此而生, 它要求控制台驱动程序提供一个特殊的write_atomic()
回调函数, 这个函数必须保证在任何情况下都不会休眠。
nbcon_atomic_flush_pending
系列函数就是这个机制的触发器。它不适用于常规日志输出, 而是专门为内核的紧急路径(如kmsg_dump
, panic()
)设计的。
__nbcon_atomic_flush_pending
: 核心实现这是执行原子刷新的底层工作函数。它负责遍历所有已注册的控制台, 筛选出符合条件的非阻塞控制台, 并对它们逐一执行刷新操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 static void __nbcon_atomic_flush_pending(u64 stop_seq, bool allow_unsafe_takeover){ struct console *con ; int cookie; cookie = console_srcu_read_lock(); for_each_console_srcu(con) { short flags = console_srcu_read_flags(con); if (!(flags & CON_NBCON)) continue ; if (!console_is_usable(con, flags, true )) continue ; if (nbcon_seq_read(con) >= stop_seq) continue ; nbcon_atomic_flush_pending_con(con, stop_seq, allow_unsafe_takeover); } console_srcu_read_unlock(cookie); }
nbcon_atomic_flush_pending
: 便捷的API封装这是一个上层的、导出的API函数, 它为内核的紧急路径提供了一个简洁的调用接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 void nbcon_atomic_flush_pending (void ) { __nbcon_atomic_flush_pending(prb_next_reserve_seq(prb), false ); }
kernel/printk/printk.c 内核消息打印 oops_in_progress 指示当前是否正在处理内核错误 oops_in_progress
是 Linux 内核中的一个全局变量,用于指示当前是否正在处理内核 “oops”(内核错误)。以下是对其功能和用途的详细解释:
内核 “oops” 的概念 :
在 Linux 内核中,”oops” 是一种严重的错误,通常表示内核代码中发生了非法操作,例如访问无效内存地址或触发了断言失败。
当发生 “oops” 时,内核会打印错误信息(通过 printk
),记录问题的详细信息,并尝试继续运行(如果可能的话),而不是立即崩溃。
oops_in_progress
的作用 :
oops_in_progress
是一个标志变量,用于指示当前是否正在处理 “oops” 错误。
当内核开始处理 “oops” 时,会将 oops_in_progress
设置为非零值,表示系统处于错误处理状态。
在错误处理完成后,oops_in_progress
会被重置为零。
防止递归错误 :
在处理 “oops” 的过程中,内核可能会调用其他函数(例如日志记录或调试工具)。如果这些函数再次触发错误,可能会导致递归 “oops” 或系统崩溃。
通过检查 oops_in_progress
的状态,内核可以避免在处理 “oops” 时再次触发某些操作,从而防止递归错误的发生。
典型使用场景 :
oops_in_progress
通常与内核日志记录(printk
)和调试机制配合使用。在记录 “oops” 信息时,内核会检查该标志,以决定是否需要限制某些操作。
例如,如果系统正在处理 “oops”,某些非关键的日志可能会被跳过,以避免进一步的干扰。
与系统稳定性相关 :
oops_in_progress
的存在使得内核在处理严重错误时能够更好地控制系统行为,尽量避免系统完全崩溃。
在某些情况下,内核可能会尝试继续运行,即使发生了 “oops”。这对于嵌入式系统或关键任务系统尤为重要,因为它们可能需要尽量保持运行状态。
总的来说,oops_in_progress
是 Linux 内核中一个关键的全局变量,用于管理和协调 “oops” 错误的处理过程。它在提高系统稳定性和防止递归错误方面起到了重要作用。
printk_delay 当CONFIG_BOOT_PRINTK_DELAY配置时 每条消息延时打印配置
当CONFIG_BOOT_PRINTK_DELAY配置时 每条消息延时打印配置
1 2 3 4 5 6 7 8 9 10 11 12 13 static inline void printk_delay (int level) { boot_delay_msec(level); if (unlikely(printk_delay_msec)) { int m = printk_delay_msec; while (m--) { mdelay(1 ); touch_nmi_watchdog(); } } }
__printk_recursion_counter 返回指向调用方 CPU 上下文的专用计数器的指针
根据当前 CPU 是否处于 NMI(非屏蔽中断)上下文来选择使用哪个递归计数器。
根据当前是否完成了 printk
的每 CPU 数据准备来选择使用哪个递归计数器。
1 2 3 4 5 6 7 8 9 10 11 12 13 static u8 *__printk_recursion_counter(void ){ #ifdef CONFIG_HAVE_NMI if (in_nmi()) { if (printk_percpu_data_ready()) return this_cpu_ptr(&printk_count_nmi); return &printk_count_nmi_early; } #endif if (printk_percpu_data_ready()) return this_cpu_ptr(&printk_count); return &printk_count_early; }
printk_enter_irqsave 屏蔽中断并检查递归调用次数判断是否允许打印,失败不会禁用
检测递归调用的次数,超过PRINTK_MAX_RECURSION
则返回false。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #define PRINTK_MAX_RECURSION 3 #define printk_enter_irqsave(recursion_ptr, flags) \ ({ \ bool success = true; \ \ typecheck(u8 *, recursion_ptr); \ local_irq_save(flags); \ (recursion_ptr) = __printk_recursion_counter(); \ if (*(recursion_ptr) > PRINTK_MAX_RECURSION) { \ local_irq_restore(flags); \ success = false ; \ } else { \ (*(recursion_ptr))++; \ } \ success; \ })
printk_parse_prefix 提取日志级别或控制标志。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 u16 printk_parse_prefix (const char *text, int *level, enum printk_info_flags *flags) { u16 prefix_len = 0 ; int kern_level; while (*text) { kern_level = printk_get_level(text); if (!kern_level) break ; switch (kern_level) { case '0' ... '7' : if (level && *level == LOGLEVEL_DEFAULT) *level = kern_level - '0' ; break ; case 'c' : if (flags) *flags |= LOG_CONT; } prefix_len += 2 ; text += 2 ; } return prefix_len; }
printk_ringbuffer 静态 动态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #define LOG_ALIGN __alignof__(unsigned long) #define __LOG_BUF_LEN (1 << CONFIG_LOG_BUF_SHIFT) #define LOG_BUF_LEN_MAX ((u32)1 << 31) static char __log_buf[__LOG_BUF_LEN] __aligned(LOG_ALIGN);static char *log_buf = __log_buf;#define PRB_AVGBITS 5 #if CONFIG_LOG_BUF_SHIFT <= PRB_AVGBITS #error CONFIG_LOG_BUF_SHIFT value too small. #endif _DEFINE_PRINTKRB(printk_rb_static, CONFIG_LOG_BUF_SHIFT - PRB_AVGBITS, PRB_AVGBITS, &__log_buf[0 ]); static struct printk_ringbuffer printk_rb_dynamic ;struct printk_ringbuffer *prb = &printk_rb_static;
truncate_msg 截断消息
log_buf中仅有一半的缓冲区可用
如果发生截断,则在消息末尾添加<truncated>
,启用警告消息(如果有空间).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define MAX_LOG_TAKE_PART 4 static const char trunc_msg[] = "<truncated>" ;static void truncate_msg (u16 *text_len, u16 *trunc_msg_len) { u32 max_text_len = log_buf_len / MAX_LOG_TAKE_PART; if (*text_len > max_text_len) *text_len = max_text_len; *trunc_msg_len = strlen (trunc_msg); if (*text_len >= *trunc_msg_len) *text_len -= *trunc_msg_len; else *trunc_msg_len = 0 ; }
vprintk_store 打印消息到环形缓冲区 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 int vprintk_store (int facility, int level, const struct dev_printk_info *dev_info, const char *fmt, va_list args) { struct prb_reserved_entry e ; enum printk_info_flags flags = 0 ; struct printk_record r ; unsigned long irqflags; u16 trunc_msg_len = 0 ; char prefix_buf[8 ]; u8 *recursion_ptr; u16 reserve_size; va_list args2; u32 caller_id; u16 text_len; int ret = 0 ; u64 ts_nsec; if (!printk_enter_irqsave(recursion_ptr, irqflags)) return 0 ; ts_nsec = local_clock(); caller_id = printk_caller_id(); va_copy(args2, args); reserve_size = vsnprintf(&prefix_buf[0 ], sizeof (prefix_buf), fmt, args2) + 1 ; va_end(args2); if (reserve_size > PRINTKRB_RECORD_MAX) reserve_size = PRINTKRB_RECORD_MAX; if (facility == 0 ) printk_parse_prefix(&prefix_buf[0 ], &level, &flags); if (level == LOGLEVEL_DEFAULT) level = default_message_loglevel; if (dev_info) flags |= LOG_NEWLINE; if (is_printk_force_console()) flags |= LOG_FORCE_CON; if (flags & LOG_CONT) { prb_rec_init_wr(&r, reserve_size); if (prb_reserve_in_last(&e, prb, &r, caller_id, PRINTKRB_RECORD_MAX)) { text_len = printk_sprint(&r.text_buf[r.info->text_len], reserve_size, facility, &flags, fmt, args); r.info->text_len += text_len; if (flags & LOG_FORCE_CON) r.info->flags |= LOG_FORCE_CON; if (flags & LOG_NEWLINE) { r.info->flags |= LOG_NEWLINE; prb_final_commit(&e); } else { prb_commit(&e); } ret = text_len; goto out; } } prb_rec_init_wr(&r, reserve_size); if (!prb_reserve(&e, prb, &r)) { truncate_msg(&reserve_size, &trunc_msg_len); prb_rec_init_wr(&r, reserve_size + trunc_msg_len); if (!prb_reserve(&e, prb, &r)) goto out; } text_len = printk_sprint(&r.text_buf[0 ], reserve_size, facility, &flags, fmt, args); if (trunc_msg_len) memcpy (&r.text_buf[text_len], trunc_msg, trunc_msg_len); r.info->text_len = text_len + trunc_msg_len; r.info->facility = facility; r.info->level = level & 7 ; r.info->flags = flags & 0x1f ; r.info->ts_nsec = ts_nsec; r.info->caller_id = caller_id; if (dev_info) memcpy (&r.info->dev_info, dev_info, sizeof (r.info->dev_info)); if (!(flags & LOG_NEWLINE)) prb_commit(&e); else prb_final_commit(&e); ret = text_len + trunc_msg_len; out: printk_exit_irqrestore(recursion_ptr, irqflags); return ret; }
vprintk_emit 打印消息发送 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 asmlinkage int vprintk_emit (int facility, int level, const struct dev_printk_info *dev_info, const char *fmt, va_list args) { struct console_flush_type ft ; int printed_len; if (unlikely(suppress_printk)) return 0 ; if (other_cpu_in_panic() && !panic_triggering_all_cpu_backtrace) return 0 ; printk_get_console_flush_type(&ft); if (level == LOGLEVEL_SCHED) { level = LOGLEVEL_DEFAULT; ft.legacy_offload |= ft.legacy_direct; ft.legacy_direct = false ; } printk_delay(level); printed_len = vprintk_store(facility, level, dev_info, fmt, args); if (ft.nbcon_atomic) nbcon_atomic_flush_pending(); if (ft.nbcon_offload) nbcon_kthreads_wake(); if (ft.legacy_direct) { preempt_disable(); if (console_trylock_spinning()) console_unlock(); preempt_enable(); } if (ft.legacy_offload) defer_console_output(); else wake_up_klogd(); return printed_len; } EXPORT_SYMBOL(vprintk_emit);
printk 内核日志打印函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__) #define printk_deferred(fmt, ...) \ printk_index_wrap(_printk_deferred, fmt, ##__VA_ARGS__) int vprintk_deferred (const char *fmt, va_list args) { return vprintk_emit(0 , LOGLEVEL_SCHED, NULL , fmt, args); } int _printk_deferred(const char *fmt, ...){ va_list args; int r; va_start(args, fmt); r = vprintk_deferred(fmt, args); va_end(args); return r; }
ignore_loglevel
ignore_loglevel
是一个布尔变量,用于指示是否忽略内核的日志级别设置。
如果设置为 true,则内核将忽略日志级别设置,并将所有内核消息打印到控制台。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 static bool __read_mostly ignore_loglevel;static int __init ignore_loglevel_setup (char *str) { ignore_loglevel = true ; pr_info("debug: ignoring loglevel setting.\n" ); return 0 ; } early_param("ignore_loglevel" , ignore_loglevel_setup); module_param(ignore_loglevel, bool , S_IRUGO | S_IWUSR); MODULE_PARM_DESC(ignore_loglevel, "ignore loglevel setting (prints all kernel messages to the console)" );
—————-控制台——————————————- suppress_message_printing 禁止打印消息
suppress_message_printing
函数用于检查是否应该禁止打印消息。
如果当前的日志级别大于等于 console_loglevel
且 ignore_loglevel
为假,则返回 true,表示禁止打印消息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #define MESSAGE_LOGLEVEL_DEFAULT CONFIG_MESSAGE_LOGLEVEL_DEFAULT #define CONSOLE_LOGLEVEL_DEFAULT CONFIG_CONSOLE_LOGLEVEL_DEFAULT #define CONSOLE_LOGLEVEL_QUIET CONFIG_CONSOLE_LOGLEVEL_QUIET #define console_loglevel (console_printk[0]) #define default_message_loglevel (console_printk[1]) #define minimum_console_loglevel (console_printk[2]) #define default_console_loglevel (console_printk[3]) int console_printk[4 ] = { CONSOLE_LOGLEVEL_DEFAULT, MESSAGE_LOGLEVEL_DEFAULT, CONSOLE_LOGLEVEL_MIN, CONSOLE_LOGLEVEL_DEFAULT, }; EXPORT_SYMBOL_GPL(console_printk); static bool suppress_message_printing (int level) { return (level >= console_loglevel && !ignore_loglevel); }
console_list_lock console_list_unlock 控制台列表锁定和解锁 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 void console_list_lock (void ) { WARN_ON_ONCE(debug_lockdep_rcu_enabled() && srcu_read_lock_held(&console_srcu)); mutex_lock(&console_mutex); } EXPORT_SYMBOL(console_list_lock); void console_list_unlock (void ) { mutex_unlock(&console_mutex); } EXPORT_SYMBOL(console_list_unlock);
try_enable_default_console 尝试无条件启用控制台 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 static int console_call_setup (struct console *newcon, char *options) { int err; if (!newcon->setup) return 0 ; console_lock(); err = newcon->setup(newcon, options); console_unlock(); return err; } static void try_enable_default_console (struct console *newcon) { if (newcon->index < 0 ) newcon->index = 0 ; if (console_call_setup(newcon, NULL ) != 0 ) return ; newcon->flags |= CON_ENABLED; if (newcon->device) newcon->flags |= CON_CONSDEV; }
try_enable_preferred_console 尝试启用首选控制台
控制台没初始化时,跳过匹配
控制台使能且不需要用户指定,返回0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 static int try_enable_preferred_console (struct console *newcon, bool user_specified) { struct console_cmdline *c ; int i, err; for (i = 0 , c = console_cmdline; i < MAX_CMDLINECONSOLES && (c->name[0 ] || c->devname[0 ]); i++, c++) { if (!c->name[0 ]) continue ; if (c->user_specified != user_specified) continue ; if (!newcon->match || newcon->match(newcon, c->name, c->index, c->options) != 0 ) { BUILD_BUG_ON(sizeof (c->name) != sizeof (newcon->name)); if (strcmp (c->name, newcon->name) != 0 ) continue ; if (newcon->index >= 0 && newcon->index != c->index) continue ; if (newcon->index < 0 ) newcon->index = c->index; if (_braille_register_console(newcon, c)) return 0 ; err = console_call_setup(newcon, c->options); if (err) return err; } newcon->flags |= CON_ENABLED; if (i == preferred_console) newcon->flags |= CON_CONSDEV; return 0 ; } if (newcon->flags & CON_ENABLED && c->user_specified == user_specified) return 0 ; return -ENOENT; }
get_init_console_seq 返回新注册的控制台的起始序列号
引导控制台直接使用 @syslog_seq 作为起始序列号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 static u64 get_init_console_seq (struct console *newcon, bool bootcon_registered) { struct console *con ; bool handover; u64 init_seq; if (newcon->flags & (CON_PRINTBUFFER | CON_BOOT)) { mutex_lock(&syslog_lock); init_seq = syslog_seq; mutex_unlock(&syslog_lock); } else { init_seq = prb_next_seq(prb); if (bootcon_registered && !keep_bootcon) { console_lock(); if (!console_flush_all(true , &init_seq, &handover)) { if (handover) console_lock(); init_seq = prb_next_seq(prb); for_each_console(con) { u64 seq; if (!(con->flags & CON_BOOT) || !(con->flags & CON_ENABLED)) { continue ; } if (con->flags & CON_NBCON) seq = nbcon_seq_read(con); else seq = con->seq; if (seq < init_seq) init_seq = seq; } } console_unlock(); } } return init_seq; }
register_console 注册控制台 unregister_console_locked 注销控制台 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 static int unregister_console_locked (struct console *console) ;void register_console (struct console *newcon) { bool use_device_lock = (newcon->flags & CON_NBCON) && newcon->write_atomic; bool bootcon_registered = false ; bool realcon_registered = false ; struct console *con ; unsigned long flags; u64 init_seq; int err; console_list_lock(); for_each_console(con) { if (WARN(con == newcon, "console '%s%d' already registered\n" , con->name, con->index)) { goto unlock; } if (con->flags & CON_BOOT) bootcon_registered = true ; else realcon_registered = true ; } if ((newcon->flags & CON_BOOT) && realcon_registered) { pr_info("Too late to register bootconsole %s%d\n" , newcon->name, newcon->index); goto unlock; } if (newcon->flags & CON_NBCON) { if (!nbcon_alloc(newcon)) goto unlock; } if (preferred_console < 0 ) { if (hlist_empty(&console_list) || !console_first()->device || console_first()->flags & CON_BOOT) { try_enable_default_console(newcon); } } err = try_enable_preferred_console(newcon, true ); if (err == -ENOENT) err = try_enable_preferred_console(newcon, false ); if (err || newcon->flags & CON_BRL) { if (newcon->flags & CON_NBCON) nbcon_free(newcon); goto unlock; } if (bootcon_registered && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV)) { newcon->flags &= ~CON_PRINTBUFFER; } newcon->dropped = 0 ; init_seq = get_init_console_seq(newcon, bootcon_registered); if (newcon->flags & CON_NBCON) { have_nbcon_console = true ; nbcon_seq_force(newcon, init_seq); } else { have_legacy_console = true ; newcon->seq = init_seq; } if (newcon->flags & CON_BOOT) have_boot_console = true ; if (use_device_lock) newcon->device_lock(newcon, &flags); if (hlist_empty(&console_list)) { newcon->flags |= CON_CONSDEV; hlist_add_head_rcu(&newcon->node, &console_list); } else if (newcon->flags & CON_CONSDEV) { console_srcu_write_flags(console_first(), console_first()->flags & ~CON_CONSDEV); hlist_add_head_rcu(&newcon->node, &console_list); } else { hlist_add_behind_rcu(&newcon->node, console_list.first); } if (use_device_lock) newcon->device_unlock(newcon, flags); console_sysfs_notify(); con_printk(KERN_INFO, newcon, "enabled\n" ); if (bootcon_registered && ((newcon->flags & (CON_CONSDEV | CON_BOOT)) == CON_CONSDEV) && !keep_bootcon) { struct hlist_node *tmp ; hlist_for_each_entry_safe(con, tmp, &console_list, node) { if (con->flags & CON_BOOT) unregister_console_locked(con); } } printk_kthreads_check_locked(); unlock: console_list_unlock(); } EXPORT_SYMBOL(register_console); static int unregister_console_locked (struct console *console) { bool use_device_lock = (console->flags & CON_NBCON) && console->write_atomic; bool found_legacy_con = false ; bool found_nbcon_con = false ; bool found_boot_con = false ; unsigned long flags; struct console *c ; int res; lockdep_assert_console_list_lock_held(); con_printk(KERN_INFO, console, "disabled\n" ); res = _braille_unregister_console(console); if (res < 0 ) return res; if (res > 0 ) return 0 ; if (!console_is_registered_locked(console)) res = -ENODEV; else if (console_is_usable(console, console->flags, true )) __pr_flush(console, 1000 , true ); console_srcu_write_flags(console, console->flags & ~CON_ENABLED); if (res < 0 ) return res; if (use_device_lock) console->device_lock(console, &flags); hlist_del_init_rcu(&console->node); if (use_device_lock) console->device_unlock(console, flags); if (!hlist_empty(&console_list) && console->flags & CON_CONSDEV) console_srcu_write_flags(console_first(), console_first()->flags | CON_CONSDEV); synchronize_srcu(&console_srcu); if (console->flags & CON_NBCON) nbcon_free(console); console_sysfs_notify(); if (console->exit ) res = console->exit (console); for_each_console(c) { if (c->flags & CON_BOOT) found_boot_con = true ; if (c->flags & CON_NBCON) found_nbcon_con = true ; else found_legacy_con = true ; } if (!found_boot_con) have_boot_console = found_boot_con; if (!found_legacy_con) have_legacy_console = found_legacy_con; if (!found_nbcon_con) have_nbcon_console = found_nbcon_con; printk_kthreads_check_locked(); } int unregister_console (struct console *console) { int res; console_list_lock(); res = unregister_console_locked(console); console_list_unlock(); return res; } EXPORT_SYMBOL(unregister_console);
setup_log_buf 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 void __init setup_log_buf (int early) { struct printk_info *new_infos ; unsigned int new_descs_count; struct prb_desc *new_descs ; struct printk_info info ; struct printk_record r ; unsigned int text_size; size_t new_descs_size; size_t new_infos_size; unsigned long flags; char *new_log_buf; unsigned int free ; u64 seq; if (!early) set_percpu_data_ready(); if (log_buf != __log_buf) return ; if (!early && !new_log_buf_len) log_buf_add_cpu(); if (!new_log_buf_len) { if (!early) goto out; return ; } out: print_log_buf_usage_stats(); }
console_init 1 2 3 0x00000000c02a34bc __con_initcall_start = . *(.con_initcall.init) 0x00000000c02a34bc __con_initcall_end = .
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 void __init console_init (void ) { int ret; initcall_t call; initcall_entry_t *ce; n_tty_init(); ce = __con_initcall_start; trace_initcall_level("console" ); while (ce < __con_initcall_end) { call = initcall_from_entry(ce); trace_initcall_start(call); ret = call(); trace_initcall_finish(call, ret); ce++; } }
printk_set_kthreads_ready 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 static void printk_kthreads_check_locked (void ) { lockdep_assert_console_list_lock_held(); if (!printk_kthreads_ready) return ; if (have_legacy_console || have_boot_console) { if (!printk_legacy_kthread && force_legacy_kthread() && !legacy_kthread_create()) { hlist_for_each_entry_safe(con, tmp, &console_list, node) { if (con->flags & CON_NBCON) continue ; unregister_console_locked(con); } } } else if (printk_legacy_kthread) { kthread_stop(printk_legacy_kthread); printk_legacy_kthread = NULL ; } if (have_boot_console || !have_nbcon_console) { printk_kthreads_running = false ; return ; } if (printk_kthreads_running) return ; hlist_for_each_entry_safe(con, tmp, &console_list, node) { if (!(con->flags & CON_NBCON)) continue ; if (!nbcon_kthread_create(con)) unregister_console_locked(con); } printk_kthreads_running = true ; } static int __init printk_set_kthreads_ready (void ) { register_syscore_ops(&printk_syscore_ops); console_list_lock(); printk_kthreads_ready = true ; printk_kthreads_check_locked(); console_list_unlock(); return 0 ; } early_initcall(printk_set_kthreads_ready);
pr_flush: 同步并等待内核日志输出 此函数 (pr_flush
) 及其核心实现 (__pr_flush
) 提供了一个关键的同步机制, 用于确保所有先前由printk
产生的内核日志消息, 已经被系统中所有可用的控制台(console)驱动程序实际处理并发送出去 。
printk
本身是一个非常快速的操作, 它只是将日志数据放入一个内核环形缓冲区(printk ring buffer
)中。而真正将这些数据输出到物理设备(如UART串口、显示器、网络控制台)的任务, 是由独立的控制台驱动在后台异步完成的。此函数的核心原理就是作为一个同步点, 阻塞当前的执行流程, 直到所有控制台驱动的处理进度都”追上”了调用pr_flush
时的日志缓冲区的位置 。
这在系统即将关机或崩溃的时刻至关重要。例如, 在kernel_power_off
中调用pr_flush
, 是为了保证在物理断电前, 像”Powering down…”这样的最后一条日志消息以及任何相关的错误信息, 能够被真实地从UART等接口发送出去, 而不是永远地丢失在内存缓冲区中, 这对于事后调试至关重要。
__pr_flush
: 核心实现此函数包含了等待所有控制台追上进度的核心循环逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progress){ unsigned long timeout_jiffies = msecs_to_jiffies(timeout_ms); unsigned long remaining_jiffies = timeout_jiffies; struct console_flush_type ft ; struct console *c ; u64 last_diff = 0 ; u64 printk_seq; short flags; int cookie; u64 diff; u64 seq; if (system_state < SYSTEM_SCHEDULING) return false ; might_sleep(); seq = prb_next_reserve_seq(prb); printk_get_console_flush_type(&ft); if (ft.nbcon_atomic) nbcon_atomic_flush_pending(); if (ft.legacy_direct) { console_lock(); console_unlock(); } for (;;) { unsigned long begin_jiffies; unsigned long slept_jiffies; diff = 0 ; console_lock(); cookie = console_srcu_read_lock(); for_each_console_srcu(c) { if (con && con != c) continue ; flags = console_srcu_read_flags(c); if (!console_is_usable(c, flags, true ) && !console_is_usable(c, flags, false )) { continue ; } if (flags & CON_NBCON) { printk_seq = nbcon_seq_read(c); } else { printk_seq = c->seq; } if (printk_seq < seq) diff += seq - printk_seq; } console_srcu_read_unlock(cookie); if (diff != last_diff && reset_on_progress) remaining_jiffies = timeout_jiffies; console_unlock(); if (diff == 0 || remaining_jiffies == 0 ) break ; begin_jiffies = jiffies; msleep(1 ); slept_jiffies = jiffies - begin_jiffies; remaining_jiffies -= min(slept_jiffies, remaining_jiffies); last_diff = diff; } return (diff == 0 ); }
pr_flush
: 便捷的API封装这是一个上层的、导出的API函数, 它为内核其他部分的调用者提供了一个更简洁的接口。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 bool pr_flush (int timeout_ms, bool reset_on_progress) { return __pr_flush(NULL , timeout_ms, reset_on_progress); }
console_prepend_message
: 在日志消息前插入文本此代码片段展示了Linux内核printk
日志系统中一个非常实用的内部工具函数: console_prepend_message
。它的核心原理是提供一个安全、高效的方法, 在一段已经存在的日志消息字符串之前, 插入一个新的、格式化的前缀字符串 。
这个函数是实现printk
高级功能的基石, 例如打印”[… dropped messages …]”或”[replay]”等提示信息。它通过一个巧妙的内存操作序列来完成工作, 而不是代价高昂的字符串拼接。
console_prepend_message
: 核心实现工作流程与原理:
格式化前缀 : 函数首先像printf
一样工作。它接受一个格式化字符串fmt
和可变参数...
, 并使用vscnprintf
将格式化后的前缀字符串生成到一个临时的”草稿缓冲区”(scratchbuf
)中。vscnprintf
是vsprintf
的安全版本, 它能防止缓冲区溢出。
空间检查与截断 : 这是保证缓冲区安全的关键步骤。
它首先检查前缀的长度len
加上一个最大的可能前缀(用于应对极端情况)是否会超出整个输出缓冲区outbuf
的大小。如果会, 这是一个严重的配置错误, 函数会打印一个警告并直接返回。
接着, 它检查现有消息的长度pmsg->outbuf_len
加上前缀的长度len
是否会超出输出缓冲区。如果会, 这意味着没有足够的空间同时容纳前缀和完整的原始消息。在这种情况下, 它会截断(truncate)原始消息 , 从尾部缩短它, 以便为前缀腾出空间。它确保了在截断后, 原始消息仍然是以\0
结尾的合法字符串。
内存移动 (memmove
) : 这是整个函数最高效、最核心的操作。它调用memmove
, 将outbuf
中现有的整个日志消息(包括其结尾的\0
)向后移动len
个字节 。memmove
是一个可以安全处理源和目标内存区域重叠的内存复制函数, 这正是这里所需的情景。执行后, outbuf
的开头就空出了len
个字节的”空隙”。
前缀拷贝 (memcpy
) : 最后, 它调用memcpy
将之前在scratchbuf
中生成的前缀字符串, 拷贝到outbuf
开头刚刚腾出的”空隙”中。
更新长度 : 它将前缀的长度len
加到pmsg->outbuf_len
上, 以正确反映新的、更长的消息的总长度。
通过这种”移动-拷贝”的方式, console_prepend_message
避免了需要分配新内存或进行多次字符串操作的传统拼接方法, 实现了非常高效的字符串前插功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 __printf(2 , 3 ) static void console_prepend_message (struct printk_message *pmsg, const char *fmt, ...) { struct printk_buffers *pbufs = pmsg->pbufs; const size_t scratchbuf_sz = sizeof (pbufs->scratchbuf); const size_t outbuf_sz = sizeof (pbufs->outbuf); char *scratchbuf = &pbufs->scratchbuf[0 ]; char *outbuf = &pbufs->outbuf[0 ]; va_list args; size_t len; va_start(args, fmt); len = vscnprintf(scratchbuf, scratchbuf_sz, fmt, args); va_end(args); if (WARN_ON_ONCE(len + PRINTK_PREFIX_MAX >= outbuf_sz)) return ; if (pmsg->outbuf_len + len >= outbuf_sz) { pmsg->outbuf_len = outbuf_sz - (len + 1 ); outbuf[pmsg->outbuf_len] = 0 ; } memmove(outbuf + len, outbuf, pmsg->outbuf_len + 1 ); memcpy (outbuf, scratchbuf, len); pmsg->outbuf_len += len; }
console_prepend_dropped
: 便捷的API封装这是一个上层的、具体的应用函数。它使用console_prepend_message
来生成一条关于丢弃消息的特定前缀。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 void console_prepend_dropped (struct printk_message *pmsg, unsigned long dropped) { console_prepend_message(pmsg, "** %lu printk messages dropped **\n" , dropped); }
printk
日志文本格式化与前缀注入此代码片段展示了Linux内核printk
日志系统中负责最终文本格式化 的两个核心内部函数。它们的作用是将从环形缓冲区中取出的、”原始”的日志记录, 转换为最终将在控制台上显示的、带有时间戳和日志级别等前缀信息的、并且正确处理了多行消息的字符串。
record_print_text
是这个过程的”引擎”, 而info_print_prefix
是它的”零件供应商”。
info_print_prefix
: 生成单行前缀此函数是一个简单、专一的辅助函数。它的原理是根据传入的参数, 将日志记录的元数据(metadata)格式化成一个标准的前缀字符串 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 static size_t info_print_prefix (const struct printk_info *info, bool syslog, bool time, char *buf) { size_t len = 0 ; if (syslog) len = print_syslog((info->facility << 3 ) | info->level, buf); if (time) len += print_time(info->ts_nsec, buf + len); len += print_caller(info->caller_id, buf + len); if (IS_ENABLED(CONFIG_PRINTK_CALLER) || time) { buf[len++] = ' ' ; buf[len] = '\0' ; } return len; }
record_print_text
: 核心的”原地”文本格式化引擎这是整个格式化流程中最复杂、最精妙的部分。它的核心原理是在一个单一的缓冲区内, 通过一系列高效的memmove
操作, 实现为多行日志的每一行都正确地注入前缀, 同时优雅地处理缓冲区空间不足时的截断问题 。这种”原地”(in-place)操作避免了为格式化分配额外内存的开销, 这在资源受限的内核环境中至关重要。
工作流程与原理:
生成标准前缀 : 函数首先调用info_print_prefix
生成一个本次日志所有行都将使用的标准前缀(如"<6>[ 123.456789] "
), 并将其存储在一个临时的栈上缓冲区prefix
中。
进入多行处理循环 (for(;;)
): 函数的核心是一个循环, 它将日志文本视为一个或多个由\n
分隔的行。
查找换行符 : next = memchr(text, '\n', text_len);
它在剩余的文本中查找下一个换行符, 以确定当前行的边界和长度(line_len
)。
空间检查与截断 : 在处理每一行之前, 它都会进行一次精确的空间计算, 检查”已格式化长度 + 前缀长度 + 剩余文本长度 + 结尾换行符 + 结尾\0”是否会超出缓冲区。
如果会, 它会截断(truncate)剩余文本的长度 (text_len
), 仅保留足以容纳当前行和前缀的空间。truncated
标志被设置, 循环将在处理完这最后一行后终止。
这个检查保证了缓冲区绝对不会溢出。
“原地”前缀注入 (memmove
/memcpy
) : 这是最关键的算法。 a. memmove(text + prefix_len, text, text_len);
: 它将整个剩余的文本 (包括多行)向后移动prefix_len
个字节, 在当前行的开头腾出一个与前缀等长的”空隙”。 b. memcpy(text, prefix, prefix_len);
: 它将之前生成的标准前缀拷贝到这个”空隙”中。
更新指针和长度 : 函数会更新已格式化总长度len
, 并将text
指针移动到下一行的起始位置, 同时从text_len
中减去已处理的长度, 准备处理下一行。
处理结尾 : 当循环结束时(无论是正常处理完所有行还是因为截断而提前终止):
添加尾部换行符 : vprintk_store
在存储日志时会去掉用户输入的最后一个\n
, 以便内部处理。此函数负责在所有行都被处理完后, 将这个\n
重新添加回来, 保证日志输出的格式正确。
添加字符串终结符 : 最后, 它在已格式化文本的末尾写入一个\0
, 确保它是一个合法的C字符串。
在STM32H750上的意义:
这个函数对于确保在STM32的串口或其他控制台上看到的日志格式正确、可读、且信息完整至关重要。
时间戳和日志级别 : 开发者通过配置内核, 可以选择是否显示时间戳和syslog级别。这个函数就是将这些配置转化为实际输出的地方。在调试实时性问题时, 精确到纳秒的时间戳(info->ts_nsec
)是无价的。
处理多行日志 : 嵌入式开发中, 经常需要打印包含多行的数据结构或状态信息。这个函数保证了即使是多行消息, 每一行的开头都会有正确的前缀, 保持了日志的可读性。
内存效率 : “原地”操作的算法设计, 对于像STM32这样内存资源相对宝贵的MCU来说, 避免了不必要的动态内存分配, 降低了内存碎片风险, 提高了系统的稳定性和效率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 static size_t record_print_text (struct printk_record *r, bool syslog, bool time) { size_t text_len = r->info->text_len; size_t buf_size = r->text_buf_size; char *text = r->text_buf; char prefix[PRINTK_PREFIX_MAX]; bool truncated = false ; size_t prefix_len; size_t line_len; size_t len = 0 ; char *next; if (text_len > buf_size) text_len = buf_size; prefix_len = info_print_prefix(r->info, syslog, time, prefix); for (;;) { next = memchr (text, '\n' , text_len); if (next) { line_len = next - text; } else { if (truncated) break ; line_len = text_len; } if (len + prefix_len + text_len + 1 + 1 > buf_size) { if (len + prefix_len + line_len + 1 + 1 > buf_size) break ; text_len = buf_size - len - prefix_len - 1 - 1 ; truncated = true ; } memmove(text + prefix_len, text, text_len); memcpy (text, prefix, prefix_len); len += prefix_len + line_len + 1 ; if (text_len == line_len) { text[prefix_len + line_len] = '\n' ; break ; } text += prefix_len + line_len + 1 ; text_len -= line_len + 1 ; } if (buf_size > 0 ) r->text_buf[len] = 0 ; return len; }