驱动程序调试方法之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!\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这个文件里面的!


(3)使用printk

方法一:

我们可以再内核中使用如下打印语句:

#define DEG_PRINTK printk

//#define DEG_PRINTK(x...)

 

DEG_PRINTK("%s %s %d\n",_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,然后打印出来?答案是肯定的!下面我们就一步步来完成!

时间: 2024-12-17 07:14:46

驱动程序调试方法之printk——printk的原理与直接使用的相关文章

驱动程序调试方法之printk——自制proc文件(二)

上一节的程序很振奋人心,我们自己实现了一个myprintk打印函数.但是这个函数存在一个致命的缺陷,那就是只能使用一次cat /proc/mymsg命令来读取mylog_buf的值.这是因为读到最后会出现:mylog_r == mylog_w,表示缓冲区为空,下一次就不能在读到数据了.在本节里面我们就着手来解决这个问题,我们要实现的就是每次使用 cat /proc/mymsg 时,都会从头打印.那么我们就需要将入口做一个拷贝,一个保存起来,一个进行变换.这样的话,当下一次读的时候,我们可以将保存

驱动程序调试方法之printk——自制proc文件(一)

首先我们需要弄清楚proc机制,来看看fs/proc/proc_misc.c这个文件,从入口函数开始看: proc_misc_init(void) #ifdef CONFIG_PRINTK { struct proc_dir_entry *entry; entry = create_proc_entry("kmsg", S_IRUSR, &proc_root);//这里创建了一个proc入口kmsg if (entry) entry->proc_fops = &p

Linux内核调试方法总结

一  调试前的准备 二  内核中的bug 三  内核调试配置选项 1  内核配置 2  调试原子操作 四  引发bug并打印信息 1  BUG()和BUG_ON() 2  dump_stack() 五  printk() 1  printk函数的健壮性 2  printk函数脆弱之处 3  LOG等级 4  记录缓冲区 5  syslogd/klogd 6  dmesg 7 注意 8 内核printk和日志系统的总体结构 9  动态调试 六  内存调试工具 1  MEMWATCH 2  YAMD

linux设备驱动第四篇:从如何定位oops的代码行谈驱动调试方法

上一篇我们大概聊了如何写一个简单的字符设备驱动,我们不是神,写代码肯定会出现问题,我们需要在编写代码的过程中不断调试.在普通的c应用程序中,我们经常使用printf来输出信息,或者使用gdb来调试程序,那么驱动程序如何调试呢?我们知道在调试程序时经常遇到的问题就是野指针或者数组越界带来的问题,在应用程序中运行这种程序就会报segmentation fault的错误,而由于驱动程序的特殊性,出现此类情况后往往会直接造成系统宕机,并会抛出oops信息.那么我们如何来分析oops信息呢,甚至根据oop

linux设备驱动第四篇:以oops信息定位代码行为例谈驱动调试方法

上一篇我们大概聊了如何写一个简单的字符设备驱动,我们不是神,写代码肯定会出现问题,我们需要在编写代码的过程中不断调试.在普通的c应用程序中,我们经常使用printf来输出信息,或者使用gdb来调试程序,那么驱动程序如何调试呢?我们知道在调试程序时经常遇到的问题就是野指针或者数组越界带来的问题,在应用程序中运行这种程序就会报segmentation fault的错误,而由于驱动程序的特殊性,出现此类情况后往往会直接造成系统宕机,并会抛出oops信息.那么我们如何来分析oops信息呢,甚至根据oop

linux kernel将关键信息保存到文件做法 很好的调试方法

linux kernel将关键信息保存到文件做法      很好的调试方法 下面有2个示例: 1:保存机器从开机到结束的VBATT: 2:保存uart接收到的数据到文件: 意义不多说了. 以下是代码: #include <linux/fs.h> #include <asm/uaccess.h> #include <asm/unaligned.h> static struct file *fp =NULL; int write_to_file (char *buf, in

内核调试神器SystemTap — 更多功能与原理(三)

a linux trace/probe tool. 官网:https://sourceware.org/systemtap/ 用户空间 SystemTap探测用户空间程序需要utrace的支持,3.5以上的内核版本默认支持. 对于3.5以下的内核版本,需要自己打相关补丁. 更多信息:http://sourceware.org/systemtap/wiki/utrace 需要: debugging information for the named program utrace support i

字符设备(三)及调试方法

llseek: -EINVAL非法(无效)参数 当前位置 file->f_pos SEEK_END       pcdevp->data_len 定义的位置要在你的位置范围内唔 怎么处理 :最后要做个保存  file->f_pos =... 怎么驱动LED灯:看原理图,如果想让LED灯亮就收早相应的端口上输出相应的电平如果是led灯别一端接地,那就使其这端高电平,如果接的是三极管则要考虑三极管的导通,反之则... 编程使CPU得相应管脚设为输出管脚 控制该管脚输出高电平或低电平 阅读CP

Linux内核调试方法总结之序言

本系列主要介绍Linux内核死机.异常重启类稳定性问题的调试方法. 在Linux系统中,一切皆为文件,而系统运行的载体,是一类特殊的文件,即进程.因此,我尝试从进程的角度分析Linux内核的死机.异常重启等问题.在内核空间,内核本身是一个特权级的进程,它包含一系列系统级线程,维护整个系统内核的运转.在用户空间,有很多用户进程实现不同的功能,它们有的是独立运行,有些相互之间有依赖(同步或者互斥).在32位系统中,内核进程独享3GB~4GB的高1GB内存空间,而每个用户进程则分别占据0GB~3GB的