嵌入式Linux Kernel错误跟踪技术
2 LCRT机制的设计与实现
通过对Linux内核代码的分析可知,Linux内核本身提供了一种“内核通知机制”[7-8],并预定义了“内核事件通知链”,使得Linux内核扩展开发人员可以通过这些预定义的内核事件通知链在特定的内核事件发生时执行附加的处理流程。通过对Linux内核源代码的研究发现,对于上文中提到的“严重不可恢复的内核异常”,预定义了一个通知链和通知点,使得在发生Linux内核崩溃之后,可以在Linux内核的panic函数中预定义的一个“内核崩溃通知链”[7]上挂接LCRT机制来获得Linux内核崩溃现场的一些信息并记录到非易失性存储器中,以便分析引起Linux内核崩溃的原因。
2.1 设计要点
LCRT机制的设计和实现基于如下特定的机制:
(1) 编译器选项与内核依赖
Linux内核及相应的驱动程序都采用GNU[9]的开源编译器GCC[9]编译,为了结合LCRT机制方便地提取信息和记录信息,需要采用特定的GCC编译器选项来编译Linux内核和相关的驱动程序以及应用程序。用到的选项为:-mpoke-function-name[9]。使用这个选项编译出的二进制程序中可以包含C语言函数名称的信息,以方便函数调用链回溯时记录信息的可读性。
(2) Linux内核notify_chain机制[8]
Linux内核提供“通知链”功能,并预定义了一个内核崩溃通知链,在Linux内核的异常处理例程中判断出系统进入“不可恢复”状态时,会沿预定义的通知链顺序调用注册到相应链中的通知函数。
(3) 函数调用的栈布局
Linux内核的绝大部分由C语言实现,而且C语言也多用来进行Linux内核开发。Linux内核及使用LKM扩展而加入Linux内核执行环境的代码是有规律可循的,这些代码在执行过程中产生的栈布局和这些规律的代码相关联。例如,这些函数在执行函数之前会保存本函数调用后的返回地址、本函数被调用时传递过来的参数及调用本函数的函数所拥有的栈帧的栈底。
2.2 LCRT机制的设计思想
LCRT机制分为Linux内核模块[8]部分和Linux用户程序部分。内核模块部分的设计采用了Linux内核模块的模式而不是直接修改Linux内核。这样的设计降低了Linux内核和LCRT机制之间的耦合度,同时满足了Linux内核和LCRT机制独立升级完善的便利性。用户程序部分完成从非易失性存储器中读取、清除LCRT机制保存的信息等相关功能。
在LCRT机制的设计中,针对嵌入式系统的特点,其设计决策有:
(1) 将对于解决和定位问题最具辅助意义的函数调用关系链记录下来。
(2) 为了不占用过多的存储空间,有选择性地将函数调用序列上的函数各自用到的栈内容保存起来,而不是保存全部内容。
(3) 将记录的信息保存到非易失性存储器中,这样既达到了掉电保存的目的、又缩短了写入时间。
LCRT机制的设计包括以下五个方面。
(1) 设计Linux内核模块、动态地加载LCRT机制、尽量少地修改Linux内核代码。
(2)在相应、预定义的Linux内核通知链上挂接LCRT的通知函数。
(3) 在LCRT机制的通知处理函数中进行堆栈回溯得到函数调用信息。
(4) 记录回溯到的函数调用信息和堆栈空间内容到非易失性存储器。
(5) 开发用户空间的工具,可以从非易失性存储器中读取保存的信息。
2.3 LCRT机制的实现
LCRT机制的实现可参照2.2节的设计思想,分步予以实现。限于篇幅,本文不过多涉及Linux内核模块的原理和实现相关的细节,仅仅给出LCRT机制的内核模块实现伪代码。用伪代码描述LCRT机制的加载函数如下:
int lcrt_init(void)
{
printk("Registering my__panic notifier.");
bt_nvram_ptr=(volatile unsigned char*)ioremap_
nocache (BT_NVRAM_BASE,BT_NVRAM_LENGTH);
bt_nvram_index+=sizeof(struct bt_info);
*)bt_nvram_ptr,BT_NVRAM_LENGTH);
notifier_chain_register(panic_notifier_list,my_
panic_block);
return 0;
}
LCRT机制的通知处理函数完成函数调用关系回溯、得到函数名称、函数栈内容等工作,限于篇幅,在这里用下面伪代码说明:
void ll_bt_information(struct pt_regs *pr)
{
变量定义等初始化工作
do {
reglist=*(unsigned long *)(*myfp-8);
//从函数栈帧的顶部获取函数开始执行时保存的寄存器信息
//从函数的代码区中取得函数的名称
//从函数的栈帧里取出函数执行函数体代码之前保存的函数参数信息
//从本函数的栈帧中得到调用本函数的代码所在位置和调用本函数的函数栈帧的栈底
}while(直到函数调用链的链头);
linux操作系统文章专题:linux操作系统详解(linux不再难懂)
评论