本文也即《Linux Device Drivers》,LDD3的第四章Debuging Techniques的读书笔记之五,但我们不限于此内容。
开发中我们不能避免在执行驱动时引起系统fault,但fault并不意味就是panic,Linux还是robust的,对于驱动,通常只引 起正在使用该驱动的进程死掉,kernel在任何一个打开设备的进程死掉的时候会调用close开进行释放。虽然如此,但是oops发生的时候,即使我们 卸载了内核模块程序但是系统仍可能不正常,通常需要重启来恢复,这是在console会有一些信息打印出来,可能通过他们来获取程序bug问题。如果我们 的terminal不是系统的console,即无法显示printk的内容,可以通过dmesg来尝试。
例如无效的指针会产生oops消息。我们可以在scull_write中加入来实验一下:
*( int *) 0 =0 ; //产生一个NULL pointer的错误。加载后,执行带写操作的用户程序(我的用户程序为test),在dmesg中有以下的报错:
BUG: unable to handle kernel NULL pointer dereference at 00000000
IP: [] :scull:scull_write+0x24/0x260
*pde = 2b2d3067 *pte = 00000000
Oops: 0002 [#1] SMP
Modules linked in: scull vfat fat usb_storage fuse sco bridge stp bnep l2cap bluetooth sunrpc ip6t_REJECT nf_conntrack_ipv6 ip6table_filter ip6_tables ipv6 cpufreq_ondemand acpi_cpufreq dm_multipath kvm_intel kvm uinput snd_hda_intel snd_seq_dummy snd_seq_oss snd_seq_midi_event snd_seq snd_seq_device snd_pcm_oss snd_mixer_oss fglrx(P) snd_pcm snd_timer snd_page_alloc snd_hwdep snd ppdev e1000e i2c_i801 i2c_core soundcore serio_raw pcspkr dcdbas parport_pc iTCO_wdt ata_generic iTCO_vendor_support parport pata_acpi [last unloaded: microcode]Pid: 17913, comm: test Tainted: P (2.6.27.5-117.fc10.i686 #1)
EIP: 0060:[] EFLAGS: 00010296 CPU: 0
EIP is at scull_write +0x24/0x260 [scull ]
EAX: 00000040 EBX: 00000400 ECX: 000005dc EDX: b7f03000
ESI: eb270540 EDI: f8a3f376 EBP: f272ef74 ESP: f272ef18
DS: 007b ES: 007b FS: 00d8 GS: 0033 SS: 0068
Process test (pid: 17913, ti=f272e000 task=f25ac010 task.ti=f272e000)
Stack: f25ac348 00000000 f272ef38 c0429051 00000001 c0879c00 00000001 00000002
f8a41a20 f272ef54 000005dc b7f03000 00000000 eb270540 000005dc f272ef5c
00000040 f272ef74 c049041e 00000001 000005dc eb270540 f8a3f376 f272ef90
Call Trace:
[<c0429051>] ? finish_task_switch+0x2f/0xb0
[<c049041e>] ? rw_verify_area+0x76/0x97
[<f8a3f376>] ? scull_write+0x0/0x260 [scull]
[<c0490a70>] ? vfs_write+0x84/0xdf
[<c0490b64>] ? sys_write+0x3b/0x60
[<c0403c76>] ? syscall_call+0x7/0xb
[<c06a007b>] ? init_intel_cacheinfo+0x0/0x421
我们希望能够定于出现bug的地方,在上面的例子中:EIP is at scull_write+0x24/0x260 [scull] 。表明错误的地址在scull的模块中,位于函数scull_write,里面有两个数字,可以用来估计在函数中错误出现的位置,但这个位置是经过编译的 位置,函数总长为0x260,错误位于0x24,说明在开始部分出现bug。不是所有的bug都能准确定义位置,可以通过Call Trace来进行估计。Stack中列出出问题的部分,这里可能需要一些经验,例如如果0xa5a5a5a5a5,可能表示初始分配的空间。在x86中, 缺省用户空间小于0xc0000000,所以上面例子中,猜测f25ac348 为kernel空间。这种看stack的方式我认为比较难,都是估来估去,没有必要花费力气在此。
如果整个系统挂起(例如一个死循环引起kernel停止调度,但是在多CPU的系统中,其他的处理器仍可能可以调度,在单CPU系统中,缺省 preemption是关闭的,会引起停止调度),就不可能有什么oops消息显示,以前有个项目,开发机在实验室,我在办公室远程调用(还不在同一层 楼:(),系统挂机了好几次,只好不断地走来走去重启机器。实验室的环境过于恶劣,无法忍受。后来想办法把机器搬上来。但是实际上我们仍有一些方法,要么 我们需要防止它出现,要么在之后可以debug。
防止系统整个挂起,可以加入schedule(),例如假设我们能够在死循环中加入schedule()用于触发调度,允许其他进程从中获取 CPU时间,这使得我们kill掉进程成为可能。但是如果在正式程序中也使用schedule(),我们需要注意可能由多个程序都会同时使用到 driver,需要加锁保护,但是不要在一个持有spinlock的地方调用schedule( )。能够跟踪debug出现bug的位置,最直接的方式就是在程序中加入printk来进行定位。有时系统只是貌似挂起,实际上仍然可以执行调度,这是可 以使用SysRq键(Alt-SysRq-X)。具体详见documentation/sysrq.txt,下面是来自http://www.deansys.com/doc/ldd3/ch04s05.html#SystemHange.sect 对这些键的介绍:
r 关闭键盘原始模式; 用在一个崩溃的应用程序( 例如 X 服务器 )可能将你的键盘搞成一个奇怪的状态.
k 调用"安全注意键"( SAK ) 功能. SAK 杀掉在当前控制台的所有运行的进程, 给你一个干净的终端.
s 进行一个全部磁盘的紧急同步.
u umount. 试图重新加载所有磁盘在只读模式. 这个操作, 常常在 s 之后马上调用, 可以节省大量的文件系统检查时间, 在系统处于严重麻烦时.
b boot. 立刻重启系统. 确认先同步和重新加载磁盘.
p 打印处理器消息.
t 打印当前任务列表.
m 打印内存信息.
我们可以通过/proc/sys/kernel/sysrq来设置SysQp键是否开启。另外还有一个只写文件/proc/sysrq- trigger,特别适用于远程调用,例如我们在root下面,echo t > /proc/sysrq-trigger,相当于调用了SysRq键的X=t的情况,并且不受/proc/sys/kernel/sysrq是否打开的影 响,我们可以在dmesg或者console中看到相关信息。而SysRq的p有可能直接指出问题所在。我们还可以使用profiling的功能,但是这 部分LDD3介绍得很简略,如果需要,详见:documentation/basic_profiling.txt。LDD3还提了一个保护我们磁盘的方 式,就是使用只读来加载磁盘或者使用NFS的方式,这样来保障磁盘中的数据不会受到损害。