内核编程调试技术(1)

本文介绍了Linux内核编程中的printk调试技术,包括不同日志级别的含义及其使用场景,如KERN_EMERG、KERN_ALERT等。printk函数会将消息写入循环缓冲区,klogd和syslogd负责将内核消息追加到日志文件。此外,还讨论了如何通过/proc/sys/kernel/printk控制日志级别,以及printk_ratelimit函数用于限制打印速率。最后提到了通过/proc文件系统、ioctl和sysfs导出驱动程序信息进行查询和调试的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

通过打印调试(printk)

printk和printf不差别之一就是它通过日志级别,可以让printk根据消息的严重程度对消息进行分类

printk(KERN_DEBUG "here i am: %s:%i\n", __FILE__, __LINE__);

printk(KERN_CRIT "I'M TRASHED; giving up on %p\n", ptr);

在头文件<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   */

KERN_EMERG     用于紧急事件消息,一般是系统崩溃之前的提示消息

KERN_ALERT       用于需要立即采取动作的情况

KERN_CRIT          临界状态,通常涉及严重的硬件或软件操作失败

KERN_ERR           用于报告错误状态,驱动程序常使用KERN_ERR来报告来自硬件的问题

KERN_WARNING 对可能出现问题的情况进行警告,但这类问题通常不会对系统造成严重的问题

KERN_NOTICE     有必要进行提示的正常情形。许多与安全相关的状况用这个级别进行汇报

KERN_INFO           提示性信息。很多驱动程序在启动的时候以这个级别来打印出它们找到的硬件信息

KERN_DEBUG      用于调试信息

未指定优先级的printk语句采用kernel/printk.c文件中DEFAULT_MESSAGE_LOGLEVEL所指定的一个整数

#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */

如果消息优先级小于console_loglevel这个整数变量的值,那么消息会打印到当前控制台上,这个控制台可以是一个字符模式的终端、一个串口打印机或是一个并口打印机,而且每次输出一行

如果系统运行klogd和syslogd,则无论console_loglevel为何值,内核消息都将追加到/var/log/messages中

如果klogd没有运行,消息就不会传递到用户空间,这种情况下,只能查看/proc/kmsg文件(使用dmesg可以轻松做到)

如果使用klogd,它只会保存连续相同的第一行,并在最后打印重复次数

console_loglevel的初始值是DEFAULT_MESSAGE_LOGLEVEL,要修改它(通过sys_syslog系统调用)必须先杀掉klogd

也可以通过/proc/sys/kernel/printk来读取和修改控制台的日志级别,如:

echo 8 > /proc/sys/kernel/printk  所有的内核消息都将显示到控制台上

消息如何被记录

printk函数将消息写到一个长度为__LOG_BUF_LEN字节的循环缓冲区中(在配置内核时指定4KB-1MB)。

klogd运行时会读取内核消息并将它们分发到syslogd,syslogd随后查看/etc/syslog.conf找出处理这些数据的方法。

如果没有运行klogd,数据将保留在循环缓冲区中,直到某个进程读取它们或缓冲区溢出为止。

开启及关闭消息

下面给出了一个调用printk的编码方法,它个宏可个别或全局的开关printk语句

  1.  可以通过在宏名字中增加或删减一个字母来禁用或启用一条打印语句
  2. 在编译前修改CFLAGS变量,则可以一次禁用所有消息
  3. 同样的打印语句在内核代码中也可以在用户级代码使用,因此,关于这些额外的调试信息,驱动程序和测试程序可以用同样的方法来进行管理
undef PDEBUG
ifdef SCULL_DEBUG
    ifdef __KERNEL__
        /*表明打开调试,并处于内核空间*/
        define PDEBUG(fmt, args...) printk(KERN_DEBUG "scull: " fmt, ## args)
    else
        /*表明处于用户空间*/
        define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args)
    endif
else
    define PDEBUG(fmt, args...) /*调试被关闭:不作任何事情*/
endif

undef PDEBUGG
define PDEBUGG(fmt, args...)/*不作任何事情,仅仅是个点位符*/

 

为了进一步简化这个过程,可以在makefile中添加下面几行:

#Comment/uncomment the following line to disable/enable debuging
DEBUG = y

#Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
    DEBFLAGS = -O -g -DSCULL_DEBUG # "-O" is needed to expand inlines
else
    DEBFLAGS = -O2
endif

CFLAGS += $(DEBFLAGS)

 速度限制

有时候会一不小心利用printk产生了上千条信息,从而让日志信息充满控制台,为避免这种情况内核提供了一个有用的函数:

int printk_ratelimit(void);

在打印一条可能被重复的信息之前,应调用上面这个函数。如果该函数返回一个非零值则可以继续打印消息,否则就应跳过:

if (printk_ratelimit())

    printf(KERN_NOTICE "The printer is still on fire\n");

我们可以通过修改/proc/sys/kernel/printk_ratelimit(在重新打开消息之前应该等待的秒数)以及/proc/sys/kernel/printk_ratelimit_burst(在进行速度限制之前可以接受的消息数)来定制printk_ratelimit的行为

打印设备编号

有时当从一个驱动程序打印一个消息时,我们希望打印与硬件相关联的设备编号,内核提供了一对辅助宏(在<linux/kdev_t.h>中定义)

int print_dev_t(char *buffer, dev_t dev);          //返回的是打印的字符数

char *format_dev_t(char *buffer, dev_t dev); //返回的是缓冲区

传入上述宏的缓冲区必须足够保存一个设备编号。以后64位的设备编号可能是主流,因此缓冲区至少应有20字节长。

通过查询调试

由于syslogd会一直保持对其输出文件的同步刷新,即使降低console_loglevel以避免装载控制台设备,但大量使用printk仍然会降低系统性能。从日志记录角度看这样的处理是正确的,它试图把每件事都记录到磁盘上,以在系统万一崩溃时最后的记录信息能反应崩溃前的状况。然而因处理调试信息而使系统性能减慢是我们所不希望的。这个问题可以通过在/etc/syslogd.conf中日志文件的名字前加一个减号前缀来解决,这样可以避免在每次出现新信息时都去刷新磁盘文件。

多数情况中,获取相关信息的最好方法是在需要的时候才去查询系统信息,而不是持续不断地产生数据。

驱动开发人员可以用如下方法对系统进行查询:在/proc文件系统中创建文件,使用驱动程序的ioctl方法,以及通过sysfs导出属性等。

使用/proc文件系统

/proc文件系统是一种特殊的,由软件创建的文件系统,它能动态地反应内核信息,内核使用它向外界导出信息。如ps、top和uptime等工具都是通过/proc来获取它们需要的信息

相比最初的用途(用于提供系统中进程的信息),/proc文件系统已经不受控地增加了大量信息,因此建议新的代码通过sysfs来向外界导出信息。

使用/proc文件系统必须包含<linux/proc_fs.h>

ioctl方法

ioctl是作用于文件描述符之上的一个系统调用,ioctl接收一个命令号以及另一个可选的参数,命令号用以标识将要执行的命令,而可选参数通常是个指针。

作为替代/proc文件系统的方法,我们可以专为调试设计若干ioctl命令。这些命令从驱动程序复制相关数据到用户空间,然后可在用户空间中检验这些数据。

  • 有时ioctl获取信息是最好的方法,因为它比读/proc要快得多,如果在数据显示前需要完成某些处理工作,那么以二进制获取数据要比文本文件有效得多
  • ioctl并不要求把数据分割成不超过一个内存页面的片断
  • 在调试被禁用后,用来取得信息的这些命令仍保留在驱动程序中,万一驱动程序有什么异常,这些命令仍然可以用来调试
  • /proc文件对任何查看这个目录的人都是可见的,很多人并不知道这些文件是用来做什么的,而未公开的ioctl命令通常不会被注意到。
  • 缺点是模块会变大

通过监视调试

如:strace ls /dev > /dev/scull0

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值