分类: 嵌入式
2010-08-10 22:32:13
四、打开和关闭消息
在驱动开发的早期, printk 非常有助于调试和测试新代码. 当你正式发行驱动时, 换句话说, 你应当去掉, 或者至少关闭, 这些打印语句.
通过在编译前改变 CFLAGS 变量的值,所有消息可以马上关闭,同一个 print 语句可以在内核代码和用户级代码中使用, 因此对于格外的消息, 驱动和测试程序能以同样的方式被管理.
下面的代码片断实现了这些特性, 直接来自头文件 scull.h:
下面是我的实验:
test.c #include #include #ifdef MY_DEBUG /* This one for user space */ # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) #else # define PDEBUG(fmt, args...) /* not debugging: nothing */ #endif int main() { PDEBUG("%s,test the printf switch\n","hello"); exit(0); } 在Makefile中未定义MY_DEBUG宏,即关闭调试信息。 Makefile: OBJS = test CROSS_COMPILE = CC = $(CROSS_COMPILE)gcc .PHONY:all all: $(CC) -o $(OBJS) $(OBJS).c #$(CC) -o $(OBJS) $(OBJS).c -O -DSCULL_DEBUG .PHONY:clean clean: rm -rf *~ *.o $(OBJS) 这时将看不到打印信息 root@ubuntu:/samba_share/test# ./test root@ubuntu:/samba_share/test# 在Makefile中定义MY_DEBUG宏时,将打开调试信息。 Makefile: OBJS = test CROSS_COMPILE = CC = $(CROSS_COMPILE)gcc .PHONY:all all: #$(CC) -o $(OBJS) $(OBJS).c $(CC) -o $(OBJS) $(OBJS).c -O -DMY_DEBUG .PHONY:clean clean: rm -rf *~ *.o $(OBJS) 运行test后看到打印信息 root@ubuntu:/samba_share/test# ./test hello,test the printf switch |
除了在Makefile中编译时定义一些宏外,还可以在源码中定义一些宏开关,来开启或关闭监视信息。
五、速率限制
有时为避免终端打印大量的信息,系统运行变慢,其它程序无法得到及时响应,往往找不出什么原因,一屏蔽掉大量的打印信息后,其它部分工作正常了。这在我的工作中遇到一次,CPU与外设通过SPI通信,SPI速率为1Mbps多,大量的串口终端打印信息导致CPU与外设通信不正常,关闭打印信息后一切正常。
Linux内核提供了一个防止重复打印相同信息的函数:
int printk_ratelimit(void);
典型的调用如下所示:
if(printk_ratelimit())
printk(“KERN_NOTICE “The printer is still on fire\n”);
printk_ratelimit 的行为可以通过修改 /proc/sys/kern/printk_ratelimit( 在重新打开消息前等待的秒数 ) 和 /proc/sys/kernel/printk_ratelimit_burst(限速前可接收的消息数)来定制.
六、打印设备编号
内核提供了一些实用的宏定义( 在
int print_dev_t(char *buffer, dev_t dev);
char *format_dev_t(char *buffer, dev_t dev);
两个宏定义都将设备号编码进给定的缓冲区,唯一的区别是 print_dev_t 返回打印的字符数, 而 format_dev_t 返回缓存区。
七、通过查询调试
Syslogd将每件事情都记录到磁盘上,以便系统崩溃后还能查看崩溃前的状况。但它也会降低系统的性能。一种提高系统性能的方法就是不适用log,而是在需要查看系统信息时用工具来查询,如ps、cat、netstat等。
八、使用/proc文件系统
/proc文件系统是一个特殊的软件创建的文件系统, 内核用来输出消息到外界。大部分时间, /proc 条目是只读的文件。
要创建一个只读 /proc 文件, 你的驱动必须实现一个函数来在文件被读时产生数据. 当某个进程读文件时(使用 read 系统调用), 这个请求通过这个函数到达你的模块。当一个进程读你的 /proc 文件, 内核分配了一页内存(就是说, PAGE_SIZE 字节,一个PAGE_SIZE是4KB), 驱动可以写入数据来返回给用户空间. 那个缓存区传递给你的函数, 是一个称为 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 是驱动特定的数据指针, 你可以用做内部用途.
创建proc文件时通过如下函数:
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/scullmem的函数:
create_proc_read_entry("scullmem", 0 /* default mode */,
NULL /* parent dir */, scull_read_procmem,
NULL /* client data */);
这里,创建了一个名为 scullmem 的文件, 直接在 /proc 下, 带有缺省的, 全局可读的保护。
proc文件的删除时通过如下函数:
remove_proc_entry("scullmem", NULL /* parent dir */);
由以上可以看到使用create_proc_read_entry等函数创建和删除proc文件很简单,但如果是所要显示到proc文件中的内容很少的话,那将会浪费很多内存。
九、通过监视调试
有时小问题可以通过观察用户空间的应用程序的行为来追踪,你可以运行一个调试器来单步过它的函数, 增加打印语句, 或者在 strace 下运行程序。strace 命令时一个有力工具, 显示所有的用户空间程序发出的系统调用. 它不仅显示调用, 还以符号形式显示调用的参数和返回值。当一个系统调用失败, 错误的符号值(例如, ENOMEM)和对应的字串(Out of memory) 都显示。strace 从内核自身获取信息. 这意味着可以跟踪一个程序, 不管它是否带有调试支持编译(对 gcc 是 -g 选项)以及不管它是否 strip 过. 你也可以连接追踪到一个运行中的进程, 类似于一个调试器的方式连接到一个运行中的进程并控制它。strace的使用我会作为一个单独的一部分来学习并在以后发表日记。
十、调试系统故障
当内核代码出错, 一个提示性的消息打印在控制台上。处理器使用的任何地址几乎都是一个虚拟地址, 通过一个复杂的页表结构映射为物理地址(例外是内存管理子系统自己使用的物理地址). 当引用一个无效的指针, 分页机制无法映射指针到一个物理地址, 处理器发出一个页错误给操作系统. 如果地址无效, 内核无法"换入"缺失的页面;这时,如果处理器恰好处于超级用户模式,系统就会产生一个oops。
一个oops消息已经能提示我们出现故障的位置,最相关的信息时指令指针(EIP),即出错指针的地址。另外,oops信息中的调用栈可以显示系统是如何到达故障点的,这个没有分析过。
十一 调试器和相关工具
kgdb可以用于调试内核,高版本的内核中已经支持kgdb了,这部分以后再单独学习。