需求:
Linux发生panic之后,如果/proc/sys/kernel/panic
没有值的话,会一直停留在panic的界面。如果有值,则会自动重启。在一般的客户版本中,一般都会设置成自动重启,但这样的话就无法查看重启的原因了。
为了调查问题,经常需要保留现场。因此,希望在重启之前把相应的信息以文件的形式保存下来。但是如果是文件系统发生panic,这个方案明显不行。我们可以将现场的信息暂时保留在内存里面,重启的话再从内存中将信息以log的形式保存下来。
所要解决的问题
按照上述的方案,我们需要解决以下几个问题:
(1)、panic的信息保存在内存哪个位置?什么时候申请,怎么申请大块内存区域?
(2)、怎么获取到发生panic的信息?
(3)、发生panic后重启的代码位置在哪?
(4)、重启之后判断哪个标记是否需要保存log?
(5)、应用层怎么读取信息?
xxx_lkcd
(xxx project, Linux Kernel Crash Dump)来表示。解答
(1)、这里涉及到内存的layout,要明确信息保存在哪个地址,保存的大小,地址要唯一,并且这部分的内存区域要reserve,不能被其他使用。
保存的地址为:#define XXX_LKCD_RESERVE_AREA_ADDRESS 0xb9c00000
,内存区域大小为3M。
为了要保证这3M的内存区域不被使用,要在kernel一起来就要向内核进行申请保留,因此在DT_MACHINE_START()
中.reserve
对应的函数去申请内存区域。注:
- reserve 3M的地址有讲究,不能reserve到DDR最后的地址处。因为在uboot中,会有个relocate的动作,将会使用到高端地址,有可能会被覆盖掉;
- 使用dts的kernel中支持往
reserve_memory
设备节点直接reserve memory,无需再去代码中reserve。也就是说,不一定需要再调用xxx_lkcd_reserve()
函数。
(2)、对于驱动工程师而言,获取panic信息主要就是kernel log,也就是printk的log。
内核提供了一个syslog_print_all()
的函数,通过该接口应用层可以获取kernel log的信息。函数定义如下:<kernel_dir>/kernel/printk/printk.c
1 | /* |
怎么办呢?这个接口是供应用层使用的,内核怎么用?
其实很简单,完全拷贝syslog_print_all()
函数的内容为dump_logbuffer()
,将其buf保存在kernel就可以,不用传到应用层即可。
(3)、发生panic,会去调用DT_MACHINE_START()
所指定的.restart
对应的函数。
因此,我们可以在kernel panic发生之后,给.restart
对应的函数传递特定的参数,根据参数判断是正常重启还是由于kernel panic重启。
如果是由于kernel panic重启的话,可以做一些保护现场的操作,也就是保存信息到指定的内存区域中去。
(4)、重启之后,判断是否需要保存log是通过申请到的3M内存区域的前4个字节(magic)
字段,如果这个字段为XXX_LKCD_MAGIC
,那么驱动中需要创建/proc
接口供应用程序读写并保存log到文件系统中去。
(5)、如第(4)步所说的,应用程序通过/proc
接口来获取数据并以文件的形式保存起来。
详细流程图
代码实现
在Kernel_SrcDir/drivers/
目录下创建xxx_lkcd/
的目录文件,在里面添加如下文件:
Makefile
Kconfig
xxx_lkcd.c
:实现大块内存区域的申请以及重启之前的信息保存动作;xxx_lkcd.h
:定义内存区域大小、文件名等内容;xxx_lkcd_proc.c
: 创建/proc/xxx_lkcd/
接口供应用层读取数据
(1)、Makefile
1 | obj-$(CONFIG_XXX_LKCD) += xxx_lkcd.o |
(2)、Kconfig
1 | # |
(3)、xxx_lkcd.c
1 |
|
(4)、xxx_lkcd.h
1 |
|
(5)、xxx_lkcd_proc.c
1 |
|
详细步骤
(1)、添加DT_MACHINE_START()
指定的.reserve
和.restart
对应的函数,此处以i.MX6
平台为例。
在arch/arm/mach-imx/board-xxx.c
文件的末尾都会定义如下的结构,用于与dts
中指定的compatible
进行匹配,匹配完毕之后就会执行其相应的内容:
1 | DT_MACHINE_START(IMX6Q, "Freescale i.MX6 Quad/DualLite (Device Tree)") |
在机器启动的时候会执行
.init_machine
对应的函数imx6q_init_machine()
和.reserve
对应的函数mx6q_reserve()
。
因此,我们可以在mx6q_reserve()
中事先去申请一块大块的内存用于保存log,这块内存在一开机就申请,不让其他程序使用。也就是说在mx6q_reserve()
中调用xxx_lkcd_reserve()
函数。在机器重启之前,会去执行
.restart
对应的函数imx6q_restart()
。
因此,我们可以在kernel panic发生之后,给imx6q_restart()
函数传递特定的参数,根据参数判断是正常重启还是由于kernel panic重启,如果是由于kernel panic 重启的话,可以做一些保护现场的操作。
因此,在imx6q_restart()
中调用xxx_lkcd_save()
函数即可。
(2)、xxx_lkcd_reserve()
函数用来申请3M的内存区域,起始地址为xxx_lkcd_mem.pstart
,这是真正的内存物理地址,大小为 xxx_lkcd_mem.psize。在执行xxx_lkcd_proc_init()
内容的时候会去获取到虚拟地址,如下:
1 | xxx_lkcd_mem.vstart = (unsigned long)ioremap_nocache(xxx_lkcd_mem.pstart, xxx_lkcd_mem.psize); |
因此我们就可以将现场的信息写入到虚拟地址xxx_lkcd_mem.vstart
指定的区域中去。可以通过#cat /proc/iomem
查看地址分配的情况。
(3)、xxx_lkcd_save()
是用来保护现场信息的,将其内容写入到struct xxx_lkcd_area_info
中去,总共3M大小。
1 | struct xxx_lkcd_area_info { |
(4)、我们会固定的去加载xxx_lkcd_proc
驱动,也就是:module_init(xxx_lkcd_proc_init);
在这个驱动中会去固定创建一个/proc/xxx_lkcd/control
控制接口,可以通过该接口模拟发生panic或者控制3M的内存区域。之后根据xxx_lkcd_area->magic
字段的值来判断是否有panic发生,如果有panic发生,那么就创建通常的读写接口给应用程序保存现场信息。比如说/proc/xxx_lkcd/logger_kernel
来保存kernel 的log信息。
(5)、驱动中提供给应用程序读取的接口为:xxx_lkcd_proc_read()
。
应用层测试程序
应用层首先会创建文件用于保存从/proc/xxx_lkcd/logger_kernel
接口读取到的数据,也就是XXX_LKCD_MakeDir()
。XXX_LKCD_SaveFile()
用于从/proc
接口读取数据并写入到创建的文件中。xxx_lkcd_test.c
的内容如下:
1 |
|