20135302魏静静——课本18章学习笔记
2016-03-28 00:00
204 查看
第十八章 调试
一、内核中的bug
内核bug的原因可能有:- 错误代码 - 同步时发生的错误,例如共享变量锁定不当 - 错误的管理硬件 - ……
内核bug发作的症状可能有:
- 降低所有程序的运行性能 - 毁坏数据 - 使得系统处于死锁状态 - ……
内核开发比起用户开发要多考虑一些独特的问题,比如:
- 定时限制 - 竞争条件 - …… 原因是允许多个线程在内核中同时运行。
二、通过打印来调试
1. 健壮性
弹性极佳的函数:任何时候、任何地方都能调用它可以在中断上下文和进程上下文中被调用
可以在任何持有锁时被调用
可以在多处理器上同时被调用
除非再启动过程中的初期就要在终端上输出
2. 日志等级
printk()和
printf()在使用上最主要的区别就是前者可以指定一个日志级别。内核通过这个级别来判断是否在终端上打印消息。
内核把级别比某个特定值低的所有消息显示在终端上。
如果没有指定一个记录等级,函数会选用默认的
DEFAULT_MASSAGE_LOGLEVEL。
内核会把这些记录等级转化,n指等级,从0-7,对应表中从上到下,数字越小越重要。
0 KERN_EMERG 最重要 7 KERN_DEBUG 最不重要
调试信息, 有两种赋予记录等级的方法:
保持终端的默认记录等级不变,给所有调试信息KERN_CRIT或更低的等级。
给所有调试信息KERN_DEBUG等级,调整终端的默认记录等级。
printk的输出日志级别如下:
等级 | 描述 |
---|---|
KERN_ EMERG | 一个紧急情况 |
KERN_ ALERT | 一个需要立即被注意到的错误 |
KERN_ CRIT | 一个临界情况 |
KERN_ ERR | 一个错误 |
KERN_ WARNING | 一个警告 |
KERN_ NOTICE | 一个普通的, 不过也有可能需要注意的情况 |
KERN_ INFO | 一条非正式的消息 |
KERN_ DEBUG | 一条调试信息--一般是冗余信息 |
3. 缓冲区
内核消息都被记录在环形队列中,以队列方式进行读写;大小可以通过设置CONFIGLOGBUF_SHIFT进行调整在单处理器上,该缓冲区大小默认为16KB,也就是说,超过的消息将覆盖旧消息
优势:
读写同步问题容易解决
记录的维护更加方便
4. 相关进程
用户空间的守护进程——klogd从记录缓冲区中读取内核信息,再通过syslogd守护进程将它们保存在系统日志文件中klogd会阻塞知道==直到有新的内核消息可供读出。被唤醒之后,它会读出新的内核消息并进行处理(默认情况下,就是传递给syslogd);
syslogd会将所有接收到的消息添加到(默认情况下是messages)文件中
三、oops
oops是内核告知用户有不幸发生的最常用的方式。内核很难自我修复,也不能将自己杀死,只能发布oops,过程包括:
- 向终端上输出错误消息 - 输出寄存器中保存的信息 - 输出可供跟踪的回溯线索
通常发送完oops之后,内核会处于一种不稳定状态。
关于oops发生的时机:
发生在中断上下文:内核无法继续,会陷入混乱,导致系统死机
发生在idle进程或init进程(0号进程和1号进程),同上
发生在其他进程运行时,内核会杀死该进程并尝试着继续执行
内和调试配置项:
配置项:CONFIGDEBUGKERNEL
选项 之 sleep-inside-spinlockchecking(自旋锁内睡眠选项)——正在使用自旋锁或者禁止抢占的代码进行的是原子操作,不可更改
探测范围:
正在使用锁的时候调用schedule();
正使用锁的时候以阻塞方式请求分配内存;
引用单CPU数据时睡眠
引发bug并打印信息:
利用BUG()以及BUG_ON()(因为大多数体系结构都把这两个函数定义成某种非法操作,可以触发oops)
当做断言或者条件语句
调用panic()函数会在打印错误信息的同时挂起系统
panic("terrible thing",terrible_thing);
调用dump_stack(),只在终端上打印寄存器上下文及函数的跟踪线索
系统请求键
配置项:CONFIGMAGICSYSR配置选项来启动;此外,通过/proc/sys/kernel/sysrq标记该特性的开关
优点:无论内核处于什么状态,都可以通过特殊的组合键跟内核进行通信
常见的命令
SysRq-b:重启设备
SysRq-o:关闭机器
SysRq-u:卸载所有的文件系统
SysRq-s:把所有已安装的文件系统都刷新到磁盘
oops中包含的重要信息:寄存器上下文和回溯线索
回溯线索:显示了导致错误发生的函数调用链。
寄存器上下文信息也很有用,比如帮助冲进引发问题的现场
ksymoops
回溯线索中的地址需要转化成有意义的符号名称——需要调用ksymoops命令。
并且还必须提供编译内核时产生的System.map。如果用的是模块,还需要一些模块信息。
kysmoop saved_oops.txt
kallsyms
现在的版本中不需要使用sysmoops这个工具,因为可能会发生很多问题,新版本中引入了kallsyms疼,可以通过定义CONFIG_KALLSYMS配置选项启用。四、内核调试器
1.gdb
可以使用标准的GNU调试器对正在运行的内核进行查看。针对内核启动调试器的方法与针对进程的方法大致相同:
gdb vmlinux /proc/kcore vmlinx:未经压缩的内核映像,区别于zImage或bImage,它存放于源代码树的根目录上。 /proc/kcore作为一个参数选项,是作为core文件来用的,通过它能够访问到内核驻留的高端内存。只有超级用户才能读取此文件的数据
可以使用gdb的所有命令来获取信息。例如:
打印一个变量的值: p global_variable 反汇编一个函数: disassemble function -g参数还可以提供更多的信息。
局限性:
没有办法修改内核数据
不能单步执行内核代码
2.kgdb
是一个补丁 ,可以让我们在远程主机上通过串口利用gdb的所有功能对内核进行调试。需要两台计算机:仪态运行带有kgdb补丁的内核,第二胎通过串行线使用gdb对第一台进行调试。
通过kgdb,gdb的所有功能都能使用:
- 读取和修改变量值 - 设置断点 - 设置关注变量 - 单步执行
五、探测系统
1.使用uid作为选择条件
一般情况下,加入特性时,只要保留原有的算法而把新算法加入到其他位置上,基本就能保证安全。可以把用户id(UID)作为选择条件来实现这种功能:
通过某种选择条件,安排到底执行哪种算法。
例如:
if (current-> uid !=7777) { /* 老算法…… */ } else { /* 新算法…… */ }
即,除了uid=7777的用户以外,其他所有的用户都是用的老算法,所以这个7777用户可以专门用来测试新算法。
2.使用条件变量
如果代码与进程无关,或者希望有一个针对所有情况都能使用的机制来控制某个特性,可以使用条件变量。这种方式比使用UID更简单,只需要创建一个全局变量作为一个条件选择开关:
如果该变量为0,就使用某一个分支上的代码;
否则,选择另外一个分支。
操控方式:某种接口,或者调试器。
3.使用统计量
这种方法常用于使用者需要掌握某个特定事件的发生规律的时候。方法是创建统计量,并提供某种机制访问其统计结果。
4.重复频率限制
当系统的调试信息过多的时候,有两种方式可以防止这类问题发生:重复频率限制
发生次数限制
相关文章推荐
- Android 开发中不错的工具
- SideBarEnhancements 插件
- 要进步要创新
- django 源码阅读二 命令check
- java.lang.IllegalStateException: No output folder
- Exception in thread "Timer-0" java.lang.OutOfMemor
- 6 --项目质量管理和项目人力资源管理
- Spring最新教程(译自Spring官网)——构建一个RESTful Web Service
- 2015年蓝桥杯省赛A组C/C++题目:猜年龄
- Android WebView 内处理302重定向
- Django-Model操作数据库(增删改查、连表结构)
- C++作业2
- 202. Happy Number
- 20145203盖泽双 《Java程序设计》第四周学习总结
- iOS 为label加删除线
- 流程
- 学Java图形界面的一次小的的总结
- POJ 3280 DP
- 做了两年程序员对未来迷茫怎么办?
- 在行为树(Behavior Tree)中使用黑板(BlackBoard)