我在 Linux閱碼場 微信公衆號發表的文章,由於版權原因,通過轉載分享此博客,原文鏈接爲:
《Linux pstore 實現自動“抓捕”內核崩潰日誌》:https://mp.weixin.qq.com/s/YkqU5_jMgc0B5_62n2QKyA
作者簡介:
廖威雄,就職於珠海全志科技股份有限公司,負責Linux IO全棧研發、性能優化、開源社區開發交流、
Linux 內核開源社區pstore/blk,mtdpstore模塊的作者、大客戶存儲技術支持、
全志首個UBI存儲方案主導人、全志首個RTOS NFTL主導人
我設計的內核模塊pstore/blk
及其衍生的pstore/zone
,mtdpstore
終於在v5.8-rc1
版本合入了torvalds/linux.git
,而且發現國內外對pstore
的介紹都好少好少,乾脆來一波科普。
簡介
pstore
文件系統(是的,這是個文件系統)是Persistent Storage
的縮寫,最早在2010年由 Tony Luck 設計併合入Linux主分支,設計的初衷是在內核Panic/Oops
時能自動轉存內核日誌(log_buf
),在Panic
重啓後,把轉存的日誌以文件形式呈現到用戶空間以分析內核崩潰問題。
這對分析那種小概率且沒辦法抓到現場的問題非常實用,尤其是現在智能且聯網的設備逐漸普及的時候,遠端的設備可以自己捕抓崩潰日誌再通過網絡傳輸到服務器,維護人員就可以根據收集來的日誌定位和解決問題,然後通過OTA讓設備升級迭代。
根據網上搜尋的資料,在pstore
文件系統之前其實有不少類似的實現。
- apanic
Android最早的panic信息記錄的方案。在linux 2.6
的安卓的內核中找到,卻沒有提交到社區,後來被放棄維護了。
網上找不到放棄的原因,我自己猜測是因爲其只適用於mtd nand
,然而現在的Android基本用的都是emmc
。
apanic
應該是Android Panic
的縮寫吧,可以實現在內核崩潰時,把日誌轉存到mtd nand
。 - ramoops
這裏指的是最早的ramoops
實現,在最新代碼已經整合入pstore
中,以pstore/ram
的後端形式存在。
ramoops
可以把日誌轉存到重啓不掉電的ram
中。這裏對ram
有一點要求,即使重啓ram
的
數據也不能丟失。 - crashlog
這是openwrt
提供的內核patch
,並沒有提交到內核社區。它也是基於ram
,只能轉存```Panic/Oops``
的日誌。 - mtdoops
MTD子系統支持的功能,與pstore
非常相似,只支持轉存Panic/Oops
日誌,不能以文件呈現,
需要用戶自行解析整個MTD分區。(因爲功能的相似,我實現了mtdpstore
用於替代mtdoops
) - kdump
如果說pstore
是個輕量級的內核崩潰日誌轉存的方案,kdump則是一個重量級的問題分析工具。在崩潰時,由
kdump
產生一個用於捕抓當前信息的內核,該內核會收集內存所有信息到dump core
文件中。在重啓後,
捕抓到的信息保存在特定的文件中。類似的還有netdump
和diskdump
。kdump
的方案適用於
服務器這種有大量資源的設備,功能也非常強大,但對嵌入式設備非常不友好。
pstore
經過長期迭代,除了轉存Panic/Oops
的日誌之外(dmesg
前端),還支持pmsg
、console
和ftrace
的前端,除了pstore/ram
的後端之外,還有我設計的pstore/blk
後端,除了支持轉存到ram
之外,還有block device
和mtd device
。
pstore
的前端,是指轉存的日誌類型,pstore
的後端,是指轉存到什麼類型的設備。
目前支持以下幾個前端:
- dmesg:主要是轉存
Panic/Oops
時log_buf
裏面的內核日誌 - pmsg:提供給用戶空間存儲日誌的入口,在Android裏有看到被用於存儲系統的日誌。
- console:終端日誌
- ftrace:
function trace
的信息
目前支持以下幾種後端:
- pstore/ram:
Persistent Ram
,重啓不會丟數據的內存 - pstore/blk:(v5.8以後的版本)所有可寫的塊設備,例如磁盤、U盤、emmc、NFTL nand等
- mtd device:(v5.8以後的版本)mtd設備,例如 mtd nand。(mtd設備的支持依賴於 pstore/blk 後端,準確來說不是一種獨立後端)
怎麼用
就像把大象裝入冰箱只需要打開冰箱,把大象放進去,關上冰箱門的3個步驟,使用pstore
也只需要3個步驟:
- 使能 pstore
- 掛載 pstore文件系統
- 讀取 轉存的日誌文件
詳細的說明可以看源碼上的文檔,本文只做基本功能的介紹。
- Documentation/admin-guide/ramoops.rst
- Documentation/admin-guide/pstore-blk.rst
使能
在menuconfig
中選擇內核pstore
模塊
$ make menuconfig
|-> File systems
|-> Miscellaneous filesystems
|-> Persistent store support
|-> Log kernel console messages # console 前端
|-> Log user space messages # pmsg 前端
|-> Persistent function tracer # ftrace 前端
|-> Log panic/oops to a RAM buffer # pstore/ram 後端
|-> Log panic/oops to a block device # pstore/blk 後端
上述兩個後端2選1即可,前端就根據自己的需求選擇,至於dmesg
前端,默認使能沒得選。如果希望用在mtd
設備上,還需要選擇mtdpstore
模塊:
$ make menuconfig
|-> Device Drivers
|-> Memory Technology Device (MTD) support
|-> Log panic/oops to an MTD buffer based on pstore
選上就可以用了?雖然我非常想說“是的”,但事實卻有點“骨感”。即使所有前端都使用默認配置,pstore/ram
至少也需要知道可用的內存範圍吧?pstore/blk
至少也需要知道使用哪個塊設備吧?
pstore/ram
支持 模塊參數(cmdline)、設備樹、和Platform Data的3種配置方式,從代碼來看,優先級關係是:模塊參數 > Platform Data > 設備樹。
pstore/blk
支持 Kconfig
和 模塊參數(cmdline)的兩種配置方式,且模塊參數比Kconfig有更高的優先級。
pstore/ram
我接觸也不多,直接介紹pstore/blk
的使用方法。對新同學來說,請忽略一大堆亂七八糟的屬性配置(使用默認值),只需要告訴pstore/blk
後端使用哪個塊設備即可。
在Kconfig
中配置:
$ make menuconfig
|-> File systems
|-> Miscellaneous filesystems
|-> Persistent store support
|-> Log panic/oops to a block device # pstore/blk 後端
|-> () block device identifier # 使用哪個塊設備?
如果使用cmdline
,可以這麼寫:
pstore_blk.blkdev=XXXX
或者以模塊加載:
$ sudo insmod pstore_blk.ko blkdev=XXX
這裏的塊設備可以是代表整個磁盤的sda
,也可以是代表某個分區的mmcblk0p4
。雖然支持7種變體,但常用的還是兩種:
/dev/<disk_name>
: 例如,使用U盤的第2個分區,則是/dev/sdb2
<major>:<minor>
:例如,mmc設備第6個分區,則是179:6
形式大概是這樣:
$ sudo insmod pstore_blk.ko blkdev=/dev/sdb2
或者
$ cat /proc/cmdline
.... pstore_blk.blkdev=179:6 ...
如果是mtd
設備,可以直接指定mtd
分區名或者編號,例如:
pstore_blk.blkdev=pstore # 假設存在名爲pstore的MTD分區
OK,對新同學來說,到這裏配置就夠了。可以從我的github
上看到我之前是怎麼測試的。如果需要知道每個配置項的作用,還是看內核文檔吧(ramoops.rst 或 pstore_blk.rst),或者在Kconfig
中按h
顯示相關配置項的說明。
掛載
在使能且正確配置設備後,啓動的時候應該會有這樣的日誌:
pstore_zone: registered pstore_blk as backend for kmsg(Oops,panic_write)
pstore: Registered pstore_blk as persistent store backend
這代表pstore
找到了設備且正常註冊。接下來,我們還需要通過掛載的形式觸發pstore
從設備讀取數據。常見的掛載是這樣的:
mount -t pstore pstore /sys/fs/pstore
掛載後,通過mount
能看到類似這樣的信息:
# mount
...
pstore on /sys/fs/pstore type pstore (rw,relatime)
...
如果曾經觸發過崩潰日誌,在掛載點應該有類似這樣的文件:
# ll /sys/fs/pstore
...
-r--r--r-- 1 root root 15521 Jan 1 00:06 dmesg-pstore_blk-0
...
如果需要驗證,咱們可以這樣主動觸發內核崩潰:
# echo c > /proc/sysrq-trigger
我是在U盤、SD卡、mmc、nand上驗證的,maintainer Kees Cook 提供了另外一種基於loop
的驗證方法,實現用文件模擬塊設備。當然這方法不適用於轉存Panic
日誌,只能用於Oops
或者其他前端:
# insmod pstore.ko compress=off
# insmod pstore_zone.ko
# truncate pstore-blk.raw --size 100M
# losetup -f --show pstore-blk.raw
/dev/loop0
# insmod pstore_blk.ko blkdev=/dev/loop0 kmsg_size=16 console_size=64 best_effort=on
讀取
經過上述的掛載後,可以在掛載點看到轉存的日誌文件。既然是文件,肯定支持文件的一系列操作,例如讀取、刪除。
root@TinaLinux:/sys/fs/pstore# head -n 10 dmesg-pstore_blk-1
Oops: Total 2 times
Oops#1 Part1
<6>[ 2.743794] Bluetooth: RFCOMM socket layer initialized
<6>[ 2.743813] Bluetooth: RFCOMM ver 1.11
<6>[ 2.743822] 8021q: 802.1Q VLAN Support v1.8
<3>[ 2.751766] reg-virt-consumer reg-virt-consumer.1: Failed to obtain supply 'drivevbus': -517
<3>[ 2.752330] reg-virt-consumer reg-virt-consumer.1: Failed to obtain supply 'drivevbus': -517
<5>[ 2.752742] ubi0: attaching mtd4
<5>[ 2.890302] random: crng init done
<5>[ 2.965927] ubi0: scanning is finished
root@TinaLinux:/sys/fs/pstore# ll
drwxr-x--- 2 root root 0 Jan 1 00:11 .
drwxr-xr-x 5 root root 0 Jan 1 00:11 ..
-r--r--r-- 1 root root 15521 Jan 1 00:06 dmesg-pstore_blk-0
-r--r--r-- 1 root root 15128 Jan 1 00:11 dmesg-pstore_blk-1
root@TinaLinux:/sys/fs/pstore# rm dmesg-pstore_blk-1
root@TinaLinux:/sys/fs/pstore# ll
drwxr-x--- 2 root root 0 Jan 1 00:13 .
drwxr-xr-x 5 root root 0 Jan 1 00:11 ..
-r--r--r-- 1 root root 15521 Jan 1 00:06 dmesg-pstore_blk-0
對dmesg
前端的Panic/Oops
日誌,pstore
會自動添加兩行統計信息。例如:
Oops: Total 2 times # 表示觸發了Oops,且是自系統安裝後第一次啓動以來第2次觸發Oops。
Oops#1 Part1 # 表示這是上一次運行期間第1次觸發Oops的日誌。
可以發現,第一行是累計總的觸發次數,第二行是上一次啓動觸發的次數。
每個文件名的格式都是<前端名>-<後端名>-<id>
,例如dmesg-pstore_blk-1
表示dmesg
前端,pstore_blk
後端以及是dmesg
前端的第1個zone
的日誌。
當然,除了dmesg
前端外,其他前端的名字大概是這樣的:
# ll
-r--r--r-- 1 root root 31 1月 15 11:53 console-pstore-blk-0
-r--r--r-- 1 root root 3666 1月 15 11:53 demsg-pstore-blk-0
-r--r--r-- 1 root root 65524 1月 15 11:53 ftrace-pstore-blk-0
-r--r--r-- 1 root root 9 1月 15 11:53 pmsg-pstore-blk-0
除此之外,每個文件的時間戳表示 崩潰觸發的時間。上例中,由於系統並沒有實現同步更新系統時間,所以時間戳不合理。
展望未來
正如我前文說的,pstore
在物聯網設備逐漸普及的現在,能發揮很大的作用,例如智能音箱和掃地機已經用起來了。
全功能支持
到目前爲止,不管是塊設備還是mtd
設備,社區的代碼都沒能做到pstore
的全部前端的支持。
設備 | dmesg(Oops) | dmesg(Panic) | pmsg | console | ftrace |
---|---|---|---|---|---|
塊設備 | Y | N | Y | Y | Y |
MTD設備 | Y | Y | N | N | N |
ram設備 | Y | Y | Y | Y | Y |
塊設備如果需要記錄Panic
日誌,需要提供一個在Panic
時寫塊設備的接口。我在全志的mmc和nand驅動中實現了這樣的接口,卻因爲種種原因不適合提交到社區。社區塊驅動的適配寄希望於更多同學的努力了。
MTD設備很早前就有了panic_write()
的定義,因此可以支持Panic
日誌轉存。不支持其他前端,則是因爲其擦寫的物理特性。對pmsg
,console
,ftrace
等這些不能頁對齊寫入的前端,還需要更多的適配工作。
遷移pstore/ram
在當前pstore
的目錄結構是這樣的:
$ tree fs/pstore
fs/pstore/
├── blk.c # pstore/blk 後端的實現
├── ftrace.c # ftrace 前端的實現
├── inode.c # pstore 文件系統的註冊與操作
├── internal.h
├── Kconfig
├── Makefile
├── platform.c # pstore 前後端功能的核心
├── pmsg.c # pmsg 前端的實現
├── ram.c # pstore/ram 後端的實現
├── ram_core.c # pstore/ram 後端的實現
└── zone.c # pstore/zone 實現存儲空間的分配和管理
在我的補丁之前,只支持轉存日誌到ram
,因此如果研讀代碼,我們會發現ram.c
和ram_core.c
實現了兩部分功能:
- dram空間分配與管理
- dram的讀寫操作
我實現的blk.c
支持了轉存到塊設備。但是後來發現不管pstore/ram
還是pstore/blk
,他們對於存儲空間的分配和管理極度相似,我就提煉出了pstore/zone
。於是乎,期望的代碼層次應該是這樣的:
pstore/ram
要整合入pstore/zone
已經與maintainer達成共識,但還需要更多同學一同努力做更多兼容,例如ecc
的支持。