1、oops
内核通过oops告知用户有错误的事件发生。这个过程包括:向终端上输出错误信息、输出寄存器中保存的信息和输出可供跟踪的回溯线索。通常,内核发送完oops之后会处于一种不稳定状态。当故障发生时,内核必须适当地从当前的上下文环境中退出并尝试回复对系统的控制。多数时候这种尝试都会失败:如果oops发生在中断上下文中时,内核会陷入混乱(死机);如果oops在idle进程(pid为0)或init进程(pid为1)时发生,内核也会陷入混乱,因缺缺少这两个进程的内核无法正常工作;如果oops发生在其他进程运行期间时,内核就会杀死进程并尝试继续执行。
oops产生的原因可能有很多,包括内存访问越界、非法指令等。
PPC机器上tulip网卡的定时器处理函数运行时发生的oops实例:
oops列出了现场的寄存器信息还有回溯线索。
回溯线索显示了导致错误发生的函数调用链(倒着看):
- 机器处于空间状态,执行idle循环,由cpu_idle() 循环调用default_idle()。此时定时器中断发生了,调用了tulip_timer()这个定时器处理函数,定时器处理函数中引用了空指针。(有时可以从后面的偏移量中找出导致问题的语句)
1.1、ksymoops
上面打印好的oops信息都是已经解码好的。如果时未解码的信息则需要通过ksymoops命令进行解码,并且还必须提供编译内核时产生的system.mao文件,如果使用的是模块,还需要提供一些模块的信息。
未解码时oops信息如下:
这些信息还未转换成对应的函数。
1.2、kallsyms
内核配置中存在CONFIG_KALLSYMS配置选项,该选项存放着内核镜像中相应函数地址的符号名称,内核可以直接打印解码好的跟踪信息。因此,解码oops信息不再需要system.map或者ksymoops工具,但是这样做会使得内核变大(因为函数的地址和符号名称的映射必须永久驻留在内核所映射的内存地址上)。
CONFIG_KALLSYMS_ALL表示不仅仅存访函数名称,还存放所有的符号名称。
CONFIG_KALLSYMS_EXTRA_PASS选项会引起内核构建过程中再次忽略内核的目标代码。这个选项只有在调试kallsyms本身时使用。
make menuconfig
General setup —>
[*] Configure standard kernel features (for small systems) —>
(选中此项,才有/proc/kallsyms接口文件, oops问题,选中此选项即可,子选项可以忽略)
[*] Load all symbols for debugging/ksymoops
[*] Include all symbols in kallsyms
[*] Do an extra kallsyms pass
2、引发bug打印
BUG() 和BUG_ON() 提供了引发oops打印出错信息的能力。可以把这些调用当作断言使用:
if(bad_thing)
BUG();
/* 或者 */
BUG_ON(bad_thing);
还可以用panic() 引发更严重的错误。调用panic() 不但会打印错误消息,而且还会挂起整个系统,因此,只能在最糟糕的情况下使用:
if(terrible_thing)
panic("terrible thing is %ld\n",terrible_thing);
如果只需要在终端上打印一下栈信息来回溯信息帮助调试。那么可以使用dump_stack() ,它只在终端上打印寄存器上下文和函数的跟踪线索:
dump_stack()
3、探测系统
3.1、用UID做条件选择
当在系统中测试自己修改的特性时,为了确保系统的稳定性,可以用UID作为条件选择来实现这种功能,通过这种条件选择可以安排到底执行那种特性:
if(current->uid != xxxx){
//执行旧的特性
}else{
//执行新的特性
}
3.2、重复频率限制
有时候在开发过程中会在代码中加入printk() 函数去检查错误,但是如果加入printk() 函数的代码被调用很多次的话会给系统增加很大的负担。有两种技巧来防止这种问题的发生:
- (1)、为了避免调试信息过多,可以以一定间隔进行打印:
static unsigned long prev_jiffy = jiffies;
if(time_after(jiffies,prev_jiffy + 2*HZ)){ //2秒打印一次
prev_jiffy = jiffies;
printk("bala bala bala!\n");
}
- (2)、通过printk的特殊限制函数去打印:
if(error && printk_ratelimit())
printk("bala bala bala bala!\n");
这样子,默认打印速率是每5秒最多打印10条。也可以通过 /proc/sys/kernel/printk_ratelimit和 /proc/sys/kernel/printk_ratelimit_burst分别修改时间间隔和时间间隔期间的打印数目。