zoukankan      html  css  js  c++  java
  • 驱动程序调试方法之printk——printk的原理与直接使用

    1、基本原理
    (1)在UBOOT里设置console=ttySAC0或者console=tty1
    这里是设置控制终端,tySAC0 表示串口, tty1 表示lcd
    (2)内核用printk打印
    内核就会根据命令行参数来找到对应的硬件操作函数,并将信息通过对应的硬件终端打印出来!
     
    2、printk的使用
    (1)printk函数的信息如何才能在终端显示出来
    在内核代码include/linux/kernel.h中,定义了控制台的级别:
    extern int console_printk[];
    #define console_loglevel (console_printk[0])
    #define default_message_loglevel (console_printk[1])
    #define minimum_console_loglevel (console_printk[2])
    #define default_console_loglevel (console_printk[3])
     
    我们在到kernel/printk.c里找到console_printk的定义:
     
    /* printk's without a loglevel use this.. */
    #define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */
     
    /* We show everything that is MORE important than this.. */
    #define MINIMUM_CONSOLE_LOGLEVEL 1 /* Minimum loglevel we let people use */
    #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */
     
    DECLARE_WAIT_QUEUE_HEAD(log_wait);
     
    int console_printk[4] = {
            DEFAULT_CONSOLE_LOGLEVEL,       /* console_loglevel */
            DEFAULT_MESSAGE_LOGLEVEL,       /* default_message_loglevel */
            MINIMUM_CONSOLE_LOGLEVEL,       /* minimum_console_loglevel */
            DEFAULT_CONSOLE_LOGLEVEL,       /* default_console_loglevel */
    };
     
    于是我们知道控制台的级别是:
    7   4     1     7
    我们当然可以再这里修改,但是还有一个更简单的修改方法,即在用户空间使用下面的命令:
     
    echo “1 4 1 7” > /proc/sys/kernel/printk
     
    将1 4 1 7写入 /proc/sys/kernel/printk即可!
     
    当我们使用printk函数时往往要加上信息级别,比如:
     
    printk(KERN_WARNING"there is a warning here! ")
     
    其中KERN_WARNING就表示信息的级别,相关宏在函数include/linux/kernel.h中:
     
    #define KERN_EMERG      "<0>"   /* system is unusable                   */
    #define KERN_ALERT      "<1>"   /* action must be taken immediately     */
    #define KERN_CRIT       "<2>"   /* critical conditions                  */
    #define KERN_ERR        "<3>"   /* error conditions                     */
    #define KERN_WARNING    "<4>"   /* warning conditions                   */
    #define KERN_NOTICE     "<5>"   /* normal but significant condition     */
    #define KERN_INFO       "<6>"   /* informational                        */
    #define KERN_DEBUG      "<7>"   /* debug-level messages                 */
     
    如果没有指明信息级别的话,就会采用默认的信息级别,这个默认的信息级别我们在上面见到过的,就是:
    #define default_message_loglevel (console_printk[1])
    没有改动的情况下是4
     
    上面我们说到了信息级别和控制台级别,下面我们要说到重点了!当信息级别的数值小于控制台的级别时,printk要打印的信息才会在终端打印出来,否则不会显示在终端!
     
    (2)串口控制台
    printk
            vprintk(fmt, args);
                     vscnprintf(printk_buf, sizeof(printk_buf), fmt, args);
                              vsnprintf(buf,size,fmt,args);//先把输出信息输入到临时buffer
                              //把临时buffer里面的数据稍作处理,写入log_buffer
                              //可以将信息级别与信息合并
                              //在用户空间使用命令dmesg可以把log_buffer里面的数据打印出来
                              release_console_sem();
                                      call_console_drivers(_con_start, _log_end);
                                              _call_console_drivers(start_print, cur_index, msg_level);
                                                       __call_console_drivers(start, end);
                                                                con->write(con, &LOG_BUF(start), end - start);//调用具体的输出函数
     
    这个输出函数要把数据从串口输出的话,肯定要调用到串口硬件相关的函数
    我们到文件:drivers/serial/s3c2410.c里面去这里有个串口初始化函数:
     
    s3c24xx_serial_initconsole
           register_console(&s3c24xx_serial_console);
     
    我们来看看它的注册函数:
    static struct console s3c24xx_serial_console =
    {
    .name = S3C24XX_SERIAL_NAME,
    .device = uart_console_device,
    .flags = CON_PRINTBUFFER,
    .index = -1,
    .write = s3c24xx_serial_console_write,
    .setup = s3c24xx_serial_console_setup
    };
    里面的确有个write函数!
     
    但是我们还不知道printk选择的控制台为什么是串口呢?
     
    我们知道uboot传入了参数:console=ttySAC0 或者 console=tty1
     
    内核就通过如下的函数来处理传入的参数:
    __setup("console=", console_setup);
    这是个宏 ,它的作用就是用函数console_setup来处理我们传入的参数
    console_ setup
             //先解码字符串为:name, idx, options,然后就使用这些:name, idx, options
            add_preferred_console(name, idx, options);
                      strcmp(console_cmdline[i].name, name)
                      console_cmdline[i].index == idx   //将索引和名字都记录在了console_cmdline数组中了
     
    我们记住这个数组,回过头来再来看:
     register_console(&s3c24xx_serial_console);
             if (strcmp(console_cmdline[i].name, console->name) != 0)
                     continue;
    我们看到在注册串口控制台的时候,会将串口控制台的名字与uboot传进来的参数相比较,一旦匹配才会注册。这样的话,只有与uboot传进来的控制台参数相一致的控制台才能注册成功。那么也就是说,printk会通过uboot设置的控制台的write函数,将信息打印出来!
     
    另外还有一点需要牢记的就是,printk输出的信息会先保存在缓冲区log_buf中,所以我们当然可以通过查看log_buf来看输出信息了!而这个查看命令就是:dmesg
    实际上,dmesg这个命令的作用就是去读/proc/kmsg这个文件。也就是说log_buf里面的内容是存放在/proc/kmsg这个文件里面的!


    (3)使用printk
    方法一:
    我们可以再内核中使用如下打印语句:
    #define DEG_PRINTK printk
    //#define DEG_PRINTK(x...)
     
    DEG_PRINTK("%s %s %d ",_FILE_,_FUNCTION_,_LINE_);
    这行打印语句的意思就是讲本行代码所在的文件的名(包括路径)、所在的函数、所在的行打印出来!
    当我们需要调试的时候,就使用#define DEG_PRINTK printk这个宏,当不需要调试的时候,就使用#define DEG_PRINTK(x...)这个宏。其中#define DEG_PRINTK(x...)里面的"..."的意思是DEG_PRINTK的参数是可变的!
    当代码比较少的时候,我们可以在每一行都加上这个打印语句,这样很容易就会发现错误的位置!
    当代码比较多的时候,我们可以采用对半查找的方法!先在代码中间加上打印语句!然后判断出错位置在打印语句之前还是之后,如果出现在之前,就在之前的代码代码里面再次采用对半查找!
     
    方法二:
    上面我们说到过,要打印的信息会存放在log_buf中,通过文件/proc/kmsg可以来访问这个buf,然后将信息打印出来。由此我们就想了,我们是否可以构造这样一个mylog_buf,里面存放我们所需要的打印信息,通过一个/proc/kmsg文件可以访问该buf,然后打印出来?答案是肯定的!下面我们就一步步来完成!
  • 相关阅读:
    系统架构技能之设计模式组合模式
    系统架构师基础到企业应用架构单机软件架构
    设计模式系列装饰模式
    设计模式系列命令模式
    设计模式系列外观模式
    设计模式系列原型模式
    设计模式系列代理模式
    设计模式系列桥接模式
    设计模式系列适配器模式
    设计模式系列享元模式
  • 原文地址:https://www.cnblogs.com/lidabo/p/5414007.html
Copyright © 2011-2022 走看看