嵌入式驱动学习第六周——内核函数调用(堆栈打印)

本文详细介绍了Linux内核中的dump_stack、WARN_ON、BUG_ON和panic函数在跟踪和调试函数调用堆栈中的作用,通过示例展示了如何在内核模块中使用这些工具进行堆栈打印和异常处理。
摘要由CSDN通过智能技术生成

前言

   在内核中,函数调用堆栈非常重要,因为它可以帮助开发人员理解代码是如何执行的,从而进行调试、性能优化或问题排查。堆栈可以显示当前执行的函数以及导致该函数调用的先前函数,从而形成一个函数调用链。本篇博客就介绍堆栈打印内核函数的调用。

   嵌入式驱动学习专栏将详细记录博主学习驱动的详细过程,未来预计四个月将高强度更新本专栏,喜欢的可以关注本博主并订阅本专栏,一起讨论一起学习。现在关注就是老粉啦!

dump_stack函数

   该函数作用是打印内核调用堆栈,并打印函数的调用关系。

   下面给出一段实验代码,在该内核模块中,我们定义四个函数aaabbbcccddd,然后bbb中调用aaaccc中调用bbbddd函数谁都不调用。在入口函数中,我们调用cccddd函数。

   在aaa函数中使用dump_stack函数,查看aaa函数的调用栈

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/delay.h>

void aaa(void) {
    printk(KERN_EMERG "aaa\n");
    dump_stack();
    msleep(100);
}

void bbb(void) {
    printk(KERN_EMERG "bbb\n");
    aaa();
    msleep(100);
}

void ccc(void) {
    printk(KERN_EMERG "ccc\n");
    bbb();
    msleep(100);
}

void ddd(void) {
    printk(KERN_EMERG "ddd\n");
    msleep(100);
}

static int __init chrdevTest_init(void) {
    printk(KERN_EMERG "INIT func\r\n");
    ccc();
    ddd();

    return 0;
}

static void __exit chrdevTest_exit(void) {
    printk(KERN_EMERG "EXIT func\r\n");
}

module_init(chrdevTest_init);
module_exit(chrdevTest_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("wp");

   加载驱动后,显示信息如下所示,可以看到先打印INIT func,然后按照调用关系分别打印ccc,bbb,aaa。

   之后就是aaa的调用栈,框中就是调用关系,因为是chrdevTest_init调用cccccc调用bbbbbb调用aaa,由于Cortex-A处理器的堆栈是向下生长的,因此先压入chrdevTest_init函数(地址较大),在压入ccc函数(地址递减),以此类推到aaa函数。

   最后打印了一下谁都不调用的ddd函数。
在这里插入图片描述

   dump_stack()函数有助于我们调试,查看目标函数的调用关系

   如果信息消失了,可以使用dmesg指令重新再控制台打印出来

WARN_ON函数

   WARN_ON(condition)函数的作用是,在括号中的条件成立时,内核会抛出栈回溯,打印函数的调用关系,通常用于内核抛出一个警告,暗示某种不太合理的事情发生了。

   WARN_ON实际上也是调用了dump_stack,只是多了一个条件判断是否成立。

   在刚刚dump_stack函数的实验代码基础上,我们将aaa函数中dump_stack函数调用的位置改为WARN_ON(1)

void aaa(void) {
    printk(KERN_EMERG "aaa\n");
    WARN_ON(1);						// 条件为真,打印调用信息
    msleep(100);
}

   加载模块后,基本和上一个dump_stack的结果一样,但是可以看到,dump_stack函数也被压进调用栈了,因此可以确定WARN_ON是调用的dump_stack函数。
在这里插入图片描述

   现在我们将WARN_ON中的条件改为false,再看看结果:

void aaa(void) {
    printk(KERN_EMERG "aaa\n");
    WARN_ON(0);						// 条件为假,不打印调用信息
    msleep(100);
}

   可以看到控制台并没有输出调用栈。

在这里插入图片描述

BUG_ON函数

   内核中也有许多地方用到了BUG_ON函数,这个函数就像一个内核运行时的断言,意味着本来不该执行到BUG_ON这句,一旦执行就会抛出oops,导致栈的回溯和错误信息的打印,大部分体系结构把BUG()BUG_ON()定义成某种非法操作,这样自然会产生需要的oops

   在上面代码的基础上,将aaa函数中改为BUG_ON函数

void aaa(void) {
    printk(KERN_EMERG "aaa\n");
    BUG_ON(1);
    msleep(100);
}

   加载模块后,打印出来的信息如下所示,可以看到其中抛出了oops,并且最后并没有打印ddd函数的信息。然后函数调用关系也打印出来了,寄存器值也都打印出来了。

在这里插入图片描述

   我们将BUG_ON中的条件改为false

void aaa(void) {
    printk(KERN_EMERG "aaa\n");
    BUG_ON(0);
    msleep(100);
}

   加载驱动后如下所示,可以看到不打印任何堆栈信息,并且ddd函数可以顺利执行。

在这里插入图片描述

panic函数

   可以用panic()引发更严重的错误。调用panic()不但会打印错误消息(Oops)而且还会挂起整个系统。显然,你只应该在极端恶劣的情况下使用它。

   将同样的位置换为panic()函数

void aaa(void) {
    printk(KERN_EMERG "aaa\n");
    panic("###########################################wpwpwpwp");
    msleep(100);
}

   可以看到,打印完aaa函数后,控制台打印出panic中的字符串,然后整个进程进入到了阻塞状态。

在这里插入图片描述

参考资料

[1] Linux打印内核函数调用栈(dump_stack)

[2] Linux内核之BUG_ON()和WARN_ON()

[3] linux 内核态调试函数BUG_ON()

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值