Linux Kernel調試:強大的printk(一)

引言

想了好久,還是覺得這個標題才配得上printk!^_^

我相信,不管做什么開發,使用最多的調試手段應該就是打印了,從我們學習編程語言第一課開始,寫的第一段代碼,就是打印"Hello, world"。

內核也不例外,到處都有打印語句,只不過在內核中的打印函數是printk,printk作為內核中不可或缺的基礎工具,幾乎可以在內核的任何上下文中都可以使用,正應了那句話,它明明是那么普通,卻又那么強大~~

關于printk的內容還挺多,所以分多篇深入介紹,這是第一篇,介紹printk的基本用法,后面介紹printk的進階用法。

如果本篇你已經熟悉,可以直接閱讀后面的內容,快捷跳轉:

Linux Kernel調試:強大的printk(二):pr_xxx相關的內容

Linux Kernel調試:強大的printk(三):dev_xxx相關的內容,以及限制打印速率

printk的特性

printk是一個在內核中使用的打印函數,用于向內核日志系統輸出信息,其通過將消息寫入內核日志緩沖區,然后由不同的工具讀取,比如 dmesg 命令或者查看 /var/log/syslog 文件(根據系統配置不同而有所變化)。

它對于調試和報告錯誤很有用,并且可以在中斷上下文中使用,但是使用時要小心:如果機器的控制臺中充斥著printk消息則會無法使用。

相比于用戶空間的printf,printk 有一些獨特的特性和用途:

  • 日志級別:printk 允許你指定日志級別,這可以控制該消息是否顯示到控制臺。例如,緊急錯誤可能需要立即顯示,而調試信息則不一定。

  • 緩沖區:printk 的輸出首先被寫入一個環形緩沖區(ring buffer)中。這意味著即使沒有控制臺,或者控制臺驅動程序尚未初始化,也可以記錄消息。你可以通過讀取 /proc/kmsg 或者使用 dmesg 命令來查看這些消息。

  • 時間戳:每個 printk 消息都會附帶一個時間戳,這個時間戳是從系統啟動開始計算的時間,單位是秒和微秒。這對于調試非常有用,因為它可以幫助確定事件發生的順序和間隔。

  • 異步行為:與 printf 不同,printk 并不保證消息會立刻出現在控制臺上。這是因為內核代碼不能阻塞等待 I/O 完成,所以 printk 實際上將消息放入了一個隊列中,稍后由另一個內核線程負責將其實際輸出到控制臺或其他目標。

printk的函數原型

printk定義于include/linux/printk.h文件中,原型如下:

#define printk(fmt, ...) printk_index_wrap(_printk, fmt, ##__VA_ARGS__)#define printk_index_wrap(_p_func, _fmt, ...)                                \({                                                                \__printk_index_emit(_fmt, NULL, NULL);                        \_p_func(_fmt, ##__VA_ARGS__);                                \})int _printk(const char *fmt, ...);

printk的用法

printk的調用方法如下:

printk(KERN_INFO "My printk message\n");

這里的 KERN_INFO 是一個宏,它實際上只是一個字符串常量,用來表示這條消息的日志級別。其他可用的日志級別稍后會講。我們可以使用dmesg(1)之類的查看日志的工具根據日志級別進行過濾。

盡管 printk 很方便,但在性能關鍵的路徑中應謹慎使用,因為即使是簡單的日志記錄操作也可能引入顯著的開銷。另外,在一些時序要求嚴格的場合,一定要在關閉日志的情況下充分測試,因為有時打印日志會改變時序,會出現關閉log時,程序反而不能正常工作。

日志級別

日志級別在include/linux/kern_levels.h文件中定義:

#define KERN_SOH        "\001"                /* ASCII Start Of Header */
#define KERN_SOH_ASCII        '\001'#define KERN_EMERG        KERN_SOH "0"        /* system is unusable */
#define KERN_ALERT        KERN_SOH "1"        /* action must be taken immediately */
#define KERN_CRIT        KERN_SOH "2"        /* critical conditions */
#define KERN_ERR        KERN_SOH "3"        /* error conditions */
#define KERN_WARNING        KERN_SOH "4"        /* warning conditions */
#define KERN_NOTICE        KERN_SOH "5"        /* normal but significant condition */
#define KERN_INFO        KERN_SOH "6"        /* informational */
#define KERN_DEBUG        KERN_SOH "7"        /* debug-level messages */#define KERN_DEFAULT        ""                /* the default kernel loglevel *//** Annotation for a "continued" line of log printout (only done after a* line that had no enclosing \n). Only to be used by core/arch code* during early bootup (a continued line is not SMP-safe otherwise).*/
#define KERN_CONT        KERN_SOH "c"

  • 可以看到,日志級別有0~7種,從名稱也可以看得出來,數值越小越緊急,優先級越高

  • KERN_SOH是一個控制字符,用于標記日志級別的開始,當解析到KERN_SOH后,其后的一個字符就是日志級別,或者KERN_CONT

  • KERN_CONT的意思是這是前一條消息的續行消息,不會添加時間戳、日志級別等新行信息,也不會換行

  • 如果沒有解析到KERN_SOH則使用默認級別,KERN_DEFAULT定義為空,這就可以通過編譯時配置或者運行時設置默認的日志級別了

printk輸出到哪里

以下是 printk 輸出信息可能到達的設備或位置:

內核日志緩沖區

printk 首先將消息寫入內核日志緩沖區,這是一個環形緩沖區,用于臨時存儲內核消息,直到它們被用戶空間的工具(如 dmesg 或日志守護進程)讀取。

控制臺(Console)

根據內核的配置和日志級別,printk 輸出的消息可能會直接顯示在控制臺上。

  • 虛擬控制臺(TTY):在本地終端(如 /dev/tty1 或 /dev/ttyS0)上顯示消息。

  • 串行控制臺:通過串行端口(如 COM 端口)輸出消息,常用于服務器或嵌入式設備的調試。

  • 圖形控制臺:在圖形界面的終端窗口中顯示消息(如 X 終端)。

通過內核啟動參數(如 console=ttyS0,115200)或運行時配置(如 dmesg 的日志級別設置)來控制哪些消息會顯示在控制臺上。

日志文件

用戶空間的日志守護進程(如 systemd-journald 或 rsyslogd)會從內核日志緩沖區讀取消息,并將它們寫入日志文件。

常見日志文件:

  • /var/log/syslog 或 /var/log/messages:系統日志文件,存儲內核消息和其他系統日志。

  • /var/log/dmesg:專門存儲內核日志的文件,通常由 dmesg 命令更新。

日志守護進程的配置文件(如 /etc/rsyslog.conf 或 /etc/systemd/journald.conf)決定了日志的存儲位置和格式。

網絡日志服務器

在某些配置下,內核日志可以通過網絡發送到遠程日志服務器,便于集中管理和監控。

實現方式:通過日志守護進程(如 rsyslogd)配置網絡日志功能,將日志消息發送到指定的遠程服務器。

其他輸出目標

  • 串行端口:除了作為控制臺外,串行端口也可以被配置為單獨的日志輸出目標,用于調試或日志備份。

  • USB 調試接口:在一些嵌入式設備中,printk 輸出可以通過 USB 調試接口發送到連接的主機。

  • 自定義設備:通過內核模塊或驅動程序,可以將 printk 輸出重定向到其他自定義設備(如存儲設備或專用的調試接口)。

用戶空間程序

用戶空間程序可以通過 /dev/kmsg 或 dmesg 等接口直接讀取內核日志,并進行進一步處理或顯示。

  • dmesg:用于顯示內核日志的命令行工具。

  • journalctl(systemd 系統):用于查看和管理內核日志的工具。

Ubuntu控制臺日志級別配置

我們在ubuntu上,有時會發現,有些log不會顯示到控制臺上,通過dmesg命令才可以看到,這是因為內核向控制臺輸出的信息取決于其日志級別,通過 /proc/sys/kernel/printk 文件控制,我們查看該文件內容如下:

這4個數字的含義如下:

第一個數字是控制臺日志級別(console_loglevel),表示只有嚴重程度高于或等于這個級別的內核消息會被打印到控制臺上。

第二個數字是默認的消息日志級別(default_message_loglevel),它設定了當調用內核函數 printk() 而沒有明確指定優先級時,給消息分配的默認優先級。

第三個數字是最低的控制臺日志級別(minimum_console_loglevel),系統允許的最小控制臺日志級別。例如,如果這個值為 1,則控制臺日志級別不能設置為 0KERN_EMERG)。

第四個數字是默認的控制臺日志級別(default_console_loglevel),代表啟動時的默認控制臺日志級別。它是一個參考值,在某些情況下,系統可能會重置控制臺日志級別回到這個默認值。

所以第一個數字是4,就表示比KERN_WARNING級別高的打印信息才會出現在控制臺上。這在一定程度上過濾了一些并不是很重要的信息。

printk格式占位符

一些常見的printk格式占位符:

  • 對于size_t和ssize_t類型(分別表示有符號和無符號整數),請分別使用%zu和%zd格式說明符

  • 在打印內核空間中的地址(指針)時:?

    • 非常重要:出于安全考慮,請使用%pK(它只會輸出哈希值,有助于防止信息泄露,這是一個嚴重的安全問題)

    • 對于實際的指針,請使用%px查看實際地址(不要在生產環境中這樣做!)

    • 對于物理地址,請使用%pa

  • 若要將原始緩沖區打印為十六進制字符串,請使用%*ph(其中*由字符數量代替;對于少于65個字符的緩沖區使用此方法,對于更多字符的緩沖區則使用print_hex_dump_bytes()函數)。有多種變體可用(參見隨后的內核文檔鏈接)

完整的printk格式占位符列表,包括使用示例,請查閱內核文檔:

中文:Documentation/translations/zh_CN/core-api/printk-formats.rst

英文:Documentation/core-api/printk-formats.rst

內核模塊中使用printk

雖然在內核模塊中使用printk顯得很low(一般都是使用pr_xxx或者dev_xxx),但是由于我們這一篇就是講printk的,所以還是以printk為例寫一個內核模塊,來實際使用一下printk,關于寫內核模塊的知識這里不會講解,后面看看,如有必要,會補上這部分知識,現在缺少這部分知識的同學需要自己學習一下

下面是代碼,共有兩個文件printk_usage.c和Makefile文件,可到這里獲取https://gitee.com/coolloser/linux-kerenl-debug:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>// 定義模塊加載函數
static int __init printk_module_init(void)
{printk(KERN_INFO "====printk模塊加載成功!====\n");printk(KERN_DEBUG "這是一個DEBUG級別的信息\n");printk(KERN_INFO "這是一個INFO級別的信息\n");printk(KERN_WARNING "這是一個WARNING級別的信息\n");printk(KERN_ERR "這是一個ERROR級別的信息\n");printk(KERN_CRIT "這是一個CRITICAL級別的信息\n");printk(KERN_ALERT "這是一個ALERT級別的信息\n");printk(KERN_EMERG "這是一個EMERGENCY級別的信息\n");// 使用KERN_CONT繼續上一條日志消息printk(KERN_INFO "這是一條需要繼續的信息...");printk(KERN_CONT "...這是繼續的部分\n");// 使用KERN_DEFAULT設置默認日志級別printk(KERN_DEFAULT "這是默認日志級別的信息\n");return 0; // 返回0表示模塊加載成功
}// 定義模塊卸載函數
static void __exit printk_module_exit(void)
{printk(KERN_INFO "====printk模塊卸載成功!====\n");
}// 注冊模塊加載和卸載函數
module_init(printk_module_init);
module_exit(printk_module_exit);// 模塊信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("your_name");
MODULE_DESCRIPTION("一個簡單的printk內核模塊示例");
MODULE_VERSION("0.1");
# 定義模塊名稱
MODULE_NAME := printk_usage# 定義內核構建目錄,替換成你自己的路徑
KERNEL_BUILD_DIR := /home/leo/debug_kernel/linux-6.12.28# 定義目標文件
obj-m += $(MODULE_NAME).o# 默認目標
all:@echo "Building the $(MODULE_NAME) kernel module..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) modules# 清理目標
clean:@echo "Cleaning up the build environment..."$(MAKE) -C $(KERNEL_BUILD_DIR) M=$(PWD) clean

直接執行make命令進行編譯:

會生成printk_usage.ko

為了測試哪些log會直接顯示到終端上,我們需要輸入ctrl+alt+F3打開一個虛擬終端(tty3),然后登錄

執行如下命令加載模塊:

cd modules/001_printk_usage
sudo insmod printk_usage.ko

會顯示如下內容:

忽略中文亂碼問題^_^,還是能看到ERROR,CRITICAL,ALERT,EMERGENCY級別的log直接顯示到終端上了,這符合/proc/sys/kernel/printk文件控制的log級別

然后我們回到圖形界面(ctrl+alt+F1),輸入如下命令:

sudo dmesg

可以看到如下內容:

模塊中打印的所有內容都可以看到,可以看到KERN_CONT對應的log接續在上一條的后面

總結

本篇介紹了printk的基礎知識和基本用法,最后通過一個內核模塊演示了printk的級別等用法,以及如何查看這些log,由于printk篇幅較長,一篇寫完太長,閱讀起來比較累,所以分多篇進行介紹,后面會介紹printk的進階用法,如pr_xxx和dev_xxx,還有printk_ratelimited等等的

請繼續閱讀下一篇:

Linux Kernel調試:強大的printk(二)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/81305.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/81305.shtml
英文地址,請注明出處:http://en.pswp.cn/web/81305.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

基于NLP技術的客戶投訴與需求文本分類方法研究

目錄 摘要 1. 引言 2. 文本分類基礎 2.1 文本分類的定義與類型 2.2 文本分類的評價指標 3. 傳統文本分類方法 3.1 基于TF-IDF和SVM的方法 3.2 基于主題模型和詞向量的改進方法 4. 深度學習文本分類方法 4.1 TextCNN模型 4.2 BiLSTM模型 4.3 注意力機制與Transformer…

#RabbitMQ# 消息隊列入門

目錄 一 MQ技術選型 1 運行rabbitmq 2 基本介紹 3 快速入門 1 交換機負責路由消息給隊列 2 數據隔離 二 Java客戶端 1 快速入門 2 WorkQueue 3 FanOut交換機 4 Direct交換機 5 Topic交換機 *6 聲明隊列交換機 1 在配置類當中聲明 2 使用注解的方式指定 7 消息轉…

【深度學習】多目標融合算法(六):漸進式分層提取模型PLE(Progressive Layered Extraction)

目錄 一、引言 二、PLE&#xff08;Progressive Layered Extraction&#xff0c;漸進式分層提取模型&#xff09; 2.1 技術原理 2.2 技術優缺點 2.3 業務代碼實踐 2.3.1 業務場景與建模 2.3.2 模型代碼實現 2.3.3 模型訓練與推理測試 2.3.4 打印模型結構 三、總結 一…

【Java開發日記】如何使用Java開發在線生成 pdf 文檔

一、介紹 在實際的業務開發的時候&#xff0c;研發人員往往會碰到很多這樣的一些場景&#xff0c;需要提供相關的電子憑證信息給用戶&#xff0c;例如網銀&#xff0f;支付寶&#xff0f;微信購物支付的電子發票、訂單的庫存打印單、各種電子簽署合同等等&#xff0c;以方便用…

Oracle 11g 單實例使用+asm修改主機名導致ORA-29701 故障分析

解決 把服務器名修改為原來的&#xff0c;重啟服務器。 故障 建表空間失敗。 分析 查看告警日志 ORA-1119 signalled during: create tablespace splex datafile ‘DATA’ size 2000M… Tue May 20 18:04:28 2025 create tablespace splex datafile ‘DATA/option/dataf…

消息隊列的使用

使用內存隊列來處理基于內存的【生產者-消費者】場景 思考和使用Disruptor Disruptor可以實現單個或多個生產者生產消息&#xff0c;單個或多個消費者消息&#xff0c;且消費者之間可以存在消費消息的依賴關系 使用Disruptor需要結合業務特性&#xff0c;設計要靈活 什么業務…

《帝國時代1》游戲秘籍

資源類 PEPPERONI PIZZA&#xff1a;獲得 1000 食物。COINAGE&#xff1a;獲得 1000 金。WOODSTOCK&#xff1a;獲得 1000 木頭。QUARRY&#xff1a;獲得 1000 石頭。 建筑與生產類 STEROIDS&#xff1a;快速建筑。 地圖類 REVEAL MAP&#xff1a;顯示所有地圖。NO FOG&#xf…

使用JSP踩過的坑

雖然說jsp已經過時了&#xff0c;但是有時維護比較老的項目還是需要的。 下面說下&#xff0c;我使用jsp踩過的坑&#xff1a; 1.關于打印輸出 在jsp中輸出使用 out.println("hello");而不是 System.out.println("hello");如果在定義函數部分需要打印…

redis集群創建時手動指定主從關系的方法

適用場景&#xff1a; 創建主從關系時默認參數 --cluster-replicas 1 會自動分配從節點。 為了能精確控制 Redis Cluster 的主從拓撲結構&#xff0c;我們通過 Redis Cluster 的手動分片功能來實現 一、手動指定主從關系的方法 使用 redis-cli --cluster-replicas 0 先創建純…

ROS合集(七)SVIn2聲吶模塊分析

文章目錄 一、整體思想二、具體誤差建模流程三、總結明確&#xff08;預測值與觀測值&#xff09;四、選點邏輯五、Sonar 數據處理流水線1. ROS Launch 配置&#xff08;imagenex831l.launch&#xff09;2. SonarNode 節點&#xff08;sonar_node.py&#xff09;3. Subscriber …

Python爬蟲實戰:研究PySpider框架相關技術

1. 引言 1.1 研究背景與意義 網絡爬蟲作為互聯網數據采集的重要工具,在信息檢索、輿情分析、市場調研等領域發揮著重要作用。隨著互聯網信息的爆炸式增長,如何高效、穩定地獲取所需數據成為了一個關鍵挑戰。PySpider 作為一款功能強大的 Python 爬蟲框架,提供了豐富的功能…

《大模型開源與閉源的深度博弈:科技新生態下的權衡與抉擇》

開源智能體大模型的核心魅力&#xff0c;在于它構建起了一個全球開發者共同參與的超級協作網絡。想象一下&#xff0c;來自世界各個角落的開發者、研究者&#xff0c;無論身處繁華都市還是偏遠小鎮&#xff0c;只要心懷對技術的熱愛與追求&#xff0c;就能加入到這場技術狂歡中…

大數據模型對陌生場景圖像的識別能力研究 —— 以 DEEPSEEK 私有化部署模型為例

摘要 本研究聚焦于已訓練的大數據模型能否識別未包含在樣本數據集中的陌生場景圖像這一問題&#xff0c;以 DEEPSEEK 私有化部署模型為研究對象&#xff0c;結合機器學習理論&#xff0c;分析模型識別陌生場景圖像的影響因素&#xff0c;并通過理論探討與實際應用場景分析&…

STM32——從點燈到傳感器控制

STM32基礎外設開發&#xff1a;從點燈到傳感器控制 一、前言 本篇文章總結STM32F10x系列基礎外設開發實例&#xff0c;涵蓋GPIO控制、按鍵檢測、傳感器應用等。所有代碼基于標準庫開發&#xff0c;適合STM32初學者參考。 二、硬件準備 STM32F10x系列開發板LED模塊有源蜂鳴器…

[特殊字符] 使用增量同步+MQ機制將用戶數據同步到Elasticsearch

在開發用戶搜索功能時&#xff0c;我們通常會將用戶信息存儲到 Elasticsearch&#xff08;簡稱 ES&#xff09; 中&#xff0c;以提高搜索效率。本篇文章將詳細介紹我們是如何實現 MySQL 到 Elasticsearch 的增量同步&#xff0c;以及如何通過 MQ 消息隊列實現用戶信息實時更新…

MyBatis緩存機制全解析

在MyBatis中&#xff0c;緩存分為一級緩存和二級緩存&#xff0c;它們的主要目的是減少數據庫的訪問次數&#xff0c;提高查詢效率。下面簡述這兩種緩存的工作原理&#xff1a; 一、 一級緩存&#xff08;SqlSession級別的緩存&#xff09; 一級緩存是MyBatis默認開啟的緩存機…

【短距離通信】【WiFi】WiFi7關鍵技術之4096-QAM、MRU

目錄 3. 4096-QAM 3.1 4096-QAM 3.2 QAM 的階數越高越好嗎&#xff1f; 4. MRU 4.1 OFDMA 和 RU 4.2 MRU 資源分配 3. 4096-QAM 摘要 本章主要介紹了Wi-Fi 7引入的4096-QAM對數據傳輸速率的提升。 3.1 4096-QAM 對速率的提升 Wi-Fi 標準一直致力于提升數據傳輸速率&a…

【二刷力扣】【力扣熱題100】今天的題目是:283.移動零

題目&#xff1a; 給定一個數組 nums&#xff0c;編寫一個函數將所有 0 移動到數組的末尾&#xff0c;同時保持非零元素的相對順序。 請注意 &#xff0c;必須在不復制數組的情況下原地對數組進行操作。 示例 1: 輸入: nums [0,1,0,3,12] 輸出: [1,3,12,0,0] 示例 2: 輸…

機器學習中的多GPU訓練模式

文章目錄 一、數據并行&#xff08;Data Parallelism&#xff09;二、模型并行&#xff08;Model Parallelism&#xff09;1. 模型并行2. 張量并行&#xff08;Tensor Parallelism&#xff09; 三、流水線并行&#xff08;Pipeline Parallelism&#xff09;四、混合并行&#x…

《JavaScript 性能優化:從原理到實戰的全面指南》

《JavaScript 性能優化&#xff1a;從原理到實戰的全面指南》 一、JavaScript 性能優化基礎理論 在深入探討 JavaScript 性能優化技術之前&#xff0c;我們需要明白JavaScript 的執行機制和性能瓶頸產生的根本原因。JavaScript 是一種單線程、非阻塞的腳本語言&#xff0c;其…