首页/新闻资讯/正文详情

深入理解printk:内核日志级别、缓冲机制与调试实战

发布时间:2026/9/25 14:34:49 来源:云帆数科 栏目:资讯中心
深入理解printk:内核日志级别、缓冲机制与调试实战
第一次写内核模块时我对着屏幕里的黑框敲了半天printk(hello, kernel\n)编进模块插入内核结果终端上一个字都没蹦出来。转而敲dmesg | tail那条消息才懒洋洋地躺在最后一行。那会儿我才意识到内核打印不是printf换个名字那么简单背后藏着一整套日志级别、缓冲区和控制台机制。搞懂printk的工作流程和级别控制不只是为了“看清楚日志”更是排查驱动问题、理解内核启动过程的必修课。这篇文章把我这些年积累的东西一次性写出来从打印路径、级别映射到实际调试中反复踩过的坑希望对正在啃内核源码或写驱动的你有点用。1. printk为什么存在从printf到内核日志系统的第一步1.1 用户态printf与内核printk的本质差异printf是C标准库的函数它把格式化的数据写到标准输出流背后依赖文件描述符和文件系统层。内核里根本没有 libc也没有“标准输出”的概念。内核运行在CPU的特权级它要输出信息必须自己实现一套机制。printk就是内核自己的格式化输出函数它不依赖文件句柄直接往内核内部的环形缓冲区写数据同时根据日志级别决定是否把消息同步到当前控制台设备上。一个关键差异是上下文环境。用户程序调用printf时可以放心地执行内存分配、加锁、睡眠因为这些操作在进程上下文里是合法的。但内核里很多调用点处于中断上下文、原子上下文或者底半部处理环境调用可能睡眠的函数会直接导致系统崩溃比如“BUG: scheduling while atomic”或者内核死锁。printk被设计成在这些环境里也能干活核心原理是它尽量不睡眠写入环形缓冲区时使用自旋锁保护然后把往控制台输出这种事情放到合适时机处理。这也就解释了为什么内核开发者遇事不决就printk——它是少数几个在极端环境下还能安全执行的操作之一。1.2 打印到底去了哪里ring buffer、dmesg与/dev/kmsgprintk写入的环形缓冲区通常叫kernel message ring buffer。它有固定大小是一个只进内存的循环结构。较新内核比如5.10之后把这个缓冲区设计成每一条消息有独立的struct printk_record包含时间戳、消息长度、级别、调用者信息等。老内核则是一条连续字节流用游标分割。用户态查日志常用dmesg它本质上是从/dev/kmsg或/proc/kmsg读取内核环形缓冲区内容。/dev/kmsg是新的字符设备接口支持按记录读取、写入、清除/proc/kmsg是老接口只能顺序读取读到的不支持回退。多数现代发行版推荐的journalctl -k就是把/dev/kmsg的事件捕获后落盘到 systemd journal。这里有一个新手容易误解的点控制台没显示的日志不代表内核没记录。只要消息成功写进环形缓冲区你都能在dmesg里翻到。控制台只是“投影”了部分消息而已。1.3 为什么“看不见”就对了控制台、串口和不可见缓冲区一台服务器通常有多个“控制台”候选显示终端VT、串口、远程管理口、虚拟控制台。内核通过console启动参数指定显式控制台比如consolettyS0,115200表示把串口当作主控制台。还有consoletty0表示显示终端。如果没有特别指定内核会使用最后一个注册的控制台作为默认控制台。关键是控制台输出有开销尤其是串口115200波特率下每秒钟也就打印十几KB。如果把所有printk都实时推送到串口打印风暴会直接把系统拖死。所以内核只把“达到控制台日志级别”的消息发送给控制台设备其余消息仍然在环形缓冲区里安静待着。你看不到它们不是丢了而是被级别过滤掉了。2. 日志级别解密数字、宏定义与printf级别的控制链路2.1 八个级别从宏到数字在内核源码的include/linux/kern_levels.h里定义了从KERN_EMERG到KERN_DEBUG八个日志级别。严格说加上KERN_DEFAULT和KERN_CONT算是另外两种修饰宏定义数值含义典型场景KERN_EMERG0系统崩溃级别panic、不可用硬件KERN_ALERT1必须立即处理损坏的RAM、主板资源耗尽KERN_CRIT2严重错误驱动发生严重错误KERN_ERR3错误条件设备IO失败KERN_WARNING4警告降级运行、硬件奇偶校验KERN_NOTICE5正常但重要设备检测到、网络链路upKERN_INFO6信息级别驱动登记的Banner信息KERN_DEBUG7调试信息函数入口、变量值调用printk时级别其实加载在格式串的最前面比如printk(KERN_ERR something failed\n)——注意级别宏和格式串之间没有逗号因为它们在编译期就是相邻字符串编译器自动拼接。这个设计容易让刚接触源码的开发者看懵但是想通了就只佩服这种技巧宏本质上是\001 3这样的字符串前缀最终拼进同一个字符串。2.2 console_loglevel的默认值和启动参数的调整控制台只显示数值上小于等于console_loglevel的消息。默认值是CONFIG_CONSOLE_LOGLEVEL_DEFAULT通常为 7有些发行版编译内核时改成了 4。也就是说正常情况下KERN_DEBUG7级别的消息在控制台看不到但KERN_INFO6及更高级别的都能看到。启动参数里有几个关键开关loglevelN把console_loglevel直接设为 N例如loglevel4。quiet等效于设loglevel4并且会隐藏大部分启动阶段的 banner。debug等效于loglevel8把级别提到比DEBUG还高此时所有日志都会出现在控制台。ignore_loglevel无视级别所有printk都往控制台打。这个参数非常危险生产环境慎用串口直接刷屏系统性能会被拖垮。还有一个容易忽略的initcall_debug它不是简单提高级别而是让内核在初始化每一个 initcall驱动、子系统的初始化函数时都打印进入和退出信息排查启动挂死很有用。2.3 运行时控制/proc/sys/kernel/printk四个字段系统跑起来之后你可以通过/proc/sys/kernel/printk动态调整。它包含四个整数7 4 1 7四个字段的含义分别是字段含义说明1console_loglevel当前控制台可显示的最高消息级别2default_message_loglevel没指定级别时printk使用的默认级别默认 43minimum_console_loglevel控制台级别可被设置的最小值4default_console_loglevel默认控制台级别想查看当前值cat /proc/sys/kernel/printk想临时把控制台级别调到只显示KERN_WARNING以上的echo 3 4 1 7 /proc/sys/kernel/printk调试完改回来echo 7 4 1 7 /proc/sys/kernel/printk这个动态控制在不重启的情况下切换调试状态非常方便写驱动的朋友应该把它刻在脑子里。3. 一次printk的完整旅程从调用点到控制台的路径3.1 消息如何进入环形缓冲区printk最终会调用核心函数vprintk_emit。整个过程大致如下用local_irq_save保存中断状态并禁用本地中断防止嵌套打印时产生竞争。加锁早期内核用logbuf_lock现代内核引入无锁环形缓冲区设计后逻辑变了但本质都是保护系统记录结构。格式化输出到临时缓冲区。把临时缓冲区的记录追加到struct printk_ringbufferkernel/printk/ringbuffer.c。根据级别判断是否需要唤醒console线程来刷新控制台。解锁并恢复中断状态。一个重要的设计变化从5.15开始比较明显printk内核线程化。早期版本是printk在调用者上下文中直接同步调用控制台驱动输出后来为了避免同一个CPU在禁用中断时被慢速串口拖住引入了专门的打印线程printkkthread内核进程[kthread/...]也就是说消息先入环形缓冲区再由打印线程异步送到控制台设备。这样做的好处是即使串口速度很慢也不会让某个写驱动的CPU长时间卡在打印路径里。3.2 原子上下文与安全为什么中断里敢用printk中断上下文里能用printk靠的是“不睡眠 不长期持有锁”的设计。消息入环形缓冲区时使用自旋锁或无锁算法自旋较短不会主动让出CPU。但这里有一个安全隐患控制台驱动本身可能并不是原子安全的。比如串口驱动在输出字符时需要等待发送 FIFO 空可能要用忙等待轮询udelay这会占用CPU时间但不睡眠一般不会引发调度器错误。在实际工程中我不建议在中断处理函数里做大量printk。环境允许、逻辑可行不代表性能可接受。中断里打印上百条日志会产生不可预测的延迟还可能触发自锁问题。记住一个原则中断里记录关键事件把详细打印放到进程上下文或 workqueue 里。3.3 延迟打印、同步打印与早期的boot输出这里要区分两个概念printk_deferred和普通printk。printk_deferred是把消息放进一个延迟队列在后续的调度时机统一打印。它用于特别敏感的上下文比如CPU热插拔、时间中断里。普通printk则尽量即时送到控制台。不过前面也提到现代内核里普通printk也可能被打印线程异步处理完全同步的控制台输出主要出现在启动早期或 panic 路径。早期启动阶段串口控制台还没注册、内存管理、调度器还没初始化的时候printk也照样能把消息记录下来但你可能看不到。内核处理这个问题的机制是earlycon和earlyprink在正式控制台驱动注册前用极简的早期控制台直接操作串口寄存器输出。这就是为什么你在调试 U-Boot 引导内核时内核启动早期的一堆打印能出现在串口上——那往往是早期控制台输出的而不是标准控制台。4. 日志级别带来的真实坑boot阶段、init进程与console_may_schedule4.1 完全没输出boot太早、level filter、未实现console driver我排查过很多“内核日志空白”的案例最常见的三个原因值得单独列出来。第一个是启动太早而 earlycon 没配置。在setup_arch之前的代码也有打印比如汇编阶段的putc但那属于体系结构专用代码不会经过printk。你如果没有任何早期控制台能看到的第一条日志往往是在内存管理初始化之后的printk输出的。这时候改启动参数加earlyconuart8250,mmio32,0x...能看到更多早期信息。第二个是日志级别设太低。启动参数里如果带了quiet或loglevel3KERN_INFO级别的驱动装载信息全被吞掉。串口上干干净净但dmesg里其实有。这种“看似没输出”的情况最坑因为它不是系统没打印只是你选择的观察窗口不对。第三个是控制台驱动没匹配到实际硬件。比如你的产品是串口调试但内核默认只注册了 VT 控制台串口控制台没注册或者注册了但引脚复用被别的驱动抢了。这种情况不管日志级别怎么调串口永远不出字。排查思路是先用dmesg看有没有printk: console [ttyS0] enabled这条信息。没有的话说明串口控制台压根没启用。4.2 打印风暴控制台被刷死系统像冻住一样一个让我印象深刻的故障是驱动里放了一个全局错误处理函数里面有printk(KERN_ERR ...)。结果某个中断风暴期间每秒触发几万次串口输出跟不上console_lock卡住然后整个系统感觉像停顿了几秒钟。用top看%sys居高不下用户的交互基本无响应。这种情况下内核提供了一系列限速策略printk_ratelimited()限速打印默认在10秒内最多打印10条再多就直接丢弃。pr_err_ratelimited()、pr_info_ratelimited()带级别的限速版本。dev_warn_ratelimited()等dev_*系列也有限速版本。如果限速还不够升级做法是直接调低console_loglevel让控制台不刷新这些批量错误环形缓冲区里的记录还在事后dmesg慢慢看。日志级别不只是给人看的也是性能保护机制。4.3 看到级别不一致不同内核版本、不同配置的差异有朋友问我为什么自己编译的内核里printk(KERN_ERR)在控制台看到了换成发行版内核同样代码却没输出。问题往往不在代码而在发行版默认的console_loglevel。Debian/Ubuntu 的 grub 配置里经常带quietFedora/RHEL 也可能是quietrhgb。这些参数会把控制台级别压到4以下。KERN_ERR是3按说能显示但有些厂商改动启动脚本在启动初期把/proc/sys/kernel/printk覆盖得更低。所以排查时要先敲一下cat /proc/sys/kernel/printk拿实际运行值说话而不是看源码默认值。另一个不一致来自配置项CONFIG_PRINTK。如果你为追求极致体积把CONFIG_PRINTK关掉那printk直接变成空函数任何日志都不会记录。这种内核极其罕见但我在嵌入式裁剪环境里真见过——为了省空间把日志系统砍了出了问题才后悔。4.4 死锁风险panic时、锁上下文里的printkprintk本身有锁。如果在某个路径上你持有一把锁 A然后调用printk而控制台输出路径也想拿同一个锁 A就可能自锁。更经典的场景是中断里调用printk打印过程中又来了更高优先级的中断再次调用printk如果二次中断和第一次中断在打印路径上竞争同一个console锁就可能死锁。现代内核引入了console_may_schedule、printk_safe等机制缓解这些问题。NMI里打印甚至还有独立的printk_nmi缓冲。但在写驱动时我的建议很朴素不要在中断处理程序里做复杂打印真要打印就带超时标记和限速别打多条拼接的长日志。panic 路径上的日志输出是另外一回事内核会把消息写到专门的 panic 缓冲区那部分的优先级完全不一样。5. 进阶打印工程化pr_fmt、dev_xxx与%p系列扩展5.1 pr_xxx系列 vs 裸printk工程化选择大量驱动代码里你会看到pr_info、pr_err、pr_warn而很少看到裸printk。原因不仅是它们更好读、级别更明确还因为在文件顶部定义一个宏之后所有调试打印都会统一加上前缀。#define pr_fmt(fmt) KBUILD_MODNAME : fmt #include linux/printk.h pr_info(probe called\n);pr_fmt是一个特殊的宏pr_xxx系列函数会把它悄悄拼在格式串前面。比如驱动模块名叫mx_driver那上面那条日志实际输出就像mx_driver: probe called这个前缀对多模块系统排查问题极其重要。dmesg里日志成百上千条没有模块名前缀你根本无法快速筛选出自己驱动打印的内容。5.2 格式化占位符和dev_xxx的好处如果你写的是平台驱动或设备驱动我更推荐直接使用dev_info(pdev-dev, ...)、dev_err(dev, ...)这类接口。它们的好处是自动附带设备名、驱动名、甚至挂载总线的信息。dev_err(client-dev, I2C transaction failed at %d\n, ret);输出的日志会带类似i2c-0: I2C transaction failed at -5这样的上下文信息。dmesg | grep i2c-0一下就能把所有属于该设备的日志捞出来这比靠时间戳靠猜靠谱得多。printk还支持一大堆%p扩展显示符号比如%pS打印函数符号名 地址偏移%ps只打印函数符号名%pB处理 backtrace 时的符号解析%pOF打印设备树节点路径struct device_node *%p4cc打印四字符编码常用于 DRM 调色板调试我在追踪内核崩溃栈时喜欢把偏移地址用%pS输出就能直接看到当前指令落在哪个函数里大大的加速了与 System.map 对照的过程。5.3 rate limiting与动态调试开关内核还有一个强大的dynamic debug动态调试机制它编译时必须打开CONFIG_DYNAMIC_DEBUG。打开后pr_debug和dev_dbg这类调用不会无条件输出而是作为一个动态调试点注册。运行时通过 debugfs 控制哪些文件、哪些函数、哪个级别的pr_debug要打印echo file mx_driver.c p /sys/kernel/debug/dynamic_debug/control这种方式的好处是打印点可以按需开启平时零开销。对生产设备来说用动态调试替代开关编译宏是完全不同的体验。6. 实战场景我是怎么用printk定位一个启动崩溃问题的6.1 案例背景串口只输出到一半就停了那是在一块 ARM64 板卡上内核启动到某个外设驱动时突然无输出。普通开发者第一反应是printk级别太低于是我把启动参数改成loglevel8 ignore_loglevel但问题依旧。此时我意识到崩溃点可能真的在控制台初始化完成之前或者 CPU 在内核中直接卡死了。接着加earlyconconsolettyS0,115200 earlyconuart8250,mmio64,0x1c020000,115200这样能看到异常发生前的最后一条日志发现卡在drivers/clk/clk.c: clk_core_set_parent_nolock附近。于是我用 BFD 反编译vmlinux把打印地址换算成符号最终定位到某个时钟驱动父时钟的CLK_SET_RATE_PARENT标志设置不当导致父时钟没有被正确使能。这个经历验证了一个原则printk 排查问题第一步是确认打印链路本身是通的。先用启用参数看早期日志再决定是改级别、加 earlycon还是上 ftrace。6.2 案例后续用 dump_stack 和 backtrace 配合printk定位函数调用路径时光是打印一个错误码可能不够。我常在错误处理里加上一句dump_stack();它会在当前调用点打出一个完整的内核调用栈。和printk相比dump_stack不需要额外参数在每个 CPU 上单独表示当前上下文。尤其当问题是“这个函数为什么会在这里被调用”时一个调用栈比几十行日志都直接。要注意它也有开销别放在高频热路径里。6.3 决定打印何时留下、何时删掉很多人写完printk就不管了。实际产品化的驱动我建议把调试信息分成三类类型接口保留策略错误、严重状态dev_err/pr_err永远保留正常生命周期事件dev_info/pr_info一般保留但要注意刷屏风险函数内部详细变量pr_debug/dev_dbg默认隐藏需要时通过动态调试开启上线前把级别检查一遍。模块加载时打印一行带版本号的 banner 是有用的比如mx_driver: version 1.2.3 initialized但如果你的驱动缺失固件时每秒钟都打印一次dev_err(fw is NULL)这种就是典型的日志设计缺陷应当加限速或者只打印一次。我在实际项目中还会顺便在模块加载成功日志里带上product_rev、firmware_ver这样的关键编译时信息。这样每当用户反馈问题我只要拿到 dmesg 的第一屏就知道当前运行的固件版本省得反复打电话确认。7. 调试经验之外的几个printk细节还有几个细节虽然不起眼但踩上一次就会记忆深刻。第一个是KERN_CONT的正确姿势。同一个日志要分成多段打印时老内核支持printk(KERN_CONT part);连续追加到上一条。但现代内核在多核并发下根本不能保证两条日志紧接着输出——中间可能插入别的打印。所以我明确建议不要用KERN_CONT拼接要用snprintf先把内容格式化到局部缓冲区再一次性printk出去。第二个是时间戳精度。printk自带的时间戳来自clock_gettime的ktime路径正常足够用。但如果你需要精确到纳秒的时序关系建议同时开启CONFIG_PRINTK_TIME并在日志里配合ktime_get()记录硬件时间戳。注意dmesg显示的时间是从系统启动开始的相对时间不是墙上时钟。第三个是日志持久化问题。printk写入的环形缓冲区在断电、重启后会被清空。如果设备的最终日志需要送给某个人分析你要么在启动脚本里及时journalctl -k /var/log/kernel.log要么在驱动里直接把关键日志写到持久化存储。很多人第一次做现场问题分析时都会在“重启后 dmesg 没了”这件事上吃大亏。说到底printk 的机制从外部看像一个简单的“日志打印 API”但一旦你深入到工作流程、级别过滤、控制台同步、原子上下文限制就会发现每一条设计背后都有过血的教训。掌握它不是目的真正排查一次莫名其妙的启动卡死或驱动异常你才会理解这些细节值多少时间成本。

相关推荐

多通道波分复用器技术拆解:原理、关键参数与部署排障
多通道波分复用器技术拆解:原理、关键参数与部署排障

1. 从一根光纤到多通道:波分复用到底在解决什么问题1.1 先理解“一根光纤能传多少”这件事做光通信的人对“光纤资源不够用”这句话应该都不陌生。早年组网,业务扩容最常见的做法是加纤、加设备,运营商和大型企业机房里的ODF架(光… · 2026/9/25 14:34:49

ChatGPT Shortcut 浏览器扩展安装与使用指南:把 AiShort 提示词库直接嵌入 ChatGPT/Gemini/Claude 侧边栏
ChatGPT Shortcut 浏览器扩展安装与使用指南:把 AiShort 提示词库直接嵌入 ChatGPT/Gemini/Claude 侧边栏

AI 应用提示工程人工智能前端 【免费下载链接】ChatGPT-Shortcut Stop writing prompts from scratch — a searchable prompt library for ChatGPT, Claude, Gemini and Cursor Русский 한국어 العربية हिन्दी ไทย | 别再从头写提示词&… · 2026/9/25 14:34:49

生产级AI Agent记忆系统设计:DDD分层、SSE流式输出与HITL实战
生产级AI Agent记忆系统设计:DDD分层、SSE流式输出与HITL实战

1. 为什么"记忆"才是生产级 Agent 和玩具 Demo 的分水岭我接触过不少团队做 AI Agent,Demo 阶段都很惊艳:接个大模型,挂几个工具,跑通一个"查天气订机票"的流程,演示效果拉满。但一上生产就露馅—… · 2026/9/25 14:34:30

使用 [特殊字符] Transformers 在 TensorFlow 中微调摘要模型:run_summarization 脚本实战与源码解析
使用 [特殊字符] Transformers 在 TensorFlow 中微调摘要模型:run_summarization 脚本实战与源码解析

推理引擎大模型 【免费下载链接】FlexGen Running large language models on a single GPU for throughput-oriented scenarios. 项目地址: https://gitcode.com/gh_mirrors/fl/FlexGen 点击查看 免费下载 本篇技术指南以 FlexGen 仓库基准测试套件中收录的 Tensor… · 2026/9/25 15:36:32

自研轻量CRM系统实战:从数据模型到自动化规则的完整设计
自研轻量CRM系统实战:从数据模型到自动化规则的完整设计

1. 项目概述与设计思路做 DeskcommCRM 这个项目之前,我们团队其实已经和大多数中小型服务团队一样,陷入了一堆工具的泥潭里:客户资料散落在 Excel 和网盘,邮件往来挂在个人邮箱里,在线客服聊天记录在另一个后台&#x… · 2026/9/25 15:36:32

agentic-awesome-skills 测试模式技能解析:Jest/Vitest 工厂函数、Mock 策略与 TDD 红绿重构工作流
agentic-awesome-skills 测试模式技能解析:Jest/Vitest 工厂函数、Mock 策略与 TDD 红绿重构工作流

AI 技能AI 插件 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and planning, backed by 2,445 agentic skills. Includes CLI, local MCP, catalog, … · 2026/9/25 15:35:48

OpenChamber 新特性前瞻:Session Timeline 时间线视图、扩展浏览器代理与全类型文件预览
OpenChamber 新特性前瞻:Session Timeline 时间线视图、扩展浏览器代理与全类型文件预览

AI Agent人工智能代码智能体交互助手 【免费下载链接】openchamber Agentic Development Environment based on OpenCode AI agent 项目地址: https://gitcode.com/gh_mirrors/op/openchamber 点击查看 免费下载 本文基于仓库中 changelog/unreleased.md 记录的下一… · 2026/9/25 15:35:22

Atlas 300V部署YOLO全流程:从环境配置到性能实测与踩坑记录
Atlas 300V部署YOLO全流程:从环境配置到性能实测与踩坑记录

前阵子一位做安防项目的朋友,拿着块 Atalas 300V 24G 的卡过来问我:这玩意儿是不是运算加速卡?我说是,但你得先搞清楚,它加速的是“推理”,不是“训练”。后来他又问,现有这套 YOLO 检测模型能不… · 2026/9/25 15:35:10

国产麒麟系统安装部署OpenClaw完整指南(适配V10/VSP)国产操作系统的AI智能体部署
国产麒麟系统安装部署OpenClaw完整指南(适配V10/VSP)国产操作系统的AI智能体部署

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 15:35:10

数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)
数值优化(Numerical Optimization)学习系列-03-共轭梯度方法(Conjugate Gradient)

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战
创维E900V22D刷机全攻略:S905L3SB芯片兼容性解析与救砖实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:31

MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX
MQTT协议原理与Broker服务器搭建实战:从Mosquitto到EMQX

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/25 1:00:37

了解更多?预约专属演示

我们的顾问将为您一对一讲解产品与方案

企业微信二维码