《Linux内核设计与实现》——第18章(调试)

一、准备开始

在这一章里,调试的主要思想是让bug重现,但是在内核中这并不是很容易做到的。因此,在跟踪bug的时候,掌握的信息越多越好。

二、内核中的bug

内核bug的原因可能有:

  • 错误代码
  • 同步时发生的错误,例如共享变量锁定不当
  • 错误的管理硬件
  • ……

内核bug发作的症状可能有:

  • 降低所有程序的运行性能
  • 毁坏数据
  • 使得系统处于死锁状态
  • ……

内核开发比起用户开发要多考虑一些独特的问题,比如:

  • 定时限制
  • 竞争条件
  • ……
    原因是允许多个线程在内核中同时运行。

三、通过打印来调试

这里说的打印是指的内核的格式化打印函数printk(),因为它有自己的一些特殊的功能:

1.健壮性

健壮性的意思是,在任何时候,任何地方都能调用它。

  • 在中断上下文和进程上下文中被调用
  • 在任何持有锁时被调用
  • 在多处理器上同时被调用,并且不必使用锁。

弹性极佳。
唯一的例外是【在系统启动过程的初期就要输出】这种情况,需要用early_printk()代替,两者功能完全相同。

2.日志等级

printk()和printf()在使用上最主要的区别就是前者可以指定一个日志级别,内核根据这个级别来判断是否在终端上打印消息。内核把级别比某个特定值低的所有消息显示在终端上。
这里写图片描述
如果没有特别特别指定,函数会选用默认的DEFAULT_MESSAGE_LOGLEVEL,在当前来看是KERN_WARNING,即一个警告。最好还是给自己的消息指定一个记录等级。
内核会把这些记录等级转化为0-7这几个等级,对应表中从上到下,数字越小越重要,也就是说:

0 KERN_EMERG 最重要
……
7 KERN_DEBUG 最不重要

对于调试信息, 有两种赋予记录等级的方法:

  1. 保持终端的默认记录等级不变,给所有调试信息KERN_CRIT或更低的等级。
  2. 给所有调试信息KERN_DEBUG等级,调整终端的默认记录等级。

3.记录缓冲区

内核消息是保存在一个环形队列中,这个环形队列就是它的记录缓冲区。
大小是可以在编译时进行调整的,但是在单处理器的系统上默认值是16kb。
也就是说内核在同一时间只能保存16kb的内核消息,再多的话新消息就会覆盖老消息,读写都是按照环形队列方式操作的。

优点:

健壮性:在中断上下文中也可以方便的使用。
简单性:使记录维护起来更容易。

缺点:
可能会丢失消息。

4.syslogd和klogd

这是两个用户空间的守护进程,klogd从记录缓冲区中获取内核消息,再通过syslogd守护进程将他们保存在系统日志文件中。

(1)klogd

既可以从/proc/kmsg文件中,也可以通过syslog()系统调用读取这些消息。
默认是/proc方式。
两种情况klogd都会阻塞,知道有新的内核消息可供读出,唤醒之后默认处理是将消息传给syslogd。
可以通过-c标志来改变终端的记录等级

(2)syslogd

将它接收到的所有消息添加到一个文件中,默认是/var/log/messages。

四、oops

oops是内核告知用户有不幸发生的最常用的方式。
内核很难自我修复,也不能将自己杀死,只能发布oops,过程包括:

  • 向终端上输出错误消息
  • 输出寄存器中保存的信息
  • 输出可供跟踪的回溯线索

通常发送完oops之后,内核会处于一种不稳定状态。

关于oops发生的时机:

发生在中断上下文:内核无法继续,会陷入混乱,导致系统死机
发生在idle进程或init进程(0号进程和1号进程),同上
发生在其他进程运行时,内核会杀死该进程并尝试着继续执行

oops发生的可能原因:

内存访问越界
非法的指令
……

oops中包含的重要信息:寄存器上下文和回溯线索

回溯线索:显示了导致错误发生的函数调用链。
寄存器上下文信息也很有用,比如帮助冲进引发问题的现场

五、内核调试配置选项

位于内核配置编辑器的内核开发菜单项中,都依赖于CONFIG_DEBUG_KERNEL。

slab layer debugging slab层调试选项
high-memory debugging 高端内存调试选项
I/O mapping debugging I/O映射调试选项
spin-lock debugging 自旋锁调试选项
stack-overflow debugging 栈溢出检查选项
sleep-inside-spinlock checking 自旋锁内睡眠选项
……

原子操作:指那些能够不分隔执行的东西;在执行时不能中断否则就
是完不成的代码。
例如;正在使用一个自旋锁或禁止抢占的代码。
使用锁时睡眠是引发死锁的元凶。

六、引发bug并打印信息

1.BUG()和BUG_ON()

被调用时会引发oops,导致栈的回溯和错误信息的打印。
可以把这些调用当做断言使用,想要断言某种情况不该发生:

if (bad_thing)
BUG();
或:
BUG_ON(bad_thing);

2.BUILD_BUG_ON()

与BUG_ON()作用相同,仅在编译时调用。

3.panic()

可以引发更严重的错误,不但会打印错误信息,还会挂起整个系统。

4.dump_stack()

只在终端上打印寄存器上下文和函数的跟踪线索。

七、神奇的系统请求键

这个功能可以通过定义CONFIG_MAGIC_SYSRQ配置选项来启用。SysRq(系统请求)键在大多数键盘上都是标准键。
该功能被启用时,无论内核出于什么状态,都可以通过特殊的组合键和内核进行通信。
除了配置选项以外,还要通过一个sysctl用来标记该特性的开或关,启动命令如下:

echo 1 > /proc/sys/kernel/sysrq

Sysrq的几个命令:
- SysRq-s:将“脏”缓冲区跟硬盘交换分区同步
- SysRq-u:卸载所有的文件系统
- SysRq-b:重启设备

在一行内发送这三个键的组合可以重新启动濒临死亡的系统。

八、内核调试器的传奇

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的所有功能都能使用:

  • 读取和修改变量值
  • 设置断点
  • 设置关注变量
  • 单步执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值