[導讀][導讀]剛剛開始做Linux相關開發(fā)工作時,深感Linux內(nèi)核代碼龐大,要加些自己的驅動進內(nèi)核代碼樹,常常深陷bug的泥沼難以自拔,今天來分享一下內(nèi)核調試利器printk的使用心得。前面一段時間很忙,后期更文頻率會漸漸回歸正常頻率,盡量會保證每周一到兩更。感謝各位朋友的關注而沒有...
[導讀] 剛剛開始做Linux相關開發(fā)工作時,深感Linux內(nèi)核代碼龐大,要加些自己的驅動進內(nèi)核代碼樹,常常深陷bug的泥沼難以自拔,今天來分享一下內(nèi)核調試利器printk的使用心得。
前面一段時間很忙,后期更文頻率會漸漸回歸正常頻率,盡量會保證每周一到兩更。感謝各位朋友的關注而沒有棄我而去,我定不負厚愛,會持續(xù)輸出些日常技術工作中的心得體會,如對朋友們有些許幫助,也煩請幫忙點個贊或者在看(這并不會對各位有何不利的影響哈~~~),這也是對我堅持持續(xù)輸出的大大激勵!
printk初接觸
Linux內(nèi)核啟動之后常會看見很多信息打印出來,這在底層是printk子系統(tǒng)實現(xiàn)的,其實現(xiàn)代碼在./kernel/printk/中實現(xiàn)的。一個小小的打印,對于內(nèi)核而言也需要考慮很多方面,需要考慮到多核、中斷、緩沖以及用戶空間接口。對于用戶空間接口很多朋友或許會很疑惑。其中/dev/kmsg字符設備就是printk子系統(tǒng)實現(xiàn)的內(nèi)核打印字符設備。如果利用文件操作寫這個設備就最終會以printk形式輸出,如果讀這個設備最終就會返回printk歷史,你如不信不妨用這個命令試試:cat?/dev/kmsg
/proc/kmsg
/proc/kmsg僅為root用戶提供內(nèi)核日志緩沖區(qū)的只讀操作。等效于通過SYSLOG_ACTION_READ操作調用[syslog(2)]。一個進程必須具有超級用戶特權才能讀取此文件,并且只有一個進程應讀取該文件。如果正在運行使用syslog(2)系統(tǒng)調用記錄內(nèi)核消息的syslog進程,則不應讀取該文件。這里補充說一點是,/proc文件系統(tǒng)本質上是偽文件系統(tǒng),它提供了內(nèi)核數(shù)據(jù)結構的接口。它一般掛載在/proc上。通常情況下,它是由系統(tǒng)自動掛載的的,但是也可以使用以下命令手動安裝:
?mount?-t?proc?proc?/proc
大部分位于/proc下的文件屬于只讀特性,但也有少部分是可寫的。但是對于/proc/kmsg而言則是只讀的。/dev/kmsg
/dev/kmsg提供對同一內(nèi)核日志緩沖區(qū)的訪問,但以一種更易于使用的方式。每次打開都會對讀取進行跟蹤,因此可以并行讀取多個進程,并且在讀取條目時不會將其從緩沖區(qū)中刪除。/dev/kmsg還提供對日志緩沖區(qū)的寫訪問權,因此可用于將條目添加到日志緩沖區(qū)。那么為什么兩者都存在,以及為什么一個存在于/proc中和而另一個存在于/dev中,/proc/kmsg是歷史設計,而/dev/kmsg是較新引入的,被設計為日志緩沖區(qū)的可用接口。該接口也實現(xiàn)了用戶空間添加記錄進內(nèi)核日志系統(tǒng)的可能。其代碼實現(xiàn)也可以簡單來瞅瞅:printk使用
printk怎么打印的呢?想必做嵌入式開發(fā)的一定熟悉printf函數(shù),那么從范式上printk也比較類似,但也有很多不同。且看:內(nèi)核打印,界定了日志級別,其語法范式:printk([KERN_LOG_LEVEL]?"Message:?%s\n",?arg);
比如:printk(KERN_DEBUG?“Here?is:?%s:%i\n”,?__FILE__,?__LINE__);
那么有哪些日志級別,又各有何區(qū)別呢?日志級別
| 級別 | 宏名 | 描述 |
|---|---|---|
| 0 | KERN_EMERG | 最高級別,系統(tǒng)遇到緊急狀況,嚴重時可能掛機了 |
| 1 | KERN_ALERT | 告警級別,需要立即關注或處置 |
| 2 | KERN_CRIT | critical 情況,比較緊急 |
| 3 | KERN_ERR | 當系統(tǒng)檢測到某個錯誤 |
| 4 | KERN_WARNING | warning中文也會翻譯成警告,但是緊急程度級別比Alert低, |
| 5 | KERN_NOTICE | 正常操作但或許需要注意的一些操作 |
| 6 | KERN_INFO | 信息提示級別,比如驅動指示一下做了什么操作 |
| 7 | KERN_DEBUG | 調試信息 |
格式化
下面內(nèi)容來源于./Documentation/printk-formats.txt,整理于此方便使用:- 基本變量
| 類型 | 格式化 |
|---|---|
| int | %d 或 %x |
| unsigned int | %u 或 %x |
| long | %ld 或 %lx |
| unsigned long | %lu 或 %lx |
| long long | %lld 或 %llx |
| unsigned long long | %llu 或 %llx |
| size_t | %zu 或 %zx |
| ssize_t | %zd 或 %zx |
| s32 | %d 或 %x |
| u32 | %u 或 %x |
| s64 | %lld 或 %llx |
| u64 | %llu 或 %llx |
- 指針
| 類型 | 格式化 |
|---|---|
| %p | 打印基本指針 |
| %pF | versatile_init 0x0/0x110 |
| %pf | versatile_init |
| %pS | versatile_init 0x0/0x110 |
| %pSR | versatile_init 0x9/0x110 |
| %ps | versatile_init |
| %pB | prev_fn_of_versatile_init 0x88/0x88 |
修改控制臺打印級別
運行時修改
在調試過程中,或許會發(fā)現(xiàn)有的printk信息沒有打印出來,那么肯定是默認運行中內(nèi)核控制臺printk打印級別低于代碼中使用的級別,那么如果不想重新編譯內(nèi)核,有沒有辦法動態(tài)修改呢?來看看怎么修改:在/proc/sys/kernel/printk文件中,有4個屬性分別對應:- 當前控制臺日志級別
- 默認日志級別
- 最小日志級別
- 啟動階段默認日志級別
echo?6?>?/proc/sys/kernel/printk
這里傳入6,表示小于6級別的打印都將會被打印出來。這里可以根據(jù)需要傳入不同的值。取值參見前表<日志級別>。如想將所有的信息都打印出來,傳入8即可,如:echo?8?>?/proc/sys/kernel/printk
如果你想將這些打印記錄進一個文件,則可以使用klogd進行重定向,比如:klogd?-o?-f?./kernel.msg
編譯修改
如果你想將某一模塊的內(nèi)核打印在編譯時使能,這樣做的好處是在模塊加載過程中的所有的信息在控制臺都可以看到,你還可以增加你感興趣的代碼添加打印信息,用以輔助調試。這怎么實現(xiàn)呢?這里需要去看看你的內(nèi)核模塊代碼是以何種方式去調用printk的,比如有的代碼這樣調用:static?int?tea5764_i2c_probe(struct?i2c_client?*client,
????????const?struct?i2c_device_id?*id)
{
?struct?tea5764_device?*radio;
?struct?v4l2_device?*v4l2_dev;
?struct?v4l2_ctrl_handler?*hdl;
?struct?tea5764_regs?*r;
?int?ret;
?PDEBUG("probe");
?.....
這里的PDEBUG其實就是printk的一種宏重包裝:#define?PINFO(format,?...)\
?printk(KERN_INFO?KBUILD_MODNAME?":?"\
??DRIVER_VERSION?":?"?format?"\n",?##?__VA_ARGS__)
#define?PWARN(format,?...)\
?printk(KERN_WARNING?KBUILD_MODNAME?":?"\
??DRIVER_VERSION?":?"?format?"\n",?##?__VA_ARGS__)
#?define?PDEBUG(format,?...)\
?printk(KERN_DEBUG?KBUILD_MODNAME?":?"\
??DRIVER_VERSION?":?"?format?"\n",?##?__VA_ARGS__)
還有的是這樣:static?int?ad9467_spi_read(struct?spi_device?*spi,?unsigned?reg)
{
?unsigned?char?buf[3];
?int?ret;
?if?(spi)?{
??buf[0]?=?0x80?|?(reg?>>?8);
??buf[1]?=?reg? 





