Chinaunix首页 | 论坛 | 博客
  • 博客访问: 399506
  • 博文数量: 115
  • 博客积分: 2501
  • 博客等级: 少校
  • 技术积分: 1009
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-23 17:05
文章分类

全部博文(115)

文章存档

2011年(2)

2010年(86)

2009年(27)

我的朋友

分类: 嵌入式

2010-08-10 22:29:46

Linux设备驱动程序》学习笔记5—调试技术

一、内核中的调试支持

在进行内核调试之前,可以在自己所要移植的内核的源码目录下选择内核调试的配置选项,所有的这些选项都在 "kernel hacking" 菜单,不同体系结构的配置选项有所不同。

Kernel hacking à[*]Kernel debugging

这个选项只是使其他调试选项可用; 它应当打开, 但是它自己不激活任何的特性.

CONFIG_DEBUG_SLAB

这个重要的选项打开了内核内存分配函数的几类检查; 激活这些检查, 就可能探测到一些内存覆盖和遗漏初始化的错误. 被分配的每一个字节在递交给调用者之前都设成 0xa5, 随后在释放时被设成 0x6b. 你在任何时候如果见到任一个这种""模式重复出现在你的驱动输出(或者常常在一个 oops 的列表), 你会确切知道去找什么类型的错误. 当激活调试, 内核还会在每个分配的内存对象的前后放置特别的守护值; 如果这些值曾被改动, 内核知道有人已覆盖了一个内存分配区, 它大声抱怨. 各种的对更模糊的问题的检查也给激活了.

 

CONFIG_DEBUG_PAGEALLOC

满的页在释放时被从内核地址空间去除. 这个选项会显著拖慢系统, 但是它也能快速指出某些类型的内存损坏错误.

 

CONFIG_DEBUG_SPINLOCK

激活这个选项, 内核捕捉对未初始化的自旋锁的操作, 以及各种其他的错误( 例如 2 次解锁同一个锁 ).

 

CONFIG_DEBUG_SPINLOCK_SLEEP

这个选项激活对持有自旋锁时进入睡眠的检查. 实际上, 如果你调用一个可能会睡眠的函数, 它就抱怨, 即便这个有疑问的调用没有睡眠.

 

CONFIG_INIT_DEBUG

__init (或者 __initdata) 标志的项在系统初始化或者模块加载后都被丢弃. 这个选项激活了对代码的检查, 这些代码试图在初始化完成后存取初始化时内存.(装载、卸载模块后清除内存用)

 

CONFIG_DEBUG_INFO

这个选项使得内核在建立时包含完整的调试信息. 如果你想使用 gdb 调试内核, 你将需要这些信息. 如果你打算使用 gdb, 你还要激活 CONFIG_FRAME_POINTER.

 

CONFIG_MAGIC_SYSRQ

激活"魔术 SysRq". 我们在本章后面的"系统挂起"一节查看这个键.

 

CONFIG_DEBUG_STACKOVERFLOW

CONFIG_DEBUG_STACK_USAGE

这些选项能帮助跟踪内核堆栈溢出. 堆栈溢出的确证是一个 oops 输出, 但是没有任何形式的合理的回溯. 第一个选项给内核增加了明确的溢出检查; 2 个使得内核监测堆栈使用并作一些统计, 这些统计可以用魔术 SysRq 键得到.

 

CONFIG_KALLSYMS

这个选项("Generl setup/Standard features")使得内核符号信息建在内核中; 缺省是激活的. 符号选项用在调试上下文中; 没有它, 一个 oops 列表只能以 16 进制格式给你一个内核回溯, 这不是很有用.

 

CONFIG_IKCONFIG

CONFIG_IKCONFIG_PROC

这些选项("Generl setup"菜单)使得完整的内核配置状态被建立到内核中, 可以通过 /proc 来使其可用. 大部分内核开发者知道他们使用的哪个配置, 并不需要这些选项(会使得内核更大). 但是如果你试着调试由其他人建立的内核中的问题, 它们可能有用.

 

CONFIG_ACPI_DEBUG

"Power management/ACPI". 这个选项打开详细的 ACPI (Advanced Configuration and Power Interface) 调试信息, 它可能有用如果你怀疑一个问题和 ACPI 相关.

 

CONFIG_DEBUG_DRIVER

"Device drivers". 打开了驱动核心的调试信息, 可用以追踪低层支持代码的问题. 我们在第 14 章查看驱动核心.

 

CONFIG_SCSI_CONSTANTS

这个选项, "Device drivers/SCSI device support", 建立详细的 SCSI 错误消息的信息. 如果你在使用 SCSI 驱动, 你可能需要这个选项.

 

CONFIG_INPUT_EVBUG

这个选项("Device drivers/Input device support")打开输入事件的详细日志. 如果你使用一个输入设备的驱动, 这个选项可能会有用. 然而要小心这个选项的安全性的隐含意义: 它记录了你键入的任何东西, 包括你的密码.

 

CONFIG_PROFILING

这个选项位于"Profiling support"之下. 剖析通常用在系统性能调整, 但是在追踪一些内核挂起和相关问题上也有用.

    另外,在高版本的内核中,已有kgdb的支持,kgdb是调试内核的一种很好的工具。这会在我的学习笔记中单独出现。

以上都是LDD3中的内容,这里我只是做学习笔记而已,便于加深理解。这些选项并不会都出现各种体系结构中,具体的配置根据体系结构来配置。在驱动内核调试阶段,需要配置该选项,如果内核调试好后可以去掉部分选项以降低内核大小。

 

二、用打印调试

1printk

       内核中的printk函数类似于应用程序中的printf函数,可以用来在合适的地方输出监视信息。但它与printf有所不同,printk 允许你根据消息的严重程度对其分类, 通过附加不同的记录级别或者优先级在消息上. 你常常用一个宏定义来指示记录级别. 例如, KERN_INFO, 我们之前曾在一些打印语句的前面看到过, 是消息记录级别的一种可能值. 记录宏定义扩展成一个字串, 在编译时与消息文本连接在一起; 这就是为什么下面的在优先级和格式串之间没有逗号的原因. 这里有 2 printk 命令的例子, 一个调试消息, 一个紧急消息:

 

printk(KERN_DEBUG "Here I am: %s:%i\n", __FILE__, __LINE__);

printk(KERN_CRIT "I'm trashed; giving up on %p\n", ptr);  //记录级别的宏定义与后面格式串之间不用逗号

8 种可能的记录字串, 在头文件 里定义; 我们按照严重性递减的顺序列出它们:

KERN_EMERG

用于紧急消息, 常常是那些崩溃前的消息.

 

KERN_ALERT

需要立刻动作的情形.

 

KERN_CRIT

严重情况, 常常与严重的硬件或者软件失效有关.

 

KERN_ERR

用来报告错误情况; 设备驱动常常使用 KERN_ERR 来报告硬件故障.

 

KERN_WARNING

有问题的情况的警告, 这些情况自己不会引起系统的严重问题.

 

KERN_NOTICE

正常情况, 但是仍然值得注意. 在这个级别一些安全相关的情况会报告.

 

KERN_INFO

信息型消息. 在这个级别, 很多驱动在启动时打印它们发现的硬件的信息.

 

KERN_DEBUG

用作调试消息.

每个字串( 在宏定义扩展里 )代表一个在角括号中的整数. 整数的范围从 0 7, 越小的数表示越大的优先级.

一条没有指定优先级的 printk 语句缺省是 DEFAULT_MESSAGE_LOGLEVEL, kernel/printk.c 里指定作为一个整数,在我使用的linux-2.6.29.1内核中的printk.c文件中定义的整数是7

基于记录级别, 内核可能打印消息到当前控制台, 可能是一个文本模式终端, 串口, 或者是一台并口打印机. 如果优先级小于整型值 console_loglevel, 消息被递交给控制台, 一次一行( 除非提供一个新行结尾, 否则什么都不发送 ). 如果 klogd syslogd 都在系统中运行, 内核消息被追加到 /var/log/messages (或者另外根据你的 syslogd 配置处理), 独立于 console_loglevel. 如果 klogd 没有运行, 你只有读 /proc/kmsg ( dmsg 命令最易做到 )将消息取到用户空间. 当使用 klogd , 你应当记住, 它不会保存连续的同样的行; 它只保留第一个这样的行, 随后是, 它收到的重复行数.

 

变量 console_loglevel 初始化成 DEFAULT_CONSOLE_LOGLEVEL, 并且可通过 sys_syslog 系统调用修改. 一种修改它的方法是在调用 klogd 时指定 -c 开关, klogd manpage 里有指定. 注意要改变当前值, 你必须先杀掉 klogd, 接着使用 -c 选项重启它. 比如“klogd –c 7命令console_loglevel设置为7

文本文件 /proc/sys/kernel/printk 读写控制台记录级别. 这个文件有 4 个整型值: 当前记录级别, 适用没有明确记录级别的消息的缺省级别, 允许的最小记录级别, 以及启动时缺省记录级别. 写一个单个值到这个文件就改变当前记录级别成这个值; 下面是我在开发板上的串口终端执行的操作:

# pwd

/proc/sys/kernel

# cat printk

7       4       1       7    //这里的红色部分的7为当前记录级别

 

# echo 8 > printk    //改变当前记录级别为8

# cat printk

8       4       1       7    //当前记录级别变为8

继续实验

# echo 8 8 8 8 > printk  //改变当前记录级别, 适用没有明确记录级别的消息的缺省级别, 允许的最小记录级别, 以及启动时缺省记录级别都为8

# cat printk

8       8       8       8

在第二章的hello_module程序中,printk中使用日志级别高的KERN_ALERT来输出监视信息,来确保信息输出到控制台。

 

三、消息是如何记录的

首先介绍以下klogdsyslogd关于klogdsyslogd,我参考下面的内容进行理解:

出自:http://hi.baidu.com/bihailan/blog/item/973a1d8f608530e5f11f36a6.html

syslog

  这个服务程序太重要了,大部分的系统维护都会用到它,也就是网络或系统管理员经常用到的log 文件。daemon在执行时其实是执行两个程序:syslogdklogd,介绍如下,其分别负责不同种类的log,但全部的信息在系统刚开完机之后,会全部存放在/var/log/message(我使用的ubuntu-9.04是放在/var/log下的,其它的系统没查过)

klogd

  先介绍klogd是因为当一开始写入/var/log/message时,klogd所记录的信息会比syslogd 的顺序优先,原因是klogd所记录的是尚未进入操作系统的信息,但其实一开始的这些信息并不是由klogd所记录的,而是自加载kernel 时,就已经开始记录的。在系统尚未进入操作系统阶段,还在加载kernel及执行initrd时,会将信息先记录在/proc/kmsg文件中(因为在initrd阶段没有实体硬盘可供记录),等进入操作系统执行完klogd后,klogd再将/proc/kmsg的所有内容全数填入/var/log/message文件中,这也是为何在/var/log/message文件的一开始,依然可以看到刚开机在加载kernel以及initrd阶段的信息

    常用到的dmesg指令其实也就是将klogd检测出来的kernel信息,它直接通过console呈现在用户面前。在进入操作系统之后,kernel当然还在运行,而此时的kernel若检测到内部错误发生,klogd则会通过/boot/System.map文件找到kernel所在,如此一来,kernel就可以直接通过klogd的管道将信息再度呈现在用户面前。

syslogd

  在klogd填入所有kernel加载及initrd阶段的信息之后,接着执行的就是这一节所提到的服务级程序,而这些程序所产生的信息,就由syslogd负责整理,所以,这些信息会直接加在刚刚klogd的信息后面。在进入操作系统后就没有依照klogdsyslogd的顺序,只要产生信息,就直接依/etc/syslog.conf文件的定义,写入/var/log/message信息文件。

    /etc/syslog.conf文件可以找出所有目前系统通过syslogd所记录的信息,有时候在找某软件的信息时,也可以从这个文件中看出要以哪一个信息文件为主,当然前提是该软件必须支持syslogd的信息记录格式。

pirntk高于一定的优先级将消息打印到终端,否则写入一个 __LOG_BUF_LEN 字节长的环形缓存, 长度值从 4 KB 1 MB, /proc/kmsg是专门输出内核信息的地方。由配置内核时选择. 这个函数接着唤醒任何在等待消息的进程, 就是说, 任何在系统调用中睡眠或者在读取 /proc/kmsg 的进程. 2 个日志引擎的接口几乎是等同的, 但是注意, /proc/kmsg 中读取是从日志缓存中消费数据, 然而 syslog 系统调用能够选择地在返回日志数据地同时保留它给其他进程. 通常, 读取 /proc 文件容易些并且是 klogd 的缺省做法. dmesg 命令可用来查看缓存的内容, 不会冲掉它; 实际上, 这个命令将缓存区的整个内容返回给 stdout, 不管它是否已经被读过.

还是做个实验,mini2440板子上电启动后:

[rootMrFeng]#dmesg /proc/kmsg

Linux version 2.6.29.1 (root@ubuntu) (gcc version 4.3.2 (Sourcery G++ Lite 2008q3-72) ) #67 Thu Aug 5 08:32:44 PDT 2010

CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177

CPU: VIVT data cache, VIVT instruction cache

Machine: Study-S3C2440

Memory policy: ECC disabled, Data cache writeback

On node 0 totalpages: 16384

free_area_init_node: node 0, pgdat c038f654, node_mem_map c03b5000

  Normal zone: 128 pages used for memmap

  Normal zone: 0 pages reserved

  Normal zone: 16256 pages, LIFO batch:3

CPU S3C2440A (id 0x32440001)

S3C24XX Clocks, (c) 2004 Simtec Electronics

S3C244X: core 405.000 MHz, memory 101.250 MHz, peripheral 50.625 MHz

CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on

Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 16256

Kernel command line: noinitrd root=/dev/mtdblock3 rootfstype=jffs2 rw init=/linuxrc console=ttySAC0 mem=64M

irq: clearing subpending status 00000002

PID hash table entries: 256 (order: 8, 1024 bytes)

timer tcon=00500000, tcnt a4ca, tcfg 00000200,00000000, usec 00001e57

Console: colour dummy device 80x30

….

dmesg命令将向串口终端输出Linux内核信息,该信息是有klogd收集到的,保存在/proc/kmsg里面,该文件有fifo的特性,dmesg命令可以查看该信息,但是不会将信息从缓冲中拿走,而使用cat来查看该信息时将把该信息从缓冲中拿走。

[rootMrFeng]#cat /proc/kmsg 

<5>Linux version 2.6.29.1 (root@ubuntu) (gcc version 4.3.2 (Sourcery G++ Lite 2008q3-72) ) #67 Thu Aug 5 08:32:44 PDT 2010

<4>CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177

<4>CPU: VIVT data cache, VIVT instruction cache

<4>Machine: Study-S3C2440

<4>Memory policy: ECC disabled, Data cache writeback

<7>On node 0 totalpages: 16384

<7>free_area_init_node: node 0, pgdat c038f654, node_mem_map c03b5000

<7>  Normal zone: 128 pages used for memmap

<7>  Normal zone: 0 pages reserved

<7>  Normal zone: 16256 pages, LIFO batch:3

<4>CPU S3C2440A (id 0x32440001)

<6>S3C24XX Clocks, (c) 2004 Simtec Electronics

<4>S3C244X: core 405.000 MHz, memory 101.250 MHz, peripheral 50.625 MHz

<4>CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on

<4>Built 1 zonelists in Zone order, mobility grouping on.  Total pages: 16256

<5>Kernel command line: noinitrd root=/dev/mtdblock3 rootfstype=jffs2 rw init=/linuxrc console=ttySAC0 mem=64M

<4>irq: clearing subpending status 00000002

<4>PID hash table entries: 256 (order: 8, 1024 bytes)

[rootMrFeng]#cat /proc/kmsg    //再次运行cat命令,什么信息都没有了。

 

在停止 klogd , 如果你偶尔手工读取内核消息, 你会发现 /proc 看起来象一个 FIFO, 读者阻塞在里面, 等待更多数据. 显然, 你无法以这种方式读消息, 如果 klogd 或者其他进程已经在读同样的数据, 因为你要竞争它.

如果环形缓存填满, printk 绕回并在缓存的开头增加新数据, 覆盖掉最老的数据. 因此, 这个记录过程会丢失最老的数据. 这个问题相比于使用这样一个环形缓存的优点是可以忽略的. 例如, 环形缓存允许系统即便没有一个日志进程也可运行, 在没有人读它的时候可以通过覆盖旧数据浪费最少的内存. Linux 对于消息的解决方法的另一个特性是, printk 可以从任何地方调用, 甚至从一个中断处理里面, 没有限制能打印多少数据. 唯一的缺点是可能丢失一些数据.

如果 klogd 进程在运行, 它获取内核消息并分发给 syslogd, syslogd 接着检查 /etc/syslog.conf 来找出如何处理它们. syslogd 根据一个设施和一个优先级来区分消息; 这个设施和优先级的允许值在 中定义. 内核消息由 LOG_KERN 设施来记录, 在一个对应于 printk 使用的优先级上(例如, LOG_ERR 用于 KERN_ERR 消息). 如果 klogd 没有运行, 数据保留在环形缓存中直到有人读它或者缓存被覆盖.

如果你要避免你的系统被来自你的驱动的监视消息击垮, 你或者给 klogd 指定一个 -f (文件) 选项来指示它保存消息到一个特定的文件, 或者定制 /etc/syslog.conf 来适应你的要求. 但是另外一种可能性是采用粗暴的方式: 杀掉 klogd 和详细地打印消息在一个没有用到的虚拟终端上,[13] 或者从一个没有用到的 xterm 上发出命令 cat /proc/kmsg.

阅读(938) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~