《LINUX设备驱动程序》第4章(调试技术)学习笔记

发布时间:2016-12-10 14:56:14 编辑:www.fx114.net 分享查询网我要评论
本篇文章主要介绍了"《LINUX设备驱动程序》第4章(调试技术)学习笔记",主要涉及到《LINUX设备驱动程序》第4章(调试技术)学习笔记方面的内容,对于《LINUX设备驱动程序》第4章(调试技术)学习笔记感兴趣的同学可以参考一下。

第  4 章  调试技术 内核中的调试支持 开发的内核应当激活的配置选项. 除特别指出外, 所有的这些选项都在menu config的 "kernel hacking" 菜单. 注意有些选项不是所有体系架构都支持其中的选项. CONFIG_DEBUG_KERNEL 这个选项只是使其他调试选项可用; 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 这个选项(在"Generlsetup/Standard features"下)使得内核符号信息建 在内核中; 缺省是激活的. 符号选项用在调试上下文中;没有它, 一个 oops 列表只能以 16 进制格式给你一个内核回溯, 这不是很有用. CONFIG_IKCONFIG CONFIG_IKCONFIG_PROC 这些选项(在"Generlsetup"菜单)使得完整的内核配置状态被建立到内核 中, 可以通过 /proc 来使其可用. 大部分内核开发者知道他们使用的哪 个配置, 并不需要这些选项(会使得内核更大). 但是如果你试着调试由其 他人建立的内核中的问题, 它们可能有用. CONFIG_ACPI_DEBUG 在"Powermanagement/ACPI"下. 这个选项打开详细的 ACPI (Advanced Configuration and Power Interface) 调试信息, 它可能有用如果你怀疑 一个问题和 ACPI 相关. CONFIG_DEBUG_DRIVER 在"Devicedrivers"下. 打开了驱动核心的调试信息, 可用以追踪低层支 持代码的问题. 我们在第 14 章查看驱动核心. CONFIG_SCSI_CONSTANTS 这个选项, 在"Devicedrivers/SCSI device support"下, 建立详细的 SCSI 错误消息的信息. 如果你在使用 SCSI 驱动, 你可能需要这个选项. CONFIG_INPUT_EVBUG 这个选项(在"Devicedrivers/Input device support"下)打开输入事件的 详细日志. 如果你使用一个输入设备的驱动, 这个选项可能会有用. 然而 要小心这个选项的安全性的隐含意义: 它记录了你键入的任何东西, 包括 你的密码. CONFIG_PROFILING 这个选项位于"Profilingsupport"之下. 剖析通常用在系统性能调整, 但 是在追踪一些内核挂起和相关问题上也有用. 我们会再次遇到一些上面的选项, 当我们查看各种方法来追踪内核问题时. 但是 首先, 我们要看一下经典的调试技术:print 语句.   用打印调试: printk 函数: 假设它如同 printf 一样使用. 不同的是 printk 允许你根据消息的严重程度对其分类, 通过附加不同的记录级别或者优先级在消息上. 你常常用一个宏定义来指示记录级别. 例子, 一个调试消息, 一个紧急消息:   printk(KERN_DEBUG "Here I am: %s:%i\n",__FILE__, __LINE__); printk(KERN_CRIT "I'm trashed; givingup on %p\n", ptr); 有 8 种可能的记录字串,每个字串(在宏定义扩展里 )代表一个在角括号中的整数. 整数的范围从 0 到 7, 越小的数表示越大的优先级, 在头文件 <linux/kernel.h> 里定义,按照严重性递减的顺序列出它们: KERN_EMERG  <0> 用于紧急消息, 常常是那些崩溃前的消息. KERN_ALERT  <1> 需要立刻动作的情形. KERN_CRIT  <2> 严重情况, 常常与严重的硬件或者软件失效有关. KERN_ERR  <3> 用来报告错误情况; 设备驱动常常使用 KERN_ERR 来报告硬件故障. KERN_WARNING  <4> 有问题的情况的警告, 这些情况自己不会引起系统的严重问题. KERN_NOTICE  <5> 正常情况, 但是仍然值得注意. 在这个级别一些安全相关的情况会报告. KERN_INFO  <6> 信息型消息. 在这个级别, 很多驱动在启动时打印它们发现的硬件的信息. KERN_DEBUG  <7> 用作调试消息. 一条没有指定优先级的printk 语句默认级别是DEFAULT_MESSAGE_LOGLEVEL, 这个宏在 kernel/printk.c 里被指定为一个整数. 在 2.6.10 内核中, DEFAULT_MESSAGE_LOGLEVEL 是 KERN_WARNING, 但以前的版本取过不同的值. 重定向控制台消息: Linux 在控制台记录策略上允许一些灵活性, 它允许你发送消息到一个指定的虚拟控制台(如果你的控制台使用的是文本屏幕). 为了选择一个不同地虚拟终端来接收消息, 你可对任何控制台设备调用 ioctl(TIOCLINUX). 下面的程序, setconsole, 可以用来选择哪个控制台接收内核消息; 它必须由超级用户运行, 可以从 misc-progs 目录得到. 下面是全部程序. 应当使用一个参数来指定用以接收消息的控制台的编号. int main(int argc, char **argv) {    char bytes[2] = {11,0}; /* 11 是 TIOCLINUX 的命令编号*/ if (argc==2) bytes[1] = atoi(argv[1]); /* 运行参数1是选定的控制台*/ else { fprintf(stderr, "%s: need a singlearg\n",argv[0]); exit(1); } if(ioctl(STDIN_FILENO, TIOCLINUX, bytes)<0) { /* 使用stdin */              fprintf(stderr,"%s: ioctl(stdin,TIOCLINUX): %s\n", argv[0], strerror(errno));           exit(1);     }    exit(0); } setconsole 使用特殊的 ioctl 命令 TIOCLINUX,来实现特定于 linux 的功能. 为使用 TIOCLINUX, 你传递它一个指向字节数组的指针作为参数. 数组的第一个字节是指定请求的子命令编号, 随后的字节所具有的功能则由这个子命令来决定. 在setconsole里, 使用子命令 11, 下一个字节(存于 bytes[1])指定虚拟控制台. TIOCLINUX 的完整描述在内核源码的 drivers/char/tty_io.c 里. 速率限制: 内核已经提供了一个函数帮助解决打印信息太多而溢出的情况:   int printk_ratelimit(void);  在打印一条可能被重复的信息之前,应调用上面这个函数。如果该函数返回一个非零值,则可以继续打印消息,否则就会跳过。这样, 典型的调用如这样:   if (printk_ratelimit())     printk(KERN_NOTICE"The printer is still on fire\n"); printk_ratelimit通过跟踪多少消息发向控制台而工作. 当输出级别超过一个限度, printk_ratelimit 开始返回 0 并使消息被扔掉. printk_ratelimit 的行为可以通过修改下面的值来定制. /proc/sys/kern/printk_ratelimit( 在重新使能消息前等待的秒数) /proc/sys/kernel/printk_ratelimit_burst(限速前可接收的消息数) 打印设备编号: 有时, 当从一个驱动打印消息, 你会想同时打印出硬件设备编号来区分打印信息,打印主次编号不是特别难, 但是, 为一致性考虑, 内核提供了一些实用的宏定义( 在 <linux/kdev_t.h> 中定义)用于这个目的:   int print_dev_t(char *buffer, dev_t dev);  char *format_dev_t(char *buffer, dev_t dev); 两个宏定义都将设备号编码进给定的缓冲区; 唯一的区别是 print_dev_t返回打印的字符数, 而 format_dev_t 返回缓存区; 因此, 它可以直接用作 printk 调用的参数, 但是必须记住 printk 只有提供一个结尾的newline(新行‘\n’)才会刷新控制台显示。   用查询来调试: 有几个技术给驱动开发者来查询系统及调试: 创建一个文件在 /proc 文件系统下、使用ioctl 驱动方法、借助 sysfs 输出属性. 使用 sysfs 需要不少关于驱动模型的背景知识. /proc 文件系统: /proc 文件系统是一个伪文件系统,它是一种内核和内核模块用来向进程发送信息的机制。这个伪文件系统让用户可以和内核内部数据结构进行交互,获取有关进程的有用信息,在运行时通过改变内核参数改变设置。与其他文件系统不同,/proc存在于内存之中而不是硬盘上。读者可以通过“ls”查看/proc文件系统的内容。 (1)在 /proc 里实现文件 所有使用 /proc 的模块应当包含<linux/proc_fs.h> 来定义正确的函数. 要创建一个只读 /proc 文件, 你的驱动必须实现一个函数来在文件被读时产生数据. 当某个进程读文件时(使用 read 系统调用), 这个请求通过这个函数到达你的模块. 当一个进程读你的 /proc 文件, 内核分配了一页内存(PAGE_SIZE字节的内存块), 驱动可以写入数据来返回给用户空间. 那个缓存区传递给你的函数, 是一个称为 read_proc 的方法:     int (*read_proc)(char*page, char **start, off_t offset, int count, int *eof, void *data); //page 指针是你写你的数据的缓存区; //start 是这个函数用来说有关的数据写在页中哪里(下面更多关于这个); /offset 和 count 对于 read 方法有同样的含义. //eof 参数指向一个整数, 必须由驱动设置来指示它不再有数据返回, //data 是驱动特定的数据指针, 你可以用做内部用途.          //返回 实际存放到 page 缓存区的数据的字节数 (2)创建你的 /proc 文件 一旦你有一个定义好的 read_proc 函数, 你应当连接它到 /proc层次中的一个入口项. 使用一个 creat_proc_read_entry 调用: struct proc_dir_entry *create_proc_read_entry(const char*name,mode_t mode, struct proc_dir_entry *base, read_proc_t *read_proc, void*data);    //name 是要创建的文件名子 //mod 是文件的保护掩码(缺省系统范围时可以作为 0 传递), //base 指出要创建的文件的目录( 如果 base 是 NULL, 文件在 /proc 根下创建 ), //read_proc 是实现文件的 read_proc 函数, //data 被内核忽略( 但是传递给 read_proc). 下面就是 scull 调用该函数创建 /proc 文件的代码: create_proc_read_entry("scullmem", 0 /* default mode */,                        NULL /*parent dir */, scull_read_procmem,                        NULL /*client data */); 上面的代码在 /proc 下创建了吗名为scullmem 的文件, 具有全局可读的权限. (3)卸载时去除/proc remove_proc_entry是恢复 create_proc_read_entry 所做的事情的函数:   remove_proc_entry("scullmem", NULL /* parent dir */);  如果删除入口项失败,将导致未预期的调用,若模块已被卸载,内核会崩溃。 seq_file 接口: …………………………… 由于调试接口对于驱动编写实践时才需要用到,这里就只看下常用/PROC,先跳过本章其他内容。    

上一篇:PHP Session操作类
下一篇:STL中map用法详解

相关文章

相关评论