expect 中trap可以在一个脚本中是否停止此脚本恢复吗

Expect学习笔记
扫扫二维码,随身浏览文档
手机或平板扫扫即可继续访问
Expect学习笔记
举报该文档含有违规或不良信息。
反馈该文档无法正常浏览。
举报该文档为重复文档。
推荐理由:
您的内容已经提交成功
您所提交的内容需要审核后才能发布,请您等待!
3秒自动关闭窗口页面导航:
→ 正文内容 写健壮Shell脚本技巧
写出健壮Bash Shell脚本的一些技巧总结
这篇文章主要介绍了写出健壮Bash Shell脚本的一些技巧总结,本文总结了set -u、set -e、竟态条件、信号描述等内容,需要的朋友可以参考下
许多人用Shell脚本完成一些简单任务,而且变成了他们生命的一部分。不幸的是,shell脚本在运行异常时会受到非常大的影响。在写脚本时将这类问题最小化是十分必要的。本文中我将介绍一些让bash脚本变得健壮的技术。
使用set -u
你因为没有对变量初始化而使脚本崩溃过多少次?对于我来说,很多次。
rm -rf $chroot/usr/share/doc
如果上面的代码你没有给参数就运行,你不会仅仅删除掉chroot中的文档,而是将系统的所有文档都删除。那你应该做些什么呢?好在bash提供了set -u,当你使用未初始化的变量时,让bash自动退出。你也可以使用可读性更强一点的set -o nounset。
david% bash /tmp/shrink-chroot.sh
david% bash -u /tmp/shrink-chroot.sh
/tmp/shrink-chroot.sh: line 3: $1: unbound variable
使用set -e
你写的每一个脚本的开始都应该包含set -e。这告诉bash一但有任何一个语句返回非真的值,则退出bash。使用-e的好处是避免错误滚雪球般的变成严重错误,能尽早的捕获错误。更加可读的版本:set -o errexit
使用-e把你从检查错误中解放出来。如果你忘记了检查,bash会替你做这件事。不过你也没有办法使用$?来获取命令执行状态了,因为bash无法获得任何非0的返回值。你可以使用另一种结构:
if [ "$?"-ne 0]; then echo "command failed"; exit 1; fi
可以替换成:
command || { echo "command failed"; exit 1; }
或者使用:
if ! then echo "command failed"; exit 1; fi
如果你必须使用返回非0值的命令,或者你对返回值并不感兴趣呢?你可以使用 command || true ,或者你有一段很长的代码,你可以暂时关闭错误检查功能,不过我建议你谨慎使用。
相关文档指出,bash默认返回管道中最后一个命令的值,也许是你不想要的那个。比如执行 false | true 将会被认为命令成功执行。如果你想让这样的命令被认为是执行失败,可以使用 set -o pipefail
程序防御 - 考虑意料之外的事
你的脚本也许会被放到“意外”的账户下运行,像缺少文件或者目录没有被创建等情况。你可以做一些预防这些错误事情。比如,当你创建一个目录后,如果父目录不存在,mkdir 命令会返回一个错误。如果你创建目录时给mkdir命令加上-p选项,它会在创建需要的目录前,把需要的父目录创建出来。另一个例子是 rm 命令。如果你要删除一个不存在的文件,它会“吐槽”并且你的脚本会停止工作。(因为你使用了-e选项,对吧?)你可以使用-f选项来解决这个问题,在文件不存在的时候让脚本继续工作。
准备好处理文件名中的空格
有些人从在文件名或者命令行参数中使用空格,你需要在编写脚本时时刻记得这件事。你需要时刻记得用引号包围变量。
if [ $filename = "foo" ];
当$filename变量包含空格时就会挂掉。可以这样解决:
if [ "$filename" = "foo" ];
使用$@变量时,你也需要使用引号,因为空格隔开的两个参数会被解释成两个独立的部分。
david% foo() { for i in $@; do echo $i; done }; foo bar "baz quux"
david% foo() { for i in "$@"; do echo $i; done }; foo bar "baz quux"
我没有想到任何不能使用"$@"的时候,所以当你有疑问的时候,使用引号就没有错误。如果你同时使用find和xargs,你应该使用 -print0 来让字符分割文件名,而不是换行符分割。
david% touch "foo bar"
david% find | xargs ls
ls: ./foo: No such file or directory
ls: bar: No such file or directory
david% find -print0 | xargs -0 ls
设置的陷阱
当你编写的脚本挂掉后,文件系统处于未知状态。比如锁文件状态、临时文件状态或者更新了一个文件后在更新下一个文件前挂掉。如果你能解决这些问题,无论是删除锁文件,又或者在脚本遇到问题时回滚到已知状态,你都是非常棒的。幸运的是,bash提供了一种方法,当bash接收到一个UNIX信号时,运行一个命令或者一个函数。可以使用trap命令。
trap command signal [signal ...]
你可以链接多个信号(列表可以使用kill -l获得),但是为了清理残局,我们只使用其中的三个:INT,TERM和EXIT。你可以使用-as来让traps恢复到初始状态。
INT :Interrupt - 当有人使用Ctrl-C终止脚本时被触发
TERM :Terminate - 当有人使用kill杀死脚本进程时被触发
EXIT :Exit - 这是一个伪信号,当脚本正常退出或者set -e后因为出错而退出时被触发
当你使用锁文件时,可以这样写:
if [ ! -e $lockfile ]; then
touch $lockfile
critical-section
rm $lockfile
echo "critical-section is already running"
当最重要的部分(critical-section)正在运行时,如果杀死了脚本进程,会发生什么呢?锁文件会被扔在那,而且你的脚本在它被删除以前再也不会运行了。解决方法:
if [ ! -e $lockfile ]; then
trap " rm -f $ exit" INT TERM EXIT
touch $lockfile
critical-section
rm $lockfile
trap - INT TERM EXIT
echo "critical-section is already running"
现在当你杀死进程时,锁文件一同被删除。注意在trap命令中明确地退出了脚本,否则脚本会继续执行trap后面的命令。
竟态条件 (wikipedia)
在上面锁文件的例子中,有一个竟态条件是不得不指出的,它存在于判断锁文件和创建锁文件之间。一个可行的解决方法是使用IO重定向和bash的noclobber(wikipedia)模式,重定向到不存在的文件。我们可以这么做:
if ( set - echo "$$" & "$lockfile") 2& /dev/
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
critical-section
rm -f "$lockfile"
trap - INT TERM EXIT
echo "Failed to acquire lockfile: $lockfile"
echo "held by $(cat $lockfile)"
更复杂一点儿的问题是你要更新一大堆文件,当它们更新过程中出现问题时,你是否能让脚本挂得更加优雅一些。你想确认那些正确更新了,哪些根本没有变化。比如你需要一个添加用户的脚本。
add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R
当磁盘空间不足或者进程中途被杀死,这个脚本就会出现问题。在这种情况下,你也许希望用户账户不存在,而且他的文件也应该被删除。
rollback() {
del_from_passwd $user
if [ -e /home/$user ]; then
rm -rf /home/$user
trap rollback INT TERM EXIT
add_to_passwd $user
cp -a /etc/skel /home/$user
chown $user /home/$user -R
trap - INT TERM EXIT
在脚本最后需要使用trap关闭rollback调用,否则当脚本正常退出的时候rollback将会被调用,那么脚本等于什么都没做。
保持原子化
又是你需要一次更新目录中的一大堆文件,比如你需要将URL重写到另一个网站的域名。你也许会写:
for file in $(find /var/www -type f -name "*.html"); do
perl -pi -e 's/www.example.net//' $file
如果修改到一半是脚本出现问题,一部分使用,而另一部分使用www.example.net。你可以使用备份和trap解决,但在升级过程中你的网站URL是不一致的。
解决方法是将这个改变做成一个原子操作。先对数据做一个副本,在副本中更新URL,再用副本替换掉现在工作的版本。你需要确认副本和工作版本目录在同一个磁盘分区上,这样你就可以利用Linux系统的优势,它移动目录仅仅是更新目录指向的inode节点。
cp -a /var/www /var/www-tmp
for file in $(find /var/www-tmp -type -f -name "*.html"); do
perl -pi -e 's/www.example.net//' $file
mv /var/www /var/www-old
mv /var/www-tmp /var/www
这意味着如果更新过程出问题,线上系统不会受影响。线上系统受影响的时间降低为两次mv操作的时间,这个时间非常短,因为文件系统仅更新inode而不用真正的复制所有的数据。
这种技术的缺点是你需要两倍的磁盘空间,而且那些长时间打开文件的进程需要比较长的时间才能升级到新文件版本,建议更新完成后重新启动这些进程。对于apache服务器来说这不是问题,因为它每次都重新打开文件。你可以使用lsof命令查看当前正打开的文件。优势是你有了一个先前的备份,当你需要还原时,它就派上用场了。&
上一篇:下一篇:下面没有链接了
最 近 更 新
热 点 排 行9370人阅读
Oops在Linux 2.6内核+PowerPC架构下的前世今生Sailor_forever& sailing_
(本原创文章发表于Sailor_forever 的个人blog,未经本人许可,不得用于商业用途。任何个人、媒体、其他网站不得私自抄袭;网络媒体转载请注明出处,增加原文链接,否则属于侵权行为。如有任何问题,请留言或者发邮件给sailing_9806#)
【摘要】本文详细分析了2.6内核下Oops的来龙去脉,重点介绍了Oops的转储机制。首先介绍了Oops的基本概念、内核的异常级别及PowerPC的异常类型,接着介绍了Oops的处理流程。基于这个流程和嵌入式系统的文件系统及存储介质的特点,详细介绍了用户态和内核态中Oops的转储机制。最后介绍了PowerPC的EABI规范及如何根据此规则分析Oops log信息。
【关键字】Oops& 转储,panic,backtrace,syslogd,klogd,PowerPC, EABI
1&何谓OOPS&22&内核的异常级别&32.1&Bug&32.2&Oops&32.3&Panic&43&PowerPC的异常类型&44&OOPS的处理流程&64.1&异常入口&64.2&Die&104.3&Panic&155&OOPS的记录及转储&175.1&Printk&175.2&符号化记录backtrace&175.3&Klogd&185.4&Syslogd&185.5&用户态OOPS转储&185.6&内核态OOPS转储&195.6.1&进程上下文&195.6.2&中断上下文&256&PowerPC的EABI规范&276.1&寄存器的使用规则&276.2&堆栈的结构&286.2.1&栈的增减规则&286.2.2&栈的结构&296.3&从反汇编来看EABI的实现&306.4&从show_stack来看函数是如何调用的&347&如何分析OOPS&357.1&如何分析backtrace&357.2&如何分析栈&358&Oops典型实例&359&参考资料&35
1&何谓OOPSOops是美国人比较常有的口语。就是有点意外,吃惊,或突然的意思。&Oops&并不是很严重,正如在Britney Spears的 &Oops I Did It Again&那首歌的歌词中,也是一种轻描淡写,有时含有抱歉的意思。
对于Linux内核来说,Oops就意外着内核出了异常,此时会将产生异常时CPU的状态,出错的指令地址、数据地址及其他寄存器,函数调用的顺序甚至是栈里面的内容都打印出来,然后根据异常的严重程度来决定下一步的操作:杀死导致异常的进程或者挂起系统。
最典型的异常是在内核态引用了一个非法地址,通常是未初始化的野指针Null,这将导致页表异常,最终引发Oops。
Linux系统足够健壮,能够正常的反应各种异常。异常通常导致当前进程的死亡,而系统依然能够继续运转,但是这种运转都处在一种不稳定的状态,随时可能出问题。对于中断上下文的异常及系统关键资源的破坏,通常会导致内核挂起,不再响应任何事件。
2&内核的异常级别2.1&BugBug是指那些不符合内核的正常设计,但内核能够检测出来并且对系统运行不会产生影响的问题,比如在原子上下文中休眠。如:BUG: scheduling while atomic: insmod/826/0xCall Trace:[ef12f700] [c00081e0] show_stack+0x3c/0x194 (unreliable)[ef12f730] [c0019b2c] __schedule_bug+0x64/0x78[ef12f750] [c0350f50] schedule+0x324/0x34c[ef12f7a0] [c03515c0] schedule_timeout+0x68/0xe4[ef12f7e0] [c027938c] fsl_elbc_run_command+0x138/0x1c0[ef12f820] [c0275820] nand_do_read_ops+0x130/0x3dc[ef12f880] [c0275ebc] nand_read+0xac/0xe0[ef12f8b0] [c0262d98] part_read+0x5c/0xe4[ef12f8c0] [c017bcac] jffs2_flash_read+0x68/0x254[ef12f8f0] [c0170550] jffs2_read_dnode+0x60/0x304[ef12f940] [c017088c] jffs2_read_inode_range+0x98/0x180[ef12f970] [c016e610] jffs2_do_readpage_nolock+0x94/0x1ac[ef12f990] [c016ee04] jffs2_write_begin+0x2b0/0x330[ef12fa10] [c005144c] generic_file_buffered_write+0x11c/0x8d0[ef12fab0] [c0051e48] __generic_file_aio_write_nolock+0x248/0x500[ef12fb20] [c0052168] generic_file_aio_write+0x68/0x10c[ef12fb50] [c007ca80] do_sync_write+0xc4/0x138[ef12fc10] [f107c0dc] oops_log+0xdc/0x1e8 [oopslog][ef12fe70] [f3087058] oops_log_init+0x58/0xa0 [oopslog][ef12fe80] [c00477bc] sys_init_module+0x130/0x17dc[ef12ff40] [c00104b0] ret_from_syscall+0x0/0x38--- Exception: c01 at 0xff29658&&& LR = 0x
2.2&Oops程序在内核态时,进入一种异常情况,比如引用非法指针导致的数据异常,数组越界导致的取指异常,此时异常处理机制能够捕获此异常,并将系统关键信息打印到串口上,正常情况下Oops消息会被记录到系统日志中去。
Oops发生时,进程处在内核态,很可能正在访问系统关键资源,并且获取了一些锁,当进程由于Oops异常退出时,无法释放已经获取的资源,导致其他需要获取此资源的进程挂起,对系统的正常运行造成影响。通常这种情况,系统处在不稳定的状态,很可能崩溃。
2.3&Panic当Oops发生在中断上下文中或者在进程0和1中,系统将彻底挂起,因为中断服务程序异常后,将无法恢复,这种情况即称为内核panic。另外当系统设置了panic标志时,无论Oops发生在中断上下文还是进程上下文,都将导致内核Panic。由于在中断复位程序中panic后,系统将不再进行调度,Syslogd将不会再运行,因此这种情况下,Oops的消息仅仅打印到串口上,不会被记录在系统日志中。
3&PowerPC的异常类型32位处理器通常有各种异常机制来响应系统运行过程中的异常。以MPC8378为例,其异常类型如下:
其中以200、300、400、所代表的machine check,取指,取数及TLB等异常为主。大部分是非法地址及非法指令造成的。TLB miss经过简单的处理后会最终转向300和400异常处理。
4&OOPS的处理流程4.1&异常入口异常处理在内核中的相关代码为: 汇编级的异常入口。&329/* Machine check */&330/*&331 * On CHRP, this is complicated by the fact that we could get a&332 * machine check inside RTAS, and we have no guarantee that certain&333 * critical registers will have the values we expect.& The set of&334 * registers that might have bad values includes all the GPRs&335 * and all the BATs.& We indicate that we are in RTAS by putting&336 * a non-zero value, the address of the exception frame to use,&337 * in SPRG2.& The machine check handler checks SPRG2 and uses its&338 * value if it is non-zero.& If we ever needed to free up SPRG2,&339 * we could use a field in the thread_info or thread_struct instead.&340 * (Other exception handlers assume that r1 is a valid kernel stack&341 * pointer when we take an exception from supervisor mode.)&342 *&&&&& -- paulus.&343 */&344&&&&&&& . = 0x200&345&&&&&&& mtspr&& SPRN_SPRG0,r10&346&&&&&&& mtspr&& SPRN_SPRG1,r11&347&&&&&&& mfcr&&& r10&348#ifdef CONFIG_PPC_CHRP&349&&&&&&& mfspr&& r11,SPRN_SPRG2&350&&&&&&& cmpwi&& 0,r11,0&351&&&&&&& bne&&&& 7f&352#endif /* CONFIG_PPC_CHRP */&353&&&&&&& EXCEPTION_PROLOG_1&3547:&&&&& EXCEPTION_PROLOG_2&355&&&&&&& addi&&& r3,r1,STACK_FRAME_OVERHEAD&356#ifdef CONFIG_PPC_CHRP&357&&&&&&& mfspr&& r4,SPRN_SPRG2&358&&&&&&& cmpwi&& cr1,r4,0&359&&&&&&& bne&&&& cr1,1f&360#endif&361&&&&&&& EXC_XFER_STD(0x200, machine_check_exception)&362#ifdef CONFIG_PPC_CHRP&3631:&&&&& b&&&&&& machine_check_in_rtas&364#endif&365&366/* Data access exception. */&367&&&&&&& . = 0x300&368DataAccess:&369&&&&&&& EXCEPTION_PROLOG&370&&&&&&& mfspr&& r10,SPRN_DSISR&371&&&&&&& andis.& r0,r10,0xa470&&&&&&&&&& /* weird error? */&372&&&&&&& bne&&&& 1f&&&&&&&&&&&&&&&&&&&&& /* if not, try to put a PTE */&373&&&&&&& mfspr&& r4,SPRN_DAR&&&&&&&&&&&& /* into the hash table */&374&&&&&&& rlwinm& r3,r10,32-15,21,21&&&&& /* DSISR_STORE -& _PAGE_RW */&375&&&&&&& bl&&&&& hash_page&3761:&&&&& stw&&&& r10,_DSISR(r11)&377&&&&&&& mr&&&&& r5,r10&378&&&&&&& mfspr&& r4,SPRN_DAR&379&&&&&&& EXC_XFER_EE_LITE(0x300, handle_page_fault)&380&381&382/* Instruction access exception. */&383&&&&&&& . = 0x400&384InstructionAccess:&385&&&&&&& EXCEPTION_PROLOG&386&&&&&&& andis.& r0,r9,0x4000&&&&&&&&&&& /* no pte found? */&387&&&&&&& beq&&&& 1f&&&&&&&&&&&&&&&&&&&&& /* if so, try to put a PTE */&388&&&&&&& li&&&&& r3,0&&&&&&&&&&&&&&&&&&& /* into the hash table */&389&&&&&&& mr&&&&& r4,r12&&&&&&&&&&&&&&&&& /* SRR0 is fault address */&390&&&&&&& bl&&&&& hash_page&3911:&&&&& mr&&&&& r4,r12&392&&&&&&& mr&&&&& r5,r9&393&&&&&&& EXC_XFER_EE_LITE(0x400, handle_page_fault)
handle_page_fault在 /*&464 * Top-level page fault handling.&465 * This is in assembler because if do_page_fault tells us that&466 * it is a bad kernel page fault, we want to save the non-volatile&467 * registers before calling bad_page_fault.&468 */&469&&&&&&& .globl& handle_page_fault&470handle_page_fault:&471&&&&&&& stw&&&& r4,_DAR(r1)&472&&&&&&& addi&&& r3,r1,STACK_FRAME_OVERHEAD&473&&&&&&& bl&&&&& do_page_fault&474&&&&&&& cmpwi&& r3,0&475&&&&&&& beq+&&& ret_from_except&476&&&&&&& SAVE_NVGPRS(r1)&477&&&&&&& lwz&&&& r0,_TRAP(r1)&478&&&&&&& clrrwi& r0,r0,1&479&&&&&&& stw&&&& r0,_TRAP(r1)&480&&&&&&& mr&&&&& r5,r3&481&&&&&&& addi&&& r3,r1,STACK_FRAME_OVERHEAD&482&&&&&&& lwz&&&& r4,_DAR(r1)&483&&&&&&& bl&&&&& bad_page_fault&484&&&&&&& b&&&&&& ret_from_except_full
并最终调用bad_page_fault或者do_page_fault &399/*&400 * bad_page_fault is called when we have a bad access from the kernel.&401 * It is called from the DSI and ISI handlers in head.S and from some&402 * of the procedures in traps.c.&403 */&404void bad_page_fault(struct pt_regs *regs, unsigned long address, int sig)&405{&406&&&&&&& const struct exception_table_entry *&407&408&&&&&&& /* Are we prepared to handle this fault?& */&409&&&&&&& if ((entry = search_exception_tables(regs-&nip)) != NULL) {&410&&&&&&&&&&&&&&& regs-&nip = entry-&&411&&&&&&&&&&&&&&&&412&&&&&&& }&413&414&&&&&&& /* kernel has accessed a bad area */&415&416&&&&&&& switch (regs-&trap) {&417&&&&&&& case 0x300:&418&&&&&&& case 0x380:&419&&&&&&&&&&&&&&& printk(KERN_ALERT "Unable to handle kernel paging request for "&420&&&&&&&&&&&&&&&&&&&&&&& "data at address 0x%08lx/n", regs-&dar);&421&&&&&&&&&&&&&&&&422&&&&&&& case 0x400:&423&&&&&&& case 0x480:&424&&&&&&&&&&&&&&& printk(KERN_ALERT "Unable to handle kernel paging request for "&425&&&&&&&&&&&&&&&&&&&&&&& "instruction fetch/n");&426&&&&&&&&&&&&&&&&427&&&&&&& default:&428&&&&&&&&&&&&&&& printk(KERN_ALERT "Unable to handle kernel paging request for "&429&&&&&&&&&&&&&&&&&&&&&&& "unknown fault/n");&430&&&&&&&&&&&&&&&&431&&&&&&& }&432&&&&&&& printk(KERN_ALERT "Faulting instruction address: 0x%08lx/n",&433&&&&&&&&&&&&&&& regs-&nip);&434&435&&&&&&& die("Kernel access of bad area", regs, sig);&436}
大多数Oops的提示信息都是Unable to handle kernel paging request。
另外一种常见错误是machine check&485void machine_check_exception(struct pt_regs *regs)&486{&487&&&&&&& int recover = 0;&488&489&&&&&&& /* See if any machine dependent calls. In theory, we would want&490&&&&&&&& * to call the CPU first, and call the ppc_md. one if the CPU&491&&&&&&&& * one returns a positive number. However there is existing code&492&&&&&&&& * that assumes the board gets a first chance, so let's keep it&493&&&&&&&& * that way for now and fix things later. --BenH.&494&&&&&&&& */&495&&&&&&& if (ppc_md.machine_check_exception)&496&&&&&&&&&&&&&&& recover = ppc_md.machine_check_exception(regs);&497&&&&&&& else if (cur_cpu_spec-&machine_check)&498&&&&&&&&&&&&&&& recover = cur_cpu_spec-&machine_check(regs);&499&500&&&&&&& if (recover & 0)&501&&&&&&&&&&&&&&&&502&503&&&&&&& if (user_mode(regs)) {&504&&&&&&&&&&&&&&& regs-&msr |= MSR_RI;&505&&&&&&&&&&&&&&& _exception(SIGBUS, regs, BUS_ADRERR, regs-&nip);&506&&&&&&&&&&&&&&&&507&&&&&&& }&508&509#if defined(CONFIG_8xx) && defined(CONFIG_PCI)&510&&&&&&& /* the qspan pci read routines can cause machine checks -- Cort&511&&&&&&&& *&512&&&&&&&& * yuck !!! that totally needs to go away ! There are better ways&513&&&&&&&& * to deal with that than having a wart in the mcheck handler.&514&&&&&&&& * -- BenH&515&&&&&&&& */&516&&&&&&& bad_page_fault(regs, regs-&dar, SIGBUS);&517&&&&&&&&518#endif&519&520&&&&&&& if (debugger_fault_handler(regs)) {&521&&&&&&&&&&&&&&& regs-&msr |= MSR_RI;&522&&&&&&&&&&&&&&&&523&&&&&&& }&524&525&&&&&&& if (check_io_access(regs))&526&&&&&&&&&&&&&&&&527&528&&&&&&& if (debugger_fault_handler(regs))&529&&&&&&&&&&&&&&&&530&&&&&&& die("Machine check", regs, SIGBUS);&531&532&&&&&&& /* Must die if the interrupt is not recoverable */&533&&&&&&& if (!(regs-&msr & MSR_RI))&534&&&&&&&&&&&&&&& panic("Unrecoverable Machine check");&535}
4.2&Die无论何种原因导致的异常,最终都将调用die统一处理。 & 97int die(const char *str, struct pt_regs *regs, long err)& 98{& 99&&&&&&& static struct {&100&&&&&&&&&&&&&&& spinlock_&101&&&&&&&&&&&&&&& u32 lock_&102&&&&&&&&&&&&&&& int lock_owner_&103&&&&&&& } die = {&104&&&&&&&&&&&&&&& .lock =&&&&&&&&&&&&&&&& __SPIN_LOCK_UNLOCKED(die.lock),&105&&&&&&&&&&&&&&& .lock_owner =&&&&&&&&&& -1,&106&&&&&&&&&&&&&&& .lock_owner_depth =&&&& 0&107&&&&&&& };&108&&&&&&& static int die_&109&&&&&&&&110&111&&&&&&& if (debugger(regs))&112&&&&&&&&&&&&&&& return 1;&113&114&&&&&&& oops_enter();&115&116&&&&&&& if (die.lock_owner != raw_smp_processor_id()) {&117&&&&&&&&&&&&&&& console_verbose();&118&&&&&&&&&&&&&&& spin_lock_irqsave(&die.lock, flags);&119&&&&&&&&&&&&&&& die.lock_owner = smp_processor_id();&120&&&&&&&&&&&&&&& die.lock_owner_depth = 0;&121&&&&&&&&&&&&&&& bust_spinlocks(1);&122&&&&&&&&&&&&&&& if (machine_is(powermac))&123&&&&&&&&&&&&&&&&&&&&&&& pmac_backlight_unblank();&124&&&&&&& } else {&125&&&&&&&&&&&&&&& local_save_flags(flags);&126&&&&&&& }&127&128&&&&&&& if (++die.lock_owner_depth & 3) {&129&&&&&&&&&&&&&&& printk("Oops: %s, sig: %ld [#%d]/n", str, err, ++die_counter);&130#ifdef CONFIG_PREEMPT&131&&&&&&&&&&&&&&& printk("PREEMPT ");&132#endif&133#ifdef CONFIG_SMP&134&&&&&&&&&&&&&&& printk("SMP NR_CPUS=%d ", NR_CPUS);&135#endif&136#ifdef CONFIG_DEBUG_PAGEALLOC&137&&&&&&&&&&&&&&& printk("DEBUG_PAGEALLOC ");&138#endif&139#ifdef CONFIG_NUMA&140&&&&&&&&&&&&&&& printk("NUMA ");&141#endif&142&&&&&&&&&&&&&&& printk("%s/n", ppc_md.name ? ppc_md.name : "");&143&144&&&&&&&&&&&&&&& print_modules();&145&&&&&&&&&&&&&&& show_regs(regs);&146&&&&&&& } else {&147&&&&&&&&&&&&&&& printk("Recursive die() failure, output suppressed/n");&148&&&&&&& }&149&150&&&&&&& bust_spinlocks(0);&151&&&&&&& die.lock_owner = -1;&152&&&&&&& add_taint(TAINT_DIE);&153&&&&&&& spin_unlock_irqrestore(&die.lock, flags);&154&155&&&&&&& if (kexec_should_crash(current) ||&156&&&&&&&&&&&&&&& kexec_sr_activated(smp_processor_id()))&157&&&&&&&&&&&&&&& crash_kexec(regs);&158&&&&&&& crash_kexec_secondary(regs);&159&160&&&&&&& if (in_interrupt())&161&&&&&&&&&&&&&&& panic("Fatal exception in interrupt");&162&163&&&&&&& if (panic_on_oops)&164&&&&&&&&&&&&&&& panic("Fatal exception");&165&166&&&&&&& oops_exit();&167&&&&&&& do_exit(err);&168&169&&&&&&& return 0;&170}
129:打印当前异常原因及Oops的次数125: show regs,打印系统关键寄存器将调用栈160:如果异常发生时,系统处于中断上下文,则panic163:如果进程上下文已经设置了panic_on_oops,则panic167:若非panic,则说明异常发生时处于进程上下文,则杀死异常进程,重新调度。
&449void show_regs(struct pt_regs * regs)&450{&451&&&&&&& int i,&452&453&&&&&&& printk("NIP: "REG" LR: "REG" CTR: "REG"/n",&454&&&&&&&&&&&&&& regs-&nip, regs-&link, regs-&ctr);&455&&&&&&& printk("REGS: %p TRAP: %04lx&& %s& (%s)/n",&456&&&&&&&&&&&&&& regs, regs-&trap, print_tainted(), init_utsname()-&release);&457&&&&&&& printk("MSR: "REG" ", regs-&msr);&458&&&&&&& printbits(regs-&msr, msr_bits);&459&&&&&&& printk("& CR: %08lx& XER: %08lx/n", regs-&ccr, regs-&xer);&460&&&&&&& trap = TRAP(regs);&461&&&&&&& if (trap == 0x300 || trap == 0x600)&462#if defined(CONFIG_4xx) || defined(CONFIG_BOOKE)&463&&&&&&&&&&&&&&& printk("DEAR: "REG", ESR: "REG"/n", regs-&dar, regs-&dsisr);&464#else&465&&&&&&&&&&&&&&& printk("DAR: "REG", DSISR: "REG"/n", regs-&dar, regs-&dsisr);&466#endif&467&&&&&&& printk("TASK = %p[%d] '%s' THREAD: %p",&468&&&&&&&&&&&&&& current, task_pid_nr(current), current-&comm, task_thread_info(current));&469&470#ifdef CONFIG_SMP&471&&&&&&& printk(" CPU: %d", raw_smp_processor_id());&472#endif /* CONFIG_SMP */&473&474&&&&&&& for (i = 0;& i & 32;& i++) {&475&&&&&&&&&&&&&&& if ((i % REGS_PER_LINE) == 0)&476&&&&&&&&&&&&&&&&&&&&&&& printk("/n" KERN_INFO "GPR%02d: ", i);&477&&&&&&&&&&&&&&& printk(REG " ", regs-&gpr[i]);&478&&&&&&&&&&&&&&& if (i == LAST_VOLATILE && !FULL_REGS(regs))&479&&&&&&&&&&&&&&&&&&&&&&&&480&&&&&&& }&481&&&&&&& printk("/n");&482#ifdef CONFIG_KALLSYMS&483&&&&&&& /*&484&&&&&&&& * Lookup NIP late so we have the best change of getting the&485&&&&&&&& * above info out without failing&486&&&&&&&& */&487&&&&&&& printk("NIP ["REG"] ", regs-&nip);&488&&&&&&& print_symbol("%s/n", regs-&nip);&489&&&&&&& printk("LR ["REG"] ", regs-&link);&490&&&&&&& print_symbol("%s/n", regs-&link);&491#endif&492&&&&&&& show_stack(current, (unsigned long *) regs-&gpr[1]);&493&&&&&&& if (!user_mode(regs))&494&&&&&&&&&&&&&&& show_instructions(regs);&495}
453:产生错误的指令地址,及子程序待返回的地址。467:导致异常的进程地址、进程号及进程的名称。482-491:当定义了CONFIG_KALLSYMS时,将查找内核符号表,解析指令地址NIP和LR。492:打印调用栈。493:如果是内核态异常,则打印异常指令。
&965void show_stack(struct task_struct *tsk, unsigned long *stack)&966{&967&&&&&&& unsigned long sp, ip, lr,&968&&&&&&& int count = 0;&969&&&&&&& int firstframe = 1;&970&971&&&&&&& sp = (unsigned long)&972&&&&&&& if (tsk == NULL)&973&&&&&&&&&&&&&&& tsk =&974&&&&&&& if (sp == 0) {&975&&&&&&&&&&&&&&& if (tsk == current)&976&&&&&&&&&&&&&&&&&&&&&&& asm("mr %0,1" : "=r" (sp));&977&&&&&&&&&&&&&&& else&978&&&&&&&&&&&&&&&&&&&&&&& sp = tsk-&thread.&979&&&&&&& }&980&981&&&&&&& lr = 0;&982&&&&&&& printk("Call Trace:/n");&983&&&&&&& do {&984&&&&&&&&&&&&&&& if (!validate_sp(sp, tsk, MIN_STACK_FRAME))&985&&&&&&&&&&&&&&&&&&&&&&&&986&987&&&&&&&&&&&&&&& stack = (unsigned long *)&988&&&&&&&&&&&&&&& newsp = stack[0];&989&&&&&&&&&&&&&&& ip = stack[FRAME_LR_SAVE];&990&&&&&&&&&&&&&&& if (!firstframe || ip != lr) {&991&&&&&&&&&&&&&&&&&&&&&&& printk("["REG"] ["REG"] ", sp, ip);&992&&&&&&&&&&&&&&&&&&&&&&& print_symbol("%s", ip);&993&&&&&&&&&&&&&&&&&&&&&&& if (firstframe)&994&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& printk(" (unreliable)");&995&&&&&&&&&&&&&&&&&&&&&&& printk("/n");&996&&&&&&&&&&&&&&& }&997&&&&&&&&&&&&&&& firstframe = 0;&998&999&&&&&&&&&&&&&&& /*1000&&&&&&&&&&&&&&&& * See if this is an exception frame.1001&&&&&&&&&&&&&&&& * We look for the "regshere" marker in the current frame.1002&&&&&&&&&&&&&&&& */1003&&&&&&&&&&&&&&& if (validate_sp(sp, tsk, INT_FRAME_SIZE)1004&&&&&&&&&&&&&&&&&&& && stack[FRAME_MARKER] == REGS_MARKER) {1005&&&&&&&&&&&&&&&&&&&&&&& struct pt_regs *regs = (struct pt_regs *)1006&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& (sp + STACK_FRAME_OVERHEAD);1007&&&&&&&&&&&&&&&&&&&&&&& printk("--- Exception: %lx", regs-&trap);1008&&&&&&&&&&&&&&&&&&&&&&& print_symbol(" at %s/n", regs-&nip);1009&&&&&&&&&&&&&&&&&&&&&&& lr = regs-&1010&&&&&&&&&&&&&&&&&&&&&&& print_symbol("&&& LR = %s/n", lr);1011&&&&&&&&&&&&&&&&&&&&&&& firstframe = 1;1012&&&&&&&&&&&&&&& }10131014&&&&&&&&&&&&&&& sp =1015&&&&&&& } while (count++ & kstack_depth_to_print);1016}10171018void dump_stack(void)1019{1020&&&&&&& show_stack(current, NULL);1021}1022EXPORT_SYMBOL(dump_stack);
Call trace的原理:根据保存的SP指针回退,而每个栈里又在固定的位置了保存了调用过程中函数的一系列返回地址,因此得到了调用顺序,由异常时的最后一个指令往回退。
4.3&Panic在中断上下文及设置了panic_on_oops时发生Oops将触发panic。panic_on_oops为内核的控制参数,可以在用户态更改。panic_on_oops的缺省设置是"0",即在Oops发生时不会进行panic()操作。echo &kernel.panic_on_oops = 1&P &&/etc/sysctl.confsysctl -p或者sysctl -w kernel.panic_on_oops=1
Panic将导致系统重启,重启等待的时间也可以控制。echo &kernel.panic = 5&P &&/etc/sysctl.confsysctl &p或者echo 5 &/proc/sys/kernel/panic或者sysctl -w kernel.panic =5或者在命令行参数里静态指定panic=5,但此值可被用户再次更改
& 62NORET_TYPE void panic(const char * fmt, ...)& 63{& 64&&&&&&&& 65&&&&&&& static char buf[1024];& 66&&&&&&& va_& 67#if defined(CONFIG_S390)& 68&&&&&&& unsigned long caller = (unsigned long) __builtin_return_address(0);& 69#endif& 70& 71&&&&&&& /*& 72&&&&&&&& * It's possible to come here directly from a panic-assertion and not& 73&&&&&&&& * have preempt disabled. Some functions called from here want& 74&&&&&&&& * preempt to be disabled. No point enabling it later though...& 75&&&&&&&& */& 76&&&&&&& preempt_disable();& 77& 78&&&&&&& bust_spinlocks(1);& 79&&&&&&& va_start(args, fmt);& 80&&&&&&& vsnprintf(buf, sizeof(buf), fmt, args);& 81&&&&&&& va_end(args);& 82&&&&&&& printk(KERN_EMERG "Kernel panic - not syncing: %s/n",buf);& 83&&&&&&& bust_spinlocks(0);& 84& 85&&&&&&& /*& 86&&&&&&&& * If we have crashed and we have a crash kernel loaded let it handle& 87&&&&&&&& * everything else.& 88&&&&&&&& * Do we want to call this before we try to display a message?& 89&&&&&&&& */& 90&&&&&&& crash_kexec(NULL);& 91& 92#ifdef CONFIG_SMP& 93&&&&&&& /*& 94&&&&&&&& * Note smp_send_stop is the usual smp shutdown function, which& 95&&&&&&&& * unfortunately means it may not be hardened to work in a panic& 96&&&&&&&& * situation.& 97&&&&&&&& */& 98&&&&&&& smp_send_stop();& 99#endif&100&101&&&&&&& atomic_notifier_call_chain(&panic_notifier_list, 0, buf);&102&103&&&&&&& if (!panic_blink)&104&&&&&&&&&&&&&&& panic_blink = no_&105&106&&&&&&& if (panic_timeout & 0) {&107&&&&&&&&&&&&&&& /*&108&&&&&&&&&&&&&&&& * Delay timeout seconds before rebooting the machine. &109&&&&&&&&&&&&&&&& * We can't use the "normal" timers since we just panicked..&110&&&&&&&&&&&&&&&& */&111&&&&&&&&&&&&&&& printk(KERN_EMERG "Rebooting in %d seconds..",panic_timeout);&112&&&&&&&&&&&&&&& for (i = 0; i & panic_timeout*1000; ) {&113&&&&&&&&&&&&&&&&&&&&&&& touch_nmi_watchdog();&114&&&&&&&&&&&&&&&&&&&&&&& i += panic_blink(i);&115&&&&&&&&&&&&&&&&&&&&&&& mdelay(1);&116&&&&&&&&&&&&&&&&&&&&&&& i++;&117&&&&&&&&&&&&&&& }&118&&&&&&&&&&&&&&& /*&&&&& This will not be a clean reboot, with everything&119&&&&&&&&&&&&&&&& *&&&&& shutting down.& But if there is a chance of&120&&&&&&&&&&&&&&&& *&&&&& rebooting the system it will be rebooted.&121&&&&&&&&&&&&&&&& */&122&&&&&&&&&&&&&&& emergency_restart();&123&&&&&&& }&124#ifdef __sparc__&125&&&&&&& {&126&&&&&&&&&&&&&&& extern int stop_a_&127&&&&&&&&&&&&&&& /* Make sure the user can actually press Stop-A (L1-A) */&128&&&&&&&&&&&&&&& stop_a_enabled = 1;&129&&&&&&&&&&&&&&& printk(KERN_EMERG "Press Stop-A (L1-A) to return to the boot prom/n");&130&&&&&&& }&131#endif&132#if defined(CONFIG_S390)&133&&&&&&& disabled_wait(caller);&134#endif&135&&&&&&& local_irq_enable();&136&&&&&&& for (i = 0;;) {&137&&&&&&&&&&&&&&& touch_softlockup_watchdog();&138&&&&&&&&&&&&&&& i += panic_blink(i);&139&&&&&&&&&&&&&&& mdelay(1);&140&&&&&&&&&&&&&&& i++;&141&&&&&&& }&142}&143&144EXPORT_SYMBOL(panic);
当panic为0时,则不重启,打开中断,陷入死循环。否则延时panic时间后,reboot
5&OOPS的记录及转储5.1&PrintkPrintk利用一个长度为 LOG_BUF_LEN循环缓冲区__log_buf记录打印信息。可以在任何地方调用printk,甚至在中断处理函数里也可以调用,而且对数据量的大小没有限制。Printk首先查询console的sem,当可用时,会根据当前系统的log级别选择将相关信息发送到真正的物理串口,然后唤醒任何正在等待消息的进程,即那些睡眠在 syslog 系统调用上的进程,或者读取 /proc/kmsg的进程。这两个访问日志引擎的接口几乎是等价的,不过请注意,对 /proc/kmsg 进行读操作时,日志缓冲区中被读取的数据就不再保留,而syslog 系统调用却能随意地返回日志数据,并保留这些数据以便其它进程也能使用。Console不可用时,仅仅将log记录到循环缓冲区,然后返回。当串口再次可用时,会自动将log刷新到终端上。
如果循环缓冲区填满了,printk就绕回缓冲区的开始处填写新数据,覆盖最陈旧的数据,于是记录进程就会丢失最早的数据。但与使用循环缓冲区所带来的好处相比,这个问题可以忽略不计。例如,循环缓冲区可以使系统在没有记录进程的情况下照样运行,同时覆盖那些不再会有人去读的旧数据,从而使内存的浪费减到最少。
5.2&符号化记录backtrace在显示调用栈信息时,若定义了CONFIG_KALLSYMS宏,则会将LR等信息解码出来。 &321/* Look up a kernel symbol and print it to the kernel messages. */&322void __print_symbol(const char *fmt, unsigned long address)&323{&324&&&&&&& char buffer[KSYM_SYMBOL_LEN];&325&326&&&&&&& sprint_symbol(buffer, address);&327&328&&&&&&& printk(fmt, buffer);&329}
从2.6内核才开始有CONFIG_KALLSYMS,这样可以提供更丰富的调试信息。这个选项(在"Generl setup/Standard features"下)使得内核符号信息内建在内核中。对于2.4内核,无法提供符号化的调用栈,因此只能借用ksymoops工具来解析。
5.3&KlogdPrintk会唤醒等待获取内核打印信息的进程Klogd。Klogd默认读取/proc/kmsg,并将它们分发到 Syslogd。如果没有运行klogd,数据将保留在循环缓冲区中,直到某个进程读取或缓冲区溢出为止。 因此此时的Oops信息不会自动传递到内核空间,这种情况下,就只好手动查看 /proc/kmsg 了。
手工读取内核消息时,在停止klogd之后,可以发现 /proc 文件很象一个FIFO,读进程会阻塞在里面以等待更多的数据。显然,如果已经有 klogd 或其它的进程正在读取相同的数据,就不能采用这种方法进行消息读取,因为会与这些进程发生竞争。
如果想避免因为来自驱动程序的大量监视信息而扰乱系统日志,则可以为 klogd 指定 -f (file) 选项,指示 klogd将消息保存到某个特定的文件。
5.4&SyslogdSyslogd通过本地UNIX套接字接口接收用户空间其他进程发送过来的日志信息,随后查看/etc/syslog.conf,找出处理这些数据的方法。Syslogd 根据设施和优先级对消息进行区分。内核消息由 LOG_KERN 设施记录,并以 printk 中使用的优先级记录(例如,printk 中使用的KERN_ERR对应于Syslogd 中的 LOG_ERR)。
一般情况下syslog的日志文件存储路径为/var/log/messages。
5.5&用户态OOPS转储Oops消息通常都会通过串口打印出来,但是对于嵌入式设备,在系统正常运行过程中,一般不接串口设备,即使有串口设备,这种Oops日志信息也不便保存,不便于远程分析调试。因此需要将Oops信息记录下来,保存成文件。
对于非panic的Oops,正常情况下,当系统同时运行了Klogd和Syslogd时,Oops信息会自动保存到日志文件/var/log/messages中。但内核所有的调试信息及用户的相关日志都混杂在一起,不便于分析Oops,因此需要将oops信息单独提取出来。
PC平台上一般自动运行了Klogd和Syslogd,嵌入式平台上,这两个程序需要定制,否则将无法记录Oops信息。
尽管如此,仅仅靠Oops信息有时候还难以找到系统崩溃的原因,需要在发生Oops后将当时系统的一些关键信息都保存起来,这就需要Oops的自动转储。
可以模仿Klogd,开发后台监控程序,定期查询/proc/kmsg,当发现&Oops:&这种系统Oops的字样时,自动保存相关信息到指定文件中。在收集到指定长度的log时或者接收内核kmsg消息超时时停止查询,然后调用相关脚本自动采集系统相关信息,并将这些信息和Oops信息打包保存。继续查询/proc/kmsg,等待下一次Oops。这样就可以将不同的Oops和其他无关信息分离,分别打包。这种Oops的转储方式,非常利于嵌入式设备上log的远程采集记录及后续分析。
Oops通常意外着系统出现严重错误,很可能伴随系统重启。Oops的转储要求系统重启或者断电后,log文件不能丢失。PC机采用的是硬盘,自然是不用考虑掉电丢失的问题。但是嵌入式系统的文件系统都是基于RAM或者Flash的,而基于RAM的Ramdisk文件系统中的信息在掉电后不能保存,因此保存log文件的文件系统必须是基于Flash的文件系统,如JFFS2或者YAFFS。关于嵌入式系统的文件系统的选择及组合可以参考相关文章。
对于系统panic的情况,内核即挂起,因此用户态的klogd及Syslogd无法获得调度机会,也就无法保存相关log文件。因此有必要开发在内核态直接读写文件的记录机制。
5.6&内核态OOPS转储另一种方式就是在内核中直接读写文件,保存Oops信息。但是这需要重新开发相关内核模块。5.6.1&进程上下文以下为内核态读写文件的代码:#include&linux/module.h&#include&linux/kernel.h&#include&linux/init.h&
#include&linux/types.h&
#include&linux/fs.h&#include&linux/string.h&#include&asm/uaccess.h& /* get_fs(),set_fs(),get_ds() */
#define OOPS_LOG_FILE "/root/oops_log_test.log"#define CFG_OOPS_LOCK_SUPPORT& 0#define CFG_OOPS_BUF_LEN 512
#define CFG_OOPS_RD_TEST 1
static char tmp[100];
static struct file *filp = NULL;
static spinlock_t&& oops_access_&&&&&& /* For exclusive access to logfile */
int oops_log(const char *fmt, ...){&&& char buf[CFG_OOPS_BUF_LEN];&&& long num_&&& va_
&&& mm_segment_t old_&&& ssize_t ret = 0;
#if CFG_OOPS_LOCK_SUPPORT&&&#endif
&&& va_start(args, fmt);
&&& num_written = vsnprintf(buf, sizeof(buf), fmt, args);&&& if (num_written & 0)&&& {&&&&&&& printk("oops_log copy string err!/n");&&& }&&& else if (num_written &= sizeof(buf))&&& {&&&&&&& buf[CFG_OOPS_BUF_LEN - 2] = '/n';&&&&&&& buf[CFG_OOPS_BUF_LEN - 1] = '/0';&&& }&&& else&&& {&&&&&&& ; /* OK */&&& }
&&& va_end(args);
&&& /* eruidin cancel disable irq and spin lock
*/#if CFG_OOPS_LOCK_SUPPORT&&& spin_lock_irqsave (&oops_access_lock, flags);&&&& /* Lock out IRQ handler: */#endif
&&& if(IS_ERR(filp))&&& {&&&&&&& printk("log file is not opened!/n");&&&&&&& ret = -1;&&& }&&& else&&& {&&&&&&& old_fs = get_fs();&&&&&&& set_fs(get_ds());
&&&&&&& filp-&f_op-&write(filp, buf, strlen(buf), &filp-&f_pos);
#if CFG_OOPS_RD_TEST&&&&&&& filp-&f_op-&llseek(filp,0,0);&&&&&&& ret = filp-&f_op-&read(filp, tmp, strlen(buf), &filp-&f_pos);
&&&&&&& if(ret & 0)&&&&&&& {&&&&&&&&&&& printk("oops_log: %s/n", tmp);&&&&&&& }&&&&&&& else if(ret == 0)&&&&&&& {&&&&&&&&&&& printk("oops_log: read nothing/n");&&&&&&& }&&&&&&& else&&&&&&& {&&&&&&&&&&& printk("oops_log: read error/n");&&&&&&&&&&& ret = -1;&&&&&&& }#endif
&&&&&&& set_fs(old_fs);
#if CFG_OOPS_LOCK_SUPPORT&&& spin_unlock_irqrestore (&oops_access_lock, flags); /* Lock in IRQ handler: */#endif
static int __init oops_log_init(void){&& &&& filp = filp_open(OOPS_LOG_FILE, O_RDWR | O_CREAT, 0644);
&&& if(IS_ERR(filp))&&& {&&&&&&& printk("oops_log module loaded failed, open log fiel err!/n");&&& }&&& else&&& {&&&&&&& printk("oops_log module loaded successfully!/n");&&& }
#if CFG_OOPS_LOCK_SUPPORT&&& spin_lock_init(&oops_access_lock); /* Apply the spinlock macro. */#endif
&&& oops_log("oops_log test!!!!!!!!!!/n");
&&& return 0;}
static void __exit oops_log_exit(void){&&& if(filp)&&& {&&&&&&& filp_close(filp,NULL);&&& }
&&& printk("oops_log unloaded!/n");}
EXPORT_SYMBOL(oops_log);
module_init(oops_log_init);module_exit(oops_log_exit);
MODULE_LICENSE("GPL");
此log以模块形式加载,oops_log为记录log的函数接口。在oops_log_init中调用oops_log进行简单的测试。CFG_OOPS_RD_TEST宏控制是否进行读测试,以验证内核态log是否记录成功-sh-3.1# insmod /usr/local/esw/drivers/oopslog.koUsing fallback suid methodoops_log module loaded successfully!oops_log: oops_log test!!!!!!!!!!
由log信息可知,内核态读写文件正常。
CFG_OOPS_LOCK_SUPPORT控制内核态读写log文件时是否进行保护。当打开此开关时,测试结果如下:-sh-3.1# insmod /usr/local/esw/drivers/oopslog.koUsing fallback suid methodoops_log module loaded successfully!BUG: scheduling while atomic: insmod/826/0xCall Trace:[ef12f700] [c00081e0] show_stack+0x3c/0x194 (unreliable)[ef12f730] [c0019b2c] __schedule_bug+0x64/0x78[ef12f750] [c0350f50] schedule+0x324/0x34c[ef12f7a0] [c03515c0] schedule_timeout+0x68/0xe4[ef12f7e0] [c027938c] fsl_elbc_run_command+0x138/0x1c0[ef12f820] [c0275820] nand_do_read_ops+0x130/0x3dc[ef12f880] [c0275ebc] nand_read+0xac/0xe0[ef12f8b0] [c0262d98] part_read+0x5c/0xe4[ef12f8c0] [c017bcac] jffs2_flash_read+0x68/0x254[ef12f8f0] [c0170550] jffs2_read_dnode+0x60/0x304[ef12f940] [c017088c] jffs2_read_inode_range+0x98/0x180[ef12f970] [c016e610] jffs2_do_readpage_nolock+0x94/0x1ac[ef12f990] [c016ee04] jffs2_write_begin+0x2b0/0x330[ef12fa10] [c005144c] generic_file_buffered_write+0x11c/0x8d0[ef12fab0] [c0051e48] __generic_file_aio_write_nolock+0x248/0x500[ef12fb20] [c0052168] generic_file_aio_write+0x68/0x10c[ef12fb50] [c007ca80] do_sync_write+0xc4/0x138[ef12fc10] [f107c0dc] oops_log+0xdc/0x1e8 [oopslog][ef12fe70] [f3087058] oops_log_init+0x58/0xa0 [oopslog][ef12fe80] [c00477bc] sys_init_module+0x130/0x17dc[ef12ff40] [c00104b0] ret_from_syscall+0x0/0x38--- Exception: c01 at 0xff29658&&& LR = 0xBUG: 提示信息表示在拥有自旋锁的时候,进程休眠。原子上下文是不允许休眠的。fsl_elbc_run_command+0x138/0x1c0的实现是执行了写操作后,需要等待中断中发送的同步信号量。 &385&&&&&&& /* wait for FCM complete flag or timeout */&386&&&&&&& ctrl-&irq_status = 0;&387&&&&&&& wait_event_timeout(ctrl-&irq_wait, ctrl-&irq_status,&388&&&&&&&&&&&&&&&&&&&&&&&&&& FCM_TIMEOUT_MSECS * HZ/1000);
1122/* NOTE: This interrupt is also used to report other localbus events,1123 * such as transaction errors on other chipselects.& If we want to1124 * capture those, we'll need to move the IRQ code into a shared1125 * LBC driver.1126 */11271128static irqreturn_t fsl_elbc_ctrl_irq(int irqno, void *data)1129{1130&&&&&&& struct fsl_elbc_ctrl *ctrl =1131&&&&&&& struct elbc_regs __iomem *lbc = ctrl-&1132&&&&&&& __be32 status = in_be32(&lbc-&ltesr) & LTESR_NAND_MASK;11331134&&&&&&& if (status) {1135&&&&&&&&&&&&&&& out_be32(&lbc-&ltesr, status);1136&&&&&&&&&&&&&&& out_be32(&lbc-&lteatr, 0);11371138&&&&&&&&&&&&&&& ctrl-&irq_status =1139&&&&&&&&&&&&&&& smp_wmb();1140&&&&&&&&&&&&&&& wake_up(&ctrl-&irq_wait);11411142&&&&&&&&&&&&&&& return IRQ_HANDLED;1143&&&&&&& }11441145&&&&&&& return IRQ_NONE;1146}
由于nand Flash读写的设计原因,导致不能在原子上下文中读写Flash中的文件。
改用NFS网络文件系统,此时log文件在服务器上。-sh-3.1# insmod /usr/local/esw/drivers/oopslog.koUsing fallback suid methodoops_log module loaded successfully!BUG: scheduling while atomic: insmod/818/0xCall Trace:[efbc7890] [c00081e0] show_stack+0x3c/0x194 (unreliable)[efbc78c0] [c0019b2c] __schedule_bug+0x64/0x78[efbc78e0] [c0350f50] schedule+0x324/0x34c[efbc7930] [c010e8f8] nfs_wait_bit_killable+0x24/0x4c[efbc7940] [c0351864] __wait_on_bit+0x98/0xec[efbc7960] [c0351918] out_of_line_wait_on_bit+0x60/0x74[efbc79a0] [c010e8bc] nfs_wait_on_request+0x34/0x4c[efbc79b0] [c0113e08] nfs_sync_mapping_wait+0xfc/0x3a4[efbc7a10] [c0114190] nfs_wb_page+0xe0/0x120[efbc7a60] [c0111a04] nfs_readpage+0xa4/0x424[efbc7aa0] [c0052a68] generic_file_aio_read+0x16c/0x654[efbc7b20] [c0107444] nfs_file_read+0xd4/0x11c[efbc7b50] [c007cbb8] do_sync_read+0xc4/0x138[efbc7c10] [f106e134] oops_log+0x134/0x1e8 [oopslog][efbc7e70] [f107c058] oops_log_init+0x58/0xa0 [oopslog][efbc7e80] [c00477bc] sys_init_module+0x130/0x17dc[efbc7f40] [c00104b0] ret_from_syscall+0x0/0x38--- Exception: c01 at 0xff29658&&& LR = 0xoops_log test!!!!!!!!!!
现象一样,在拥有自旋锁的时候不能读写网络文件系统中的文件。
5.6.2&中断上下文5.6.2.1&读写文件在中断服务程序中调用oops_log接口,在内核态进行文件读写-sh-3.1# KERNEL INFO: FISH: enter fpga_interrupt_card0_handler------------[ cut here ]------------Kernel BUG at c016e8c4 [verbose debug info unavailable]Oops: Exception in kernel mode, sig: 5 [#1]PREEMPT MPC837x RDBModules linked in: fish oopslog gpio_driverNIP: c016e8c4 LR: c016e8b4 CTR: REGS: c0451900 TRAP: 0700&& Not tainted& (2.6.25)MSR:
&EE,ME,IR,DR&& CR: & XER: TASK = c] 'swapper' THREAD: c0450000GPR00: 519b0 c00 00 GPR08: ef85828c c00003 efb88 fffa000
GPR16: 00 51a00 ef83d2c0 c00000 GPR24: 45 00018 ef41d330 ef0b04f8 c0feaf20 NIP [c016e8c4] jffs2_write_end+0x12c/0x328LR [c016e8b4] jffs2_write_end+0x11c/0x328Call Trace:[c04519b0] [c016e8b4] jffs2_write_end+0x11c/0x328 (unreliable)[c04519f0] [c00514c8] generic_file_buffered_write+0x198/0x8d0[c0451a90] [c0051e48] __generic_file_aio_write_nolock+0x248/0x500[c0451b00] [c0052168] generic_file_aio_write+0x68/0x10c[c0451b30] [c007ca80] do_sync_write+0xc4/0x138[c0451bf0] [f107c0dc] oops_log+0xdc/0x1e8 [oopslog][c0451e50] [f308e45c] fpga_interrupt_card0_handler+0x34/0xc4 [fish][c0451e70] [c004b6a8] handle_IRQ_event+0x5c/0xb0[c0451e90] [c004d8d8] handle_level_irq+0xbc/0x180[c0451eb0] [c0006494] do_IRQ+0xa0/0xc4[c0451ec0] [c0010b48] ret_from_except+0x0/0x14--- Exception: 501 at cpu_idle+0xa0/0x100&&& LR = cpu_idle+0xa0/0x100[c0451f80] [c00092a0] cpu_idle+0xe4/0x100 (unreliable)[c0451fa0] [cxc0353074[c0451fc0] [c04079b4] start_kernel+0x258/0x2dc[c0451ff0] [x3438Instruction dump:389dffd8 7cc3da14 7fc5f378 54ecb78 7cfb3a14 7d1bd050 4800599d b78 5400012e &0f3f4 3d6072cf Kernel panic - not syncing: Fatal exception in interruptRebooting in 180 seconds..
因为在中断上下文,无论是否持有锁都不能休眠,休眠将导致一个bug,进而引发一个Oops,最终导致内核Panic。
因为在中断上下文(包括硬件中断及软件中断)中无法进行文件读写,也就意味着当在中断处理程序中发生异常导致Oops时,无法在异常处理中将Oops信息保存到文件中。
本质上是因为内核态进行文件读写时有休眠的情况,这将导致系统异常。因此一个备选方案是开发新的不休眠的设备驱动程序,将Oops 信息保存到其他的存储区域中。但因为此信息不是基于文件系统的,因此用户无法直接读取该log信息,需要用户开发相关解析log的工具。
5.6.2.2&直接读写非易失性存储器非易失性介质包括Flash、NVRAM、EEPROM,EEPROM因为价格原因一般大小有限,而NVRAM一般较贵,还需要额外的电池供电,在嵌入式系统中非特殊原因一般都不会配置,而Flash是最常见的嵌入式存储介质。但内核的Flash驱动,为了系统的整体性能,在写入数据后需要等待中断信号,因此会休眠。
修改内核的Flash驱动会产生较大的影响,因此最好的办法是重写一套简单的Flash驱动,写入后,采用查询的办法,等待写入完毕或者超时,若此时间较长则会对系统的响应速度产生影响,对于一定的实时应用会产生影响,因此需要评估,应该保证利用Flash来存储oops log不会影响到系统的正常运转。
从实现来说,利用NVRAM来存储log,驱动最简单,最重要的是其写入速度很快,无需额外延时等待,对系统的性能影响最小。
所有的oops都将以int die(const char *str, struct pt_regs *regs, long err)为入口,其中oops_enter表示oops处理开始,oops_exit标识oops处理结束。在此期间,有很多地方打印oops输出信息,若在每个地方都添加额外的代码将输出信息保存到Flash中,改动的地方太多,且可移植性不好。因为最终的输出信息都由printk输出,因此最好的办法是在printk中根据oops开始结束标志来决定是否保存一份额外的输出信息至Flash中。Oops处理完毕后,清除oops标识,则以后printk的信息不会额外保存到Flash中。
由于在内核中以裸数据的形式将oops log保存在非易失性介质中,用户态需要工具来解析这些数据,并将其保存成log文件。
用户空间mmap即可将Flash或者NVRAM映射给用户直接读取。定时查询是否有oops信息,若有则将log从Flash中读取出来,保存成log文件存在Flash文件系统中。同时调用相关脚本获取系统其他信息,和oops log文件一起作为一个完整的log信息。正常情况下,从flash中读取log信息后,将log从Flash中删除,以备下次再记录log信息,但是Flash擦写需要一定的时间,在系统正常运行过程中,此擦写会对系统产生一定的影响。因此后台检测程序至负责动态读取log文件,而log信息的擦除会在在系统业务尚未运行前进行,这样就不会对系统产生影响。
6&PowerPC的EABI规范ABI或EABI(Extend ABI)通常是处理器体系结构的一部分,它与平台是紧密相连的。可以把ABI理解为一套规则,这套规则一般包括定义了以下内容:1、如何使用机器的寄存器。比如用那个通用寄存器来作stack pointer和frame pointer。2、堆栈的结构。3、参数传递规则。
特定于那个平台的编译器和链接器实现都要遵循这些约定。
6.1&寄存器的使用规则GPR0:随意使用,无需保存GPR1:该寄存器保存堆栈的栈顶指针,也就是SP指针。GPR2:专用GPR3-GPR4:使用这两个寄存器保存程序的返回值。GPR3-GPR10:用于传递函数参数。当参数多于8个时,使用存储器的栈传送。GPR11-GPR12: 随意使用,无需保存GPR13:专用,该寄存器用于保存sdata段的基地址指针。GPR14-GPR31:程序可以自由使用这几个寄存器。
共分为三类Volatile:随意使用,函数调用或者中断时不用保存Nonvolatile:非易失性的,使用前需要保存,用后恢复Dedicated:专用的,如R1作为SP,不能用于其他用途,因为中断可能随时来临
6.2&堆栈的结构PowerPC架构没有专门的push、pop指令来实现堆栈结构,因此需要有一套规范来支持参数传递、Nonvolatile寄存器的保存以及局部变量等。将这些数据以统一的格式存放在栈中。
6.2.1&栈的增减规则在EABI规范中,SP总是以双字对齐的方式指向当前stack frame的底部,新的SP递减增长。但是对于Linux平台,SP的对齐单位为16字节。
6.2.2&栈的结构进行函数调用时,堆栈将向下增长,并按照特定的格式保存现场,以防止中断后无法返回到调用处。
Back Chain:当前栈顶指针寄存器SP保存上一个栈桢的Back Chain的地址。当函数返回时,SP指回上一个栈桢。LR Save Word:保存LR寄存器的值,用于函数返回,即调用函数处的下一条指令地址。此值为程序当前栈的上一个栈的1字偏移处。Padding:自动填充补齐,将Linux的栈对齐在16字节边界上。Parameter Save Area:用于存放函数参数。当参数多于R3-R10 8个时才用此区域。Local Variable Area:用于存放局部变量。首先选择GPR0/GPR11/ GPR12保存局部变量,同时R3-R10中未用作函数参数的寄存器也可以保存局部变量,只有当寄存器不够用时才用此区域。CR Save Area:若函数可能更改CR,则保存CR寄存器。32-bit General Register Save Area:保存函数用到的32位寄存器。若使用了GPR14- GPR31之间的寄存器,则需要将之上至GPR31的所有寄存器入栈保存,如使用了GPR16,则应保存GPR16- GPR31。
注意,根据具体的函数不一样,栈桢的内容是不一样的,最小的栈桢只有Back Chain和LR Save Word,共8字节,栈始终对齐在8字节边界,否则填充补齐。对于PowerPC Linux,此值为16字节。
Back Chain和LR Save Word紧紧相邻,且Back Chain是递减的,这些特征便于定位哪个是函数调用的边界。
6.3&从反汇编来看EABI的实现Gcc工具链中的objdump可以将efl格式的印象反汇编,格式如下:powerpc-linux-objdump -d vmlinux & vmlinux-1.6.25.Spowerpc-linux-objdump -S vmlinux & vmlinux-mix-1.6.25.S-S尽可能的将c和反汇编代码对应起来,便于分析,当未打开-g选项时,-S和-d效果一样,无法获得源文件。
下面我们以arch/powerpc/kernel/trap.c中的die来分析EABI的实现。cat vmlinux-1.6.25.S | grep die。。。c000e2dc &die&:。。可知到die函数的实现在c000e2dc
c000e2dc &die&:c000e2dc:&94 21 ff e0 &stwu&&& r1,-32(r1)// 栈空间递减32个字节,Linux中栈对齐在16字节上,而EABI规范只要求8字节对齐即可,同时u表示将递减前的r1放在新的栈的0字节偏移处Back Chain Word,这样程序返回时即可恢复原有的栈c000e2e0:&7c 08 02 a6 &mflr&&& r0将LR链接寄存器即程序的返回地址暂存中r0中c000e2e4:&bf 41 00 08 &stmw&&& r26,8(r1)将r26-r31共6个寄存器24个字节入栈,偏移量为8字节,32个字节的栈空间使用完毕c000e2e8:&3f a0 c0 43 &lis&&&& r29,-16317c000e2ec:&7c 7c 1b 78 &mr&&&&& r28,r3c000e2f0:&90 01 00 24 &stw&&&& r0,36(r1)将r0中的程序返回地址保存在上一个栈的36-32=4字节偏移的地方,这正是LR Save Word的偏移量,因为0字节偏移处存放的是上一个栈的位置Back Chain Wordc000e2f4:&7c 9b 23 78 &mr&&&&& r27,r4c000e2f8:&7c ba 2b 78 &mr&&&&& r26,r5r3, r4, r5为函数的参数,这和X86不一样,因为嵌入式平台上通用寄存器较多,当参数较少时将会使用寄存器传参,多余的参数才放在栈上,这样效率更高些c000e2fc:&48 01 0b a5 &bl&&&&& c001eea0 &oops_enter&c000e300:&80 1d 23 c0 &lwz&&&& r0,9152(r29)c000e304:&2f 80 00 00 &cmpwi&& cr7,r0,0c000e308:&41 9e 01 28 &beq-&&& cr7,c000e430 &die+0x154&。。。。。。。。。c000e448:&3d 20 c0 3b &lis&&&& r9,-16325c000e44c:&38 89 e7 1c &addi&&& r4,r9,-6372c000e450:&4b ff ff 5c &b&&&&&& c000e3ac &die+0xd0&c000e454:&3c 60 c0 3a &lis&&&& r3,-16326c000e458:&38 63 4f 08 &addi&&& r3,r3,20232c000e45c:&48 01 0b cd &bl&&&&& c001f028 &panic&c000e460:&48 34 2c fd &bl&&&&& c035115c &preempt_schedule&c000e464:&4b ff ff 94 &b&&&&&& c000e3f8 &die+0x11c&c000e468:&48 01 09 f9 &bl&&&&& c001ee60 &oops_exit&c000e46c:&7f 43 d3 78 &mr&&&&& r3,r26c000e470:&48 01 4a 65 &bl&&&&& c0022ed4 &do_exit&调用do_exit后就完了,die函数怎么没有消除工作呢?是因为do_exit后,进程就消亡了,其栈空间会统一全部释放而无需一层层释放呢?
int die(const char *str, struct pt_regs *regs, long err){&static struct {&&spinlock_&&u32 lock_&&int lock_owner_&} die = {&&.lock =&&&__SPIN_LOCK_UNLOCKED(die.lock),&&.lock_owner =&&-1,&&.lock_owner_depth =&0&};&static int die_&
&if (debugger(regs))&&return 1;
&oops_enter();
&if (die.lock_owner != raw_smp_processor_id()) {&&console_verbose();&&spin_lock_irqsave(&die.lock, flags);&&die.lock_owner = smp_processor_id();&&die.lock_owner_depth = 0;&&bust_spinlocks(1);&&if (machine_is(powermac))&&&pmac_backlight_unblank();&} else {&&local_save_flags(flags);&}
&if (++die.lock_owner_depth & 3) {&&printk("Oops: %s, sig: %ld [#%d]/n", str, err, ++die_counter);#ifdef CONFIG_PREEMPT&&printk("PREEMPT ");#endif#ifdef CONFIG_SMP&&printk("SMP NR_CPUS=%d ", NR_CPUS);#endif#ifdef CONFIG_DEBUG_PAGEALLOC&&printk("DEBUG_PAGEALLOC ");#endif#ifdef CONFIG_NUMA&&printk("NUMA ");#endif&&printk("%s/n", ppc_md.name ? ppc_md.name : "");
&&print_modules();&&show_regs(regs);&} else {&&printk("Recursive die() failure, output suppressed/n");&}
&bust_spinlocks(0);&die.lock_owner = -1;&add_taint(TAINT_DIE);&spin_unlock_irqrestore(&die.lock, flags);
&if (kexec_should_crash(current) ||&&kexec_sr_activated(smp_processor_id()))&&crash_kexec(regs);&crash_kexec_secondary(regs);
&if (in_interrupt())&&panic("Fatal exception in interrupt");
&if (panic_on_oops)&&panic("Fatal exception");
&oops_exit();&do_exit(err);
&return 0;}
c001ee60 &oops_exit&:c001ee60:&94 21 ff f0 &stwu&&& r1,-16(r1)c001ee64:&7c 08 02 a6 &mflr&&& r0c001ee68:&90 01 00 14 &stw&&&& r0,20(r1)c001ee6c:&4b ff fe c1 &bl&&&&& c001ed2c &do_oops_enter_exit&c001ee70:&4b ff fe 39 &bl&&&&& c001eca8 &init_oops_id&c001ee74:&3d 20 c0 45 &lis&&&& r9,-16315c001ee78:&3c 60 c0 3a &lis&&&& r3,-16326c001ee7c:&39 29 52 88 &addi&&& r9,r9,21128c001ee80:&38 63 69 20 &addi&&& r3,r3,26912c001ee84:&80 a9 00 00 &lwz&&&& r5,0(r9)c001ee88:&80 c9 00 04 &lwz&&&& r6,4(r9)c001ee8c:&48 00 13 55 &bl&&&&& c00201e0 &printk&c001ee90:&80 01 00 14 &lwz&&&& r0,20(r1)c001ee94:&38 21 00 10 &addi&&& r1,r1,16c001ee98:&7c 08 03 a6 &mtlr&&& r0c001ee9c:&4e 80 00 20 &blr函数入口,保存SP,函数返回时从栈上取出程序返回的地址,并恢复SP,blr即跳转到返回地址处。
c001eea0 &oops_enter&:c001eea0:&94 21 ff f0 &stwu&&& r1,-16(r1)c001eea4:&7c 08 02 a6 &mflr&&& r0c001eea8:&93 e1 00 0c &stw&&&& r31,12(r1)c001eeac:&90 01 00 14 &stw&&&& r0,20(r1)c001eeb0:&48 1e e2 cd &bl&&&&& c020d17c &debug_locks_off&c001eeb4:&80 01 00 14 &lwz&&&& r0,20(r1)c001eeb8:&83 e1 00 0c &lwz&&&& r31,12(r1)c001eebc:&38 21 00 10 &addi&&& r1,r1,16c001eec0:&7c 08 03 a6 &mtlr&&& r0c001eec4:&4b ff fe 68 &b&&&&&& c001ed2c &do_oops_enter_exit&栈递减16个字节,将r31保存在12字节偏移处,0和4字节偏移处分别为上一个栈的地址及程序的返回地址。因此8字节偏移处没有使用,只是为了16字节对齐而用。
在调用b&&&&&& c001ed2c &do_oops_enter_exit&之前,已经将栈恢复了,因此到程序跳转到do_oops_enter_exit后,因为栈在oops_enter这一级并没有记录,因此Backtrace中没有oops_enter的调用轨迹。-fnoomit-frame-pointer编译选项可以防止这种栈丢失的现象。
6.4&从show_stack来看函数是如何调用的
void show_stack(struct task_struct *tsk, unsigned long *stack){&unsigned long sp, ip, lr,&int count = 0;&int firstframe = 1;
&sp = (unsigned long)&if (tsk == NULL)&&tsk =&if (sp == 0) {&&if (tsk == current)&&&asm("mr %0,1" : "=r" (sp));&&else&&&sp = tsk-&thread.&}
&lr = 0;&printk("Call Trace:/n");&do {&&if (!validate_sp(sp, tsk, MIN_STACK_FRAME))&&&
&&stack = (unsigned long *)// 获得当前栈&&newsp = stack[0];// 获得上一个栈的地址&&ip = stack[FRAME_LR_SAVE];// 获得程序返回地址&&if (!firstframe || ip != lr) {&&&printk("["REG"] ["REG"] ", sp, ip);&&&print_symbol("%s", ip);&&&if (firstframe)&&&&printk(" (unreliable)");&&&printk("/n");&&}&&firstframe = 0;
&&/*&& * See if this is an exception frame.&& * We look for the "regshere" marker in the current frame.&& */&&if (validate_sp(sp, tsk, INT_FRAME_SIZE)&&&&& && stack[FRAME_MARKER] == REGS_MARKER) {&&&struct pt_regs *regs = (struct pt_regs *)&&&&(sp + STACK_FRAME_OVERHEAD);&&&printk("--- Exception: %lx", regs-&trap);&&&print_symbol(" at %s/n", regs-&nip);&&&lr = regs-&&&&print_symbol("&&& LR = %s/n", lr);&&&firstframe = 1;&&}
&&sp =&} while (count++ & kstack_depth_to_print);}
根据当前栈底即可回朔每一个每一层栈及对应的函数调用过程,但没有打印栈里面的内容,这对于分析数据的变化过程带来不便。因此可以完善PowerPC Linux内核此处的处理过程,便于更精准的定位问题。
7&如何分析OOPS7.1&如何分析backtrace7.2&如何分析栈8&Oops典型实例9&参考资料
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:526924次
积分:7888
积分:7888
排名:第720名
原创:231篇
转载:15篇
评论:421条
(1)(1)(1)(1)(1)(1)(2)(1)(1)(1)(2)(1)(1)(1)(4)(2)(11)(3)(2)(2)(6)(5)(3)(6)(12)(4)(11)(17)(23)(10)(10)(17)(8)(8)(13)(9)(5)(1)(1)(5)(11)(20)(1)

我要回帖

更多关于 停止运行此脚本 的文章

 

随机推荐