Linux pstore 實(shí)現(xiàn)自動(dòng)“抓捕”內(nèi)核崩潰日志
我設(shè)計(jì)的內(nèi)核模塊pstore/blk及其衍生的pstore/zone,mtdpstore終于在v5.8-rc1版本合入了torvalds/linux.git(見(jiàn)參考鏈接[1]),而且發(fā)現(xiàn)國(guó)內(nèi)外對(duì)pstore的介紹都好少好少,干脆來(lái)一波科普。
簡(jiǎn)介
pstore文件系統(tǒng)(是的,這是個(gè)文件系統(tǒng))是Persistent Storage的縮寫,最早在2010年由 Tony Luck 設(shè)計(jì)并合入Linux主分支,設(shè)計(jì)的初衷是在內(nèi)核Panic/Oops時(shí)能自動(dòng)轉(zhuǎn)存內(nèi)核日志(log_buf),在Panic重啟后,把轉(zhuǎn)存的日志以文件形式呈現(xiàn)到用戶空間以分析內(nèi)核崩潰問(wèn)題。
這對(duì)分析那種小概率且沒(méi)辦法抓到現(xiàn)場(chǎng)的問(wèn)題非常實(shí)用,尤其是現(xiàn)在智能互聯(lián)網(wǎng)的設(shè)備逐漸普及的時(shí)候,遠(yuǎn)端的設(shè)備可以自己捕抓崩潰日志再通過(guò)網(wǎng)絡(luò)傳輸?shù)椒?wù)器,維護(hù)人員就可以根據(jù)收集來(lái)的日志定位和解決問(wèn)題,然后通過(guò)OTA讓設(shè)備升級(jí)迭代。
根據(jù)網(wǎng)上搜尋的資料,在pstore文件系統(tǒng)之前其實(shí)有不少類似的實(shí)現(xiàn)。
- apanic
Android最早的panic信息記錄的方案。在linux 2.6的安卓的內(nèi)核中找到,卻沒(méi)有提交到社區(qū),后來(lái)被放棄維護(hù)了。網(wǎng)上找不到放棄的原因,我自己猜測(cè)是因?yàn)槠渲贿m用于mtd nand,然而現(xiàn)在的Android基本用的都是emmc。apanic應(yīng)該是Android Panic的縮寫吧,可以實(shí)現(xiàn)在內(nèi)核崩潰時(shí),把日志轉(zhuǎn)存到mtd nand。
- ramoops
這里指的是最早的ramoops實(shí)現(xiàn),在最新代碼已經(jīng)整合入pstore中,以pstore/ram的后端形式存在。ramoops可以把日志轉(zhuǎn)存到重啟不掉電的ram中。這里對(duì)ram有一點(diǎn)要求,即使重啟ram的數(shù)據(jù)也不能丟失。
- crashlog
這是openwrt提供的內(nèi)核patch,并沒(méi)有提交到內(nèi)核社區(qū)。它也是基于ram,只能轉(zhuǎn)存Panic/Oops的日志。
- mtdoops
MTD子系統(tǒng)支持的功能,與pstore非常相似,只支持轉(zhuǎn)存Panic/Oops日志,不能以文件呈現(xiàn),需要用戶自行解析整個(gè)MTD分區(qū)。(因?yàn)楣δ艿南嗨?,我?shí)現(xiàn)了mtdpstore用于替代mtdoops)
- kdump
如果說(shuō)pstore是個(gè)輕量級(jí)的內(nèi)核崩潰日志轉(zhuǎn)存的方案,kdump則是一個(gè)重量級(jí)的問(wèn)題分析工具。在崩潰時(shí),由kdump產(chǎn)生一個(gè)用于捕抓當(dāng)前信息的內(nèi)核,該內(nèi)核會(huì)收集內(nèi)存所有信息到dump core文件中。在重啟后,捕抓到的信息保存在特定的文件中。類似的還有netdump和diskdump。kdump的方案適用于服務(wù)器這種有大量資源的設(shè)備,功能也非常強(qiáng)大,但對(duì)嵌入式設(shè)備非常不友好。
pstore經(jīng)過(guò)長(zhǎng)期迭代,除了轉(zhuǎn)存Panic/Oops的日志之外(dmesg前端),還支持pmsg、console和ftrace的前端,除了pstore/ram的后端之外,還有我設(shè)計(jì)的pstore/blk后端,除了支持轉(zhuǎn)存到ram之外,還有block device和mtd device。
pstore的前端,是指轉(zhuǎn)存的日志類型,pstore的后端,是指轉(zhuǎn)存到什么類型的設(shè)備。
目前支持以下幾個(gè)前端:
- dmesg:主要是轉(zhuǎn)存Panic/Oops時(shí)log_buf里面的內(nèi)核日志
- pmsg:提供給用戶空間存儲(chǔ)日志的入口,在Android里有看到被用于存儲(chǔ)系統(tǒng)的日志。
- console:終端日志
- ftrace:function trace的信息
目前支持以下幾種后端:
- pstore/ram:Persistent Ram,重啟不會(huì)丟數(shù)據(jù)的內(nèi)存
- pstore/blk:(v5.8以后的版本)所有可寫的塊設(shè)備,例如磁盤、U盤、emmc、NFTL nand等
- mtd device:(v5.8以后的版本)mtd設(shè)備,例如 mtd nand。(mtd設(shè)備的支持依賴于 pstore/blk 后端,準(zhǔn)確來(lái)說(shuō)不是一種獨(dú)立后端)
怎么用
就像把大象裝入冰箱只需要打開(kāi)冰箱,把大象放進(jìn)去,關(guān)上冰箱門的3個(gè)步驟,使用pstore也只需要3個(gè)步驟:
- 使能 pstore
- 掛載 pstore文件系統(tǒng)
- 讀取 轉(zhuǎn)存的日志文件
詳細(xì)的說(shuō)明可以看源碼上的文檔,本文只做基本功能的介紹。
Documentation/admin-guide/ramoops.rst
Documentation/admin-guide/pstore-blk.rst
使能
在menuconfig中選擇內(nèi)核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 后端
上述兩個(gè)后端2選1即可,前端就根據(jù)自己的需求選擇,至于dmesg前端,默認(rèn)使能沒(méi)得選。如果希望用在mtd設(shè)備上,還需要選擇mtdpstore模塊:
- $ make menuconfig
- |-> Device Drivers
- |-> Memory Technology Device (MTD) support
- |-> Log panic/oops to an MTD buffer based on pstore
選上就可以用了?雖然我非常想說(shuō)“是的”,但事實(shí)卻有點(diǎn)“骨感”。即使所有前端都使用默認(rèn)配置,pstore/ram至少也需要知道可用的內(nèi)存范圍吧?pstore/blk至少也需要知道使用哪個(gè)塊設(shè)備吧?
pstore/ram支持 模塊參數(shù)(cmdline)、設(shè)備樹(shù)、和Platform Data的3種配置方式,從代碼來(lái)看,優(yōu)先級(jí)關(guān)系是:模塊參數(shù) > Platform Data > 設(shè)備樹(shù)。
pstore/blk支持 Kconfig和 模塊參數(shù)(cmdline)的兩種配置方式,且模塊參數(shù)比Kconfig有更高的優(yōu)先級(jí)。
pstore/ram我接觸也不多,直接介紹pstore/blk的使用方法。對(duì)新同學(xué)來(lái)說(shuō),請(qǐng)忽略一大堆亂七八糟的屬性配置(使用默認(rèn)值),只需要告訴pstore/blk后端使用哪個(gè)塊設(shè)備即可。
在Kconfig中配置:
- $ make menuconfig
- |-> File systems
- |-> Miscellaneous filesystems
- |-> Persistent store support
- |-> Log panic/oops to a block device # pstore/blk 后端
- |-> () block device identifier # 使用哪個(gè)塊設(shè)備?
如果使用cmdline,可以這么寫:
- pstore_blk.blkdev=XXXX
或者以模塊加載:
- $ sudo insmod pstore_blk.ko blkdev=XXX
這里的塊設(shè)備可以是代表整個(gè)磁盤的sda,也可以是代表某個(gè)分區(qū)的mmcblk0p4。雖然支持7種變體,但常用的還是兩種:
- /dev/<disk_name>: 例如,使用U盤的第2個(gè)分區(qū),則是/dev/sdb2
- <major>:<minor>:例如,mmc設(shè)備第6個(gè)分區(qū),則是179:6
形式大概是這樣:
- $ sudo insmod pstore_blk.ko blkdev=/dev/sdb2
或者
- $ cat /proc/cmdline
- .... pstore_blk.blkdev=179:6 ...
如果是mtd設(shè)備,可以直接指定mtd分區(qū)名或者編號(hào),例如:
- pstore_blk.blkdev=pstore # 假設(shè)存在名為pstore的MTD分區(qū)
OK,對(duì)新同學(xué)來(lái)說(shuō),到這里配置就夠了??梢詮奈业膅ithub(見(jiàn)參考鏈接[2])上看到我之前是怎么測(cè)試的。如果需要知道每個(gè)配置項(xiàng)的作用,還是看內(nèi)核文檔吧(ramoops.rst 或 pstore_blk.rst),或者在Kconfig中按h顯示相關(guān)配置項(xiàng)的說(shuō)明。
掛載
在使能且正確配置設(shè)備后,啟動(dòng)的時(shí)候應(yīng)該會(huì)有這樣的日志:
- pstore_zone: registered pstore_blk as backend for kmsg(Oops,panic_write)
- pstore: Registered pstore_blk as persistent store backend
這代表pstore找到了設(shè)備且正常注冊(cè)。接下來(lái),我們還需要通過(guò)掛載的形式觸發(fā)pstore從設(shè)備讀取數(shù)據(jù)。常見(jiàn)的掛載是這樣的:
- mount -t pstore pstore /sys/fs/pstore
掛載后,通過(guò)mount能看到類似這樣的信息:
- # mount
- ...
- pstore on /sys/fs/pstore type pstore (rw,relatime)
- ...
如果曾經(jīng)觸發(fā)過(guò)崩潰日志,在掛載點(diǎn)應(yīng)該有類似這樣的文件:
- # ll /sys/fs/pstore
- ...
- -r--r--r-- 1 root root 15521 Jan 1 00:06 dmesg-pstore_blk-0
- ...
如果需要驗(yàn)證,咱們可以這樣主動(dòng)觸發(fā)內(nèi)核崩潰:
- # echo c > /proc/sysrq-trigger
我是在U盤、SD卡、mmc、nand上驗(yàn)證的,maintainer Kees Cook 提供了另外一種基于loop的驗(yàn)證方法,實(shí)現(xiàn)用文件模擬塊設(shè)備。當(dāng)然這方法不適用于轉(zhuǎn)存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
讀取
經(jīng)過(guò)上述的掛載后,可以在掛載點(diǎn)看到轉(zhuǎn)存的日志文件。既然是文件,肯定支持文件的一系列操作,例如讀取、刪除。
- 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
對(duì)dmesg前端的Panic/Oops日志,pstore會(huì)自動(dòng)添加兩行統(tǒng)計(jì)信息。例如:
- Oops: Total 2 times # 表示觸發(fā)了Oops,且是自系統(tǒng)安裝后第一次啟動(dòng)以來(lái)第2次觸發(fā)Oops。
- Oops#1 Part1 # 表示這是上一次運(yùn)行期間第1次觸發(fā)Oops的日志。
可以發(fā)現(xiàn),第一行是累計(jì)總的觸發(fā)次數(shù),第二行是上一次啟動(dòng)觸發(fā)的次數(shù)。
每個(gè)文件名的格式都是<前端名>-<后端名>-
當(dāng)然,除了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
除此之外,每個(gè)文件的時(shí)間戳表示 崩潰觸發(fā)的時(shí)間。上例中,由于系統(tǒng)并沒(méi)有實(shí)現(xiàn)同步更新系統(tǒng)時(shí)間,所以時(shí)間戳不合理。
展望未來(lái)
正如我前文說(shuō)的,pstore在物聯(lián)網(wǎng)設(shè)備逐漸普及的現(xiàn)在,能發(fā)揮很大的作用,例如智能音箱和掃地機(jī)已經(jīng)用起來(lái)了。
全功能支持
到目前為止,不管是塊設(shè)備還是mtd設(shè)備,社區(qū)的代碼都沒(méi)能做到pstore的全部前端的支持。
設(shè)備 | dmesg(Oops) | dmesg(Panic) | pmsg | console | ftrace |
---|---|---|---|---|---|
塊設(shè)備 | Y | N | Y | Y | Y |
MTD設(shè)備 | Y | Y | N | N | N |
ram設(shè)備 | Y | Y | Y | Y | Y |
塊設(shè)備如果需要記錄Panic日志,需要提供一個(gè)在Panic時(shí)寫塊設(shè)備的接口。我在全志的mmc和nand驅(qū)動(dòng)中實(shí)現(xiàn)了這樣的接口,卻因?yàn)榉N種原因不適合提交到社區(qū)。社區(qū)塊驅(qū)動(dòng)的適配寄希望于更多同學(xué)的努力了。
MTD設(shè)備很早前就有了panic_write()的定義,因此可以支持Panic日志轉(zhuǎn)存。不支持其他前端,則是因?yàn)槠洳翆懙奈锢硖匦?。?duì)pmsg,console,ftrace等這些不能頁(yè)對(duì)齊寫入的前端,還需要更多的適配工作。
遷移pstore/ram
在當(dāng)前pstore的目錄結(jié)構(gòu)是這樣的:
- $ tree fs/pstore
- fs/pstore/
- ├── blk.c # pstore/blk 后端的實(shí)現(xiàn)
- ├── ftrace.c # ftrace 前端的實(shí)現(xiàn)
- ├── inode.c # pstore 文件系統(tǒng)的注冊(cè)與操作
- ├── internal.h
- ├── Kconfig
- ├── Makefile
- ├── platform.c # pstore 前后端功能的核心
- ├── pmsg.c # pmsg 前端的實(shí)現(xiàn)
- ├── ram.c # pstore/ram 后端的實(shí)現(xiàn)
- ├── ram_core.c # pstore/ram 后端的實(shí)現(xiàn)
- └── zone.c # pstore/zone 實(shí)現(xiàn)存儲(chǔ)空間的分配和管理
在我的補(bǔ)丁之前,只支持轉(zhuǎn)存日志到ram,因此如果研讀代碼,我們會(huì)發(fā)現(xiàn)ram.c和ram_core.c實(shí)現(xiàn)了兩部分功能:
- dram空間分配與管理
- dram的讀寫操作
我實(shí)現(xiàn)的blk.c支持了轉(zhuǎn)存到塊設(shè)備。但是后來(lái)發(fā)現(xiàn)不管pstore/ram還是pstore/blk,他們對(duì)于存儲(chǔ)空間的分配和管理極度相似,我就提煉出了pstore/zone。于是乎,期望的代碼層次應(yīng)該是這樣的:

pstore/ram要整合入pstore/zone已經(jīng)與maintainer達(dá)成共識(shí),但還需要更多同學(xué)一同努力做更多兼容,例如ecc的支持。
參考
https://git.kernel.org/torvalds/c/829f3b9401fe7cc3c1f3642bb2520751a42a87dfhttps://github.com/gmpy/articles/blob/master/pstore/Test-Pstore-Block.md
本文轉(zhuǎn)載自微信公眾號(hào)「 Linux閱碼場(chǎng)」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系 Linux閱碼場(chǎng)公眾號(hào)。