3880|6

248

帖子

0

TA的资源

纯净的硅(初级)

楼主
 

《奔跑吧Linux内核2:调试与案例分析》3-内核调试方法和死锁的检测 [复制链接]

本期测评主要讲解一下内核常用的调试方法,因为对于内核和驱动开发的程序员,死锁检测和内存检测是不可避免的,所以本期测评将会重点讲讲本书提到的内核调试的方法。

 

1.什么是死锁

死锁(deadlock)是指两个或多个进程因争夺资源而造成的互相等待的现象,如进程A需要资源X,进程B需要资源Y,而双方都掌握对方所需要的资源,且都不释放,这就会导致死锁。在内核开发中,时常要考虑并发设计,即使采用正确的编程思路,也不可避免的会发生死锁。在Linux内核中,常见的死锁有如下两种:

  1. 递归死锁:如在中断等延迟操作中使用了锁,和外面的锁构成了递归死锁。
  2. AB-BA死锁;多个锁因处理不当而引发死锁,多个内核路径上的锁处理顺序不一致也会导致死锁。

 下面是一个死锁的示例程序,函数在已经获取锁的情况下再次尝试获取锁,造成了死锁。

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

static DEFINE_SPINLOCK(hack_spinA);
static struct page *page;
static struct task_struct *lock_thread;

static int nest_lock(void)
{
 int order = 5;

 spin_lock(&hack_spinA);
 page = alloc_pages(GFP_KERNEL, order);
 if (!page) {
    printk("cannot alloc pages\n");
    return -ENOMEM;
 }

 spin_lock(&hack_spinA);
 msleep(10);
 __free_pages(page, order);
 spin_unlock(&hack_spinA);
 spin_unlock(&hack_spinA);

 return 0;
}

static int lockdep_thread(void *nothing)
{
 set_freezable();
 set_user_nice(current, 0);

 while (!kthread_should_stop()) {
    msleep(10);
    nest_lock();
 }
}

static int __init my_init(void)
{

 lock_thread = kthread_run(lockdep_thread, NULL, "lockdep_test");
 if (IS_ERR(lock_thread)) {
    printk("create kthread fail\n");
    return PTR_ERR(lock_thread);
 }

 return 0;
}

static void __exit my_exit(void)
{
 kthread_stop(lock_thread);
}

MODULE_LICENSE("GPL");
module_init(my_init);
module_exit(my_exit);

2.内核调试方法

(1)printk

我以前在做简单的驱动开发的时候,最常用调试工具就是printk,因为我觉得这种方式最简单直接。本书也详细提到了这种调试方法。printk()函数和c语言体统的printf()函数类似,其中他们的一个最重要的区别就是printk()函数提供输出等级,内核可以根据这个等级来判断是否在终端或者串口中输出。下面是printk()函数的输出等级:

 

Linux内核为printk定义了8个输出等级,KERN EMERG等级最高,KERN DEBUG等级最低。在配置内核时,由一个宏来设置系统默认的输出等级CONFIG MESSAGE LOGLEVEL_Linux内核为printk定义了8个输出等级,KERN EMERG等级最高,KERN DEBUG等级最低。在配置内核时,由一个宏来设置系纱默认的输出等级CONFIG MESSAGE LOGLEVEL_DEFAULT,通常该值设置为4,因此只有输出等级高于4时才会输出到终端或者串口,即只有KERN_EMERG~KERN ERR满足这个条件。通常在产品开发阶段,会把系统默认等级设置为最低,以便在开发测试阶段可以暴更多的问题和调试信息,在发布产品时再把输出等级设置为0或者4。
(2)动态输出

动态输出(dynamic print)是内核子系统开发者最喜欢的输出技术之一。在运行系统时,动态输出可以由系统维护者动态选择打开哪些内核子系统的输出,可以有选择性地打开某些模块的输出,而printk是全局的,只能设置输出等级。要使用动态输出,必须在配置内核时打开CONFIG_DYNAMIC_DEBUG宏。内核代码里使用大量 pr_debug()/dev_dbg()函数来输出信息,这些就使用了动态输出技术。另外,还需要系统挂载debugfs 文件系统。动态输出在debugfs文件系统中有一个control文件节点,这个文件节点记录了系统中所有使用动态输出技术的文件名路径、输出所在的行号、模块名字和要输出的语句。可以使用下面的命令查看。

cat /sys/kernel/debug/dynamic_debug/control

下面几个例子讲解了如何使用动态输出技术:

 

 

(3)oops分析

在编写驱动或内核模块时,常常会显式或隐式地对指针进行非法取值或使用不正确的指针,导致内核发生一个oops错误。当处理器在内核空间中访问一个非法的指针时,因为虚拟地址到物理地址的映射关系没有建立,会触发一个缺页中断,在缺页中断中该地址是非法的,内核无法正确地为该地址建立映射关系,所以内核触会发一个 oops错误。

下面举一个例子来验证如何分析一个内核oops错误

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
static void create_oops(void)
{
* (int  * )0 = 0; //人为编造一个空指针访问
}
static int __init my_oops_init(void)
{
printk("oops module init\n");
create_oops();
return 0;
}
static void __exit my_oops_exit(void)
{
printk("goodbye\n");
}
module_init(my_oops_init);
module_exit(my_oops_exit);
MODULE_LICENSE("GPL");

将上面编译成内核模块并用insmod命令加载模块,将会出现以下错误

 

   

PC 指针指向出错的地址,另外“Call trace”也展示了出错时程序的调用关系。首先观察出错信息 create_oops+0x14/0x24,其中,0x14 表示指令指针在 create_oops()函数的第 0x14 字节处,create_oops()函数本身共 0x24 字节。继续分析这个问题,假设两种情况:一是有出错模块的源代码,二是没有源代码。在某些实际工作场景中,可能需要调试和分析没有源代码的 oops 错误。
先看有源代码的情况,通常在编译时添加到符号信息表中。在 Makefile 中添加如下语句,并重新编译内核模块。

KBUILD_CFLAGS +=-g

下面用两种方法来分析。首先,使用 objdump 工具反汇编。

aarch64-linux-gnu-objdump -Sd oops.o

 

通过反汇编工具 objdump 可以看到出错函数 create_oops()的汇编情况,第 0x10~0x14 字节的指令用于把 0 赋值给 x0 寄存器,然后往 x0 寄存器里写入 0。wzr 是一种特殊寄存器,值为 0,所以这里发生了写空指针错误。然后,使用 gdb 工具。为了快捷地定位到出错的具体位置,使用 gdb 中的“list”指令加上出错函数和偏移量即可。

aarch64-linux-gnu-gdb oops.o

 

下面来看没有源代码的情况。对于没有编译符号表的二进制文件,可以使用objdump 工具来转储汇编代码,例如使用“aarch64-linux-gnu-objdump -d oops.o”命令来转储 oops.o 文件。内核提供了一个非常好用的脚本,可以快速定位问题,该脚本位于 Linux 内核源代码目录的 scripts/decodecode 文件夹中。我们把出错日志保存到一个.txt 文件中。

 export ARCH=arm64
 export CROSS_COMPILE=aarch64-linux-gnu-
 ./scripts/decodecode < oops.txt

 

decodecode 脚本会把出错的 oops 日志信息转换成直观有用的汇编代码,并且告知具体是哪个汇编语句出错了,这对于分析没有源代码的 oops 错误非常有用。

 

 

总结:本期测评主要介绍了死锁出现的情况以及一些常见的内核调试方法,并就oops错误详细的介绍了oops分析内核错误的步骤。内核调试的方法如果只停留在阅读层面上是不能够完全掌握的,因此以后在写内核驱动的时候要多多实践这些方法,将他们真正转换为自己的技能。

最新回复

死锁特别恼火,我们之前调试一个应用层死锁花了小一个月。。。   详情 回复 发表于 2024-4-29 16:50
点赞 关注

回复
举报

248

帖子

0

TA的资源

纯净的硅(初级)

沙发
 

感谢支持

 
 

回复

2

帖子

0

TA的资源

一粒金砂(初级)

板凳
 

里面书比较多,要慢慢消化一下。有挺多都是比较难买到的了。还是比较实用的。 文件里面的书的命名有点乱。有


 
 
 

回复

1286

帖子

4

TA的资源

版主

4
 

高手~~~

分享一篇内核编译的文章?编译内核时各个选项的分析~~

点评

这个在我之前编译其他开发板的linux内核有一点介绍  详情 回复 发表于 2024-4-29 09:59
 
 
 

回复

248

帖子

0

TA的资源

纯净的硅(初级)

5
 
beyond_笑谈 发表于 2024-4-29 09:23 高手~~~ 分享一篇内核编译的文章?编译内核时各个选项的分析~~

这个在我之前编译其他开发板的linux内核有一点介绍


 
 
 

回复

1286

帖子

4

TA的资源

版主

6
 

好的,我一会搜一下看看能不能找到这方面的帖子,谢谢

 
 
 

回复

7670

帖子

2

TA的资源

五彩晶圆(高级)

7
 

死锁特别恼火,我们之前调试一个应用层死锁花了小一个月。。。

 
个人签名

默认摸鱼,再摸鱼。2022、9、28

 
 

回复
您需要登录后才可以回帖 登录 | 注册

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/7 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表