Chinaunix首页 | 论坛 | 博客
  • 博客访问: 80744
  • 博文数量: 11
  • 博客积分: 1514
  • 博客等级: 上尉
  • 技术积分: 150
  • 用 户 组: 普通用户
  • 注册时间: 2009-09-19 19:10
文章分类
文章存档

2010年(11)

我的朋友

分类: 嵌入式

2010-06-30 23:49:56

   内核是一个不与特定进程相关的功能集合,其内核代码无法轻易在调试器进行,也很难跟踪。
   本文简单介绍一下三种在内核环境下监视内核代码并跟踪错误的技术:打印调试、查询调试及监视调试。

  首先要做一个准备工作,那就是重新配置自己之前构造的内核使之具有调试功能,因为内核开发者禁止了多项用于调试的功能,那样子可避免这些功能造成额外的输出,导致性能下降。在这里,借鉴了博客Tekkaman  Ninja对内核增加如下选项:

Kernel hacking  --->    
        [*] Magic SysRq key
        [*] Kernel debugging
        [*]  Debug slab memory allocations 
        [*]  Spinlock and rw-lock debugging: basic checks
        [*]  Spinlock debugging: sleep-inside-spinlock checking
        [*]  Compile the kernel with debug info 
        [*] Magic SysRq key
Device Drivers  ---> 
        Generic Driver Options  --->
          [*] Driver Core verbose debug messages
General setup  --->
       [*] Configure standard kernel features (for small systems)  --->
          [*] Load all symbols for debugging/ksymoops
书上介绍的还有其他配置,有的我不需要,或是s3c2440不支持,菜单里看不见。

一、打印调试

    在应用程序中,调用printf显示监视信息。调试内核代码,用printk来完成相同的工作。printk与printf的差别就是,printk通过附加不同的日志级别(loglevel),或者消息优先级所表示的严重程度对消息进行分类。在头文件中定义了8种可用的日志级别字符串,根据严重程度排序如下:

KERN_EMERG              KERN_ALERT            KERN_CRIT           KERN_ERR
KERN_WARNING         KERN_NOTICE           KERN_INFO           KERN_DEBUG
   
    未指定优先级别的printk语句采用默认级别
DEFAULT_MESSAGE_LOGLEVEL,也就是在kernel/printk.c中被指定的宏,这个宏通常就是KERN_WARNING
    根据日志级别,内核可能会把消息打印到当前控制台上。控制台也有级别,用整数变量console_loglevel来表示。当日志级别的值小于console_loglevel时,消息才能显示出来。 同样,console_loglevel也有一个默认级别DEFAULT_CONSOLE_LOGLEVEL
    修改console_loglevel的方法有以下几种:
    1、通过sys_syslog系统调用
    a) 先杀掉klogd进程,然后再用-c选项重启它的同时修改console_loglevel的值;
    b) 也可通过编程对其进行修改,程序中进行了sys_syslog系统调用(见附件setlevel.c)。我们通过实验观查一下,编译setlevel.c然后通过nfs把得到可执行文件setlevel挂载到开发板mnt目录下:
[root@FriendlyARM /mnt]# ./setlevel 1
[root@FriendlyARM /mnt]# cd /lib
[root@FriendlyARM /lib]# insmod hello.ko        ;看不到打印消息
[root@FriendlyARM /lib]# rmmod hello
[root@FriendlyARM /lib]# cd /mnt
[root@FriendlyARM /mnt]# ./setlevel 8
[root@FriendlyARM /lib]# insmod hello.ko        ;消息显示出来了
Hello,world
[root@FriendlyARM /lib]# rmmod hello
Goodbye,world
    实验证明,本程序是OK的。可通过setlevel把控制台级别指定为1~8的整数值。其值设为1,则只有级别为0(KERN_EMERG)的消息才能显示;其值设为8,则包括调试信息在内的所有消息都能显示。
    2、 通过对文本/proc/sys/kernel/printk的访问来改变console_loglevel的值。
[root@FriendlyARM /lib]# echo 1 > /proc/sys/kernel/printk
[root@FriendlyARM /lib]# insmod hello.ko       ;看不到打印消息
[root@FriendlyARM /lib]# rmmod hello
[root@FriendlyARM /lib]# echo 8 > /proc/sys/kernel/printk
[root@FriendlyARM /lib]# insmod hello.ko       ;消息显示出来了
Hello,world
[root@FriendlyARM /lib]# rmmod hello
Goodbye,world

二、查询调试

    驱动程序开发人员可以用如下方法对系统进行查询:1、在/proc文件系统创建文件;2、使用驱动程序的ioctl方法;3、通过sysfs导出属性。在这里只介绍第1种方式。
    /proc文件系统是一种特殊的、由软件创建的文件系统,内核使用他向外界导出信息。/proc下面的每个文件都绑定于一个内核函数,用户读取其中的文件时,该函数动态的生成文件的内容
    因为/proc文件系统是动态的,所以驱动程序模块可以在任何时候添加或删除其中的入口项。
    1、在/proc中实现文件
    所有使用/proc的模块必须包含
    为创建一个只读的/proc文件,驱动程序程序必须实现一个函数,用于在读取文件时生成数据。当某个进程读取这个文件时,读取请求会通过这个函数发送到驱动程序模块。该函数称为read_proc方法:
int (*read_proc)(char *page,char **start,off_t offset,
                 int count, int *eof,    void *data);
    一旦定义好一个read函数,就需要把它与一个/proc入口项连接起来,这通过调用create_proc_read_entry实现:
struct proc_dir_entry *create_proc_entry(const char *name,
                mode_t mode,            struct proc_dir_entry *base,
                read_proc_t *read_proc, void *data);
     其中,name要是创建的文件名,mode是该文件的保护掩码;base指定文件所在的目录;read_proc是实现该文件的read_proc函数;内核会忽略data参数,但是会将其传递给read_proc。
     当模块卸载时,/proc中的入口项也应该被删除。remove_proc_entry就是用来摊销create_proc_read_entry所做工作的函数。
     2、seq_file接口
     为了让内核开发工作更加容易,通过对/proc代码的整理增加了seq_file接口。
     为使用seq_file,必须创建一个简单的“迭代器”对象,该对象用来表示项目序列中的位置,每前进一步,该对象输出序列中的一个项目。
    使用该方式创建/proc文件,须包含头文件,然后建立四个迭代器对象,分别为start、next、stop、show。
void *start(struct seq_file *sfile,loff_t *pos);
void *next(struct seq_file *sfile,void *v,loff_t *pos);
void *stop(struct seq_file *sfile,void *v);
int  *show(struct seq_file *sfile,void *v);
    接下来将这些函数打包并和/proc中的某个文件连接起来。首先要填充seq_operations结构:
struct seq_operations {
        void *(*start) (struct seq_file *m, loff_t *pos);
        void (*stop) (struct seq_file *m, void *v);
        void * (*next) (struct seq_file *m, void *v, loff_t *pos);
        int (*show) (struct seq_file *m, void *v);
};
    有了这个结构,须创建一个内核能够理解的文件实现,即open,read,lseek,release等方法,然后将这些方法初始化file_operations结构。最后建立实际的/proc文件:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
                                        struct proc_dir_entry *parent);
    实验测试,/proc文件驱动程序见附件:

三、监视调试
    这里有一个功能非常强大的工具就是要strace命令,通过它来监视用户空间程序工作状况或者内核代码。它可以显示由用户空间程序所发出的所有系统调用。它不仅可以显示调用,而且还能显示调用参数以及用符号形式表示的返回值。
    strace有许多命令行选项,其中最为有用的是下面几个:-t,显示调用发生的时间;-T,显示调用所花费的时间;-e,限定被跟踪的调用类型;-o,将输出重定向到一个文件中。默认情况下,strace能跟踪信息打印到stderr上。

    驱动程序
附件:setlevel.c
阅读(1816) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~