驱动程序调试方法之printk――printk的原理与直接使用
睿丰德科技 专注RFID识别技术和条码识别技术与管理软件的集成项目。质量追溯系统、MES系统、金蝶与条码系统对接、用友与条码系统对接
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!\n") 其中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这个文件里面的!
(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!\n") 其中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这个文件里面的!