• 20135202闫佳歆--week5 课本18章学习笔记


    第十八章 调试

    内核级开发的调试工作远比用户级开发艰难的多。

    一、准备开始

    准备工作需要的是:

    • 一个bug
    • 一个藏匿bug的内核版本
    • 相关内核代码的知识和运气

    在这一章里,调试的主要思想是让bug重现,但是在内核中这并不是很容易做到的。因此,在跟踪bug的时候,掌握的信息越多越好。

    二、内核中的bug

    内核bug的原因可能有:

    - 错误代码
    - 同步时发生的错误,例如共享变量锁定不当
    - 错误的管理硬件
    - ……
    

    内核bug发作的症状可能有:

    - 降低所有程序的运行性能
    - 毁坏数据
    - 使得系统处于死锁状态
    - ……
    

    内核开发比起用户开发要多考虑一些独特的问题,比如:

    - 定时限制
    - 竞争条件
    - ……
    原因是允许多个线程在内核中同时运行。
    

    三、通过打印来调试

    这里说的打印是指的内核的格式化打印函数printk(),因为它有自己的一些特殊的功能:

    1.健壮性

    健壮性的意思是,在任何时候,任何地方都能调用它。

    - 在中断上下文和进程上下文中被调用
    - 在任何持有锁时被调用
    - 在多处理器上同时被调用,并且不必使用锁。
    

    弹性极佳
    唯一的例外是【在系统启动过程的初期就要输出】这种情况,需要用early_printk()代替,两者功能完全相同。

    2.日志等级

    printk()和printf()在使用上最主要的区别就是前者可以指定一个日志级别,内核根据这个级别来判断是否在终端上打印消息。内核把级别比某个特定值低的所有消息显示在终端上。
    enter description here
    如果没有特别特别指定,函数会选用默认的DEFAULT_MESSAGE_LOGLEVEL,在当前来看是KERN_WARNING,即一个警告。最好还是给自己的消息指定一个记录等级。
    内核会把这些记录等级转化为"",n指等级,从0-7,对应表中从上到下,数字越小越重要,也就是说:

    0   KERN_EMERG  最重要
    ……
    7   KERN_DEBUG  最不重要
    

    对于调试信息, 有两种赋予记录等级的方法:

    1. 保持终端的默认记录等级不变,给所有调试信息KERN_CRIT或更低的等级。
    2. 给所有调试信息KERN_DEBUG等级,调整终端的默认记录等级。

    3.记录缓冲区

    内核消息是保存在一个环形队列中,这个环形队列就是它的记录缓冲区。
    大小是可以在编译时进行调整的,但是在单处理器的系统上默认值是16kb。
    也就是说内核在同一时间只能保存16kb的内核消息,再多的话新消息就会覆盖老消息,读写都是按照环形队列方式操作的。

    优点:

    1. 健壮性:在中断上下文中也可以方便的使用。
    2. 简单性:使记录维护起来更容易。

    缺点:
    可能会丢失消息。

    4.syslogd和klogd

    这是两个用户空间的守护进程,klogd从记录缓冲区中获取内核消息,再通过syslogd守护进程将他们保存在系统日志文件中。

    (1)klogd
    既可以从/proc/kmsg文件中,也可以通过syslog()系统调用读取这些消息。
    默认是/proc方式。
    两种情况klogd都会阻塞,知道有新的内核消息可供读出,唤醒之后默认处理是将消息传给syslogd。
    可以通过-c标志来改变终端的记录等级

    (2)syslogd
    将它接收到的所有消息添加到一个文件中,默认是/var/log/messages。

    四、oops

    oops是内核告知用户有不幸发生的最常用的方式
    内核很难自我修复,也不能将自己杀死,只能发布oops,过程包括:

    - 向终端上输出错误消息
    - 输出寄存器中保存的信息
    - 输出可供跟踪的回溯线索
    

    通常发送完oops之后,内核会处于一种不稳定状态。

    关于oops发生的时机:

    1. 发生在中断上下文:内核无法继续,会陷入混乱,导致系统死机
    2. 发生在idle进程或init进程(0号进程和1号进程),同上
    3. 发生在其他进程运行时,内核会杀死该进程并尝试着继续执行

    oops发生的可能原因:

    1. 内存访问越界
    2. 非法的指令
    3. ……

    oops中包含的重要信息:寄存器上下文和回溯线索

    1. 回溯线索:显示了导致错误发生的函数调用链。
    2. 寄存器上下文信息也很有用,比如帮助冲进引发问题的现场

    1.ksymoops

    回溯线索中的地址需要转化成有意义的符号名称
    ——需要调用ksymoops命令。
    并且还必须提供编译内核时产生的System.map。如果用的是模块,还需要一些模块信息。

    kysmoop saved_oops.txt
    

    2.kallsyms

    现在的版本中不需要使用sysmoops这个工具,因为可能会发生很多问题,新版本中引入了kallsyms疼,可以通过定义CONFIG_KALLSYMS配置选项启用。

    五、内核调试配置选项

    位于内核配置编辑器的内核开发菜单项中,都依赖于CONFIG_DEBUG_KERNEL。

    • slab layer debugging slab层调试选项
    • high-memory debugging 高端内存调试选项
    • I/O mapping debugging I/O映射调试选项
    • spin-lock debugging 自旋锁调试选项
    • stack-overflow debugging 栈溢出检查选项
    • sleep-inside-spinlock checking 自旋锁内睡眠选项
    • ……

    原子操作:指那些能够不分隔执行的东西;在执行时不能中断否则就
    是完不成的代码。
    例如;正在使用一个自旋锁或禁止抢占的代码。
    使用锁时睡眠是引发死锁的元凶。

    六、引发bug并打印信息

    1.BUG()和BUG_ON()

    被调用时会引发oops,导致栈的回溯和错误信息的打印。
    可以把这些调用当做断言使用,想要断言某种情况不该发生:

    if (bad_thing)
        BUG();
    或:
    BUG_ON(bad_thing);
    

    2.BUILD_BUG_ON()

    与BUG_ON()作用相同,仅在编译时调用。

    3.panic()

    可以引发更严重的错误,不但会打印错误信息,还会挂起整个系统。

    4.dump_stack()

    只在终端上打印寄存器上下文和函数的跟踪线索。

    七、神奇的系统请求键

    这个功能可以通过定义CONFIG_MAGIC_SYSRQ配置选项来启用。SysRq(系统请求)键在大多数键盘上都是标准键。
    该功能被启用时,无论内核出于什么状态,都可以通过特殊的组合键和内核进行通信。
    除了配置选项以外,还要通过一个sysctl用来标记该特性的开或关,启动命令如下:

    echo 1 > /proc/sys/kernel/sysrq
    

    Sysrq的几个命令:
    SysRq-s:将“脏”缓冲区跟硬盘交换分区同步
    SysRq-u:卸载所有的文件系统
    SysRq-b:重启设备
    ……参见以下表格:
    enter description here

    在一行内发送这三个键的组合可以重新启动濒临死亡的系统。

    内核代码中的Documentation/sysrq.txt有更详细说明,实际的实现在drivers/char/sysrq.c中。

    八、内核调试器的传奇

    1.gdb

    可以使用标准的GNU调试器对正在运行的内核进行查看。
    针对内核启动调试器的方法与针对进程的方法大致相同:

    gdb vmlinux /proc/kcore
    
    vmlinx:未经压缩的内核映像,区别于zImage或bImage,它存放于源代码树的根目录上。
    /proc/kcore作为一个参数选项,是作为core文件来用的,通过它能够访问到内核驻留的高端内存。只有超级用户才能读取此文件的数据
    

    可以使用gdb的所有命令来获取信息。例如:

    打印一个变量的值:
    p global_variable
    
    反汇编一个函数:
    disassemble function
    
    -g参数还可以提供更多的信息。
    

    局限性:

    1. 没有办法修改内核数据
    2. 不能单步执行内核代码

    2.kgdb

    是一个补丁 ,可以让我们在远程主机上通过串口利用gdb的所有功能对内核进行调试。
    需要两台计算机:仪态运行带有kgdb补丁的内核,第二胎通过串行线使用gdb对第一台进行调试。
    通过kgdb,gdb的所有功能都能使用:

    - 读取和修改变量值
    - 设置断点
    - 设置关注变量
    - 单步执行
    

    九、探测系统

    1.使用uid作为选择条件

    一般情况下,加入特性时,只要保留原有的算法而把新算法加入到其他位置上,基本就能保证安全。

    可以把用户id(UID)作为选择条件来实现这种功能:
    通过某种选择条件,安排到底执行哪种算法。
    例如:

    if (current-> uid !=7777) {
        /* 老算法…… */
    } else {
        /* 新算法…… */
    }
    

    即,除了uid=7777的用户以外,其他所有的用户都是用的老算法,所以这个7777用户可以专门用来测试新算法。

    2.使用条件变量

    如果代码与进程无关,或者希望有一个针对所有情况都能使用的机制来控制某个特性,可以使用条件变量
    这种方式比使用UID更简单,只需要创建一个全局变量作为一个条件选择开关:

    • 如果该变量为0,就使用某一个分支上的代码;
    • 否则,选择另外一个分支。

    操控方式:某种接口,或者调试器。

    3.使用统计量

    这种方法常用于使用者需要掌握某个特定事件的发生规律的时候。
    方法是创建统计量,并提供某种机制访问其统计结果。

    定义全局变量
    
    在/proc目录中创建一个文件
    or新建一个系统调用
    or通过调试器直接访问(最直接)
    

    注:不是SMP安全的,更好的方式是用原子操作。

    4.重复频率限制

    当系统的调试信息过多的时候,有两种方式可以防止这类问题发生:

    • 重复频率限制
    • 发生次数限制

    (1)重复频率限制
    就是限制调试信息,最多几秒打印一次,可以根据自己的需要调节频率。
    例如printk()函数的调节频率,可以用printk_ratelimit()函数限制。
    (2)发生次数限制
    这种方法是要调试信息至多输出几次,超过次数限制后就不能再输出。
    这种方法可以用来确认在特定情况下某段代码的确被执行了。

    ※用到的变量需要是**静态的、局部的。

    注:不是SMP安全,不是抢占安全,更好的方式是用院子操作。

    SMP(Symmetric Multi-Processing),对称多处理结构的简称,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。在这种技术的支持下,一个服务器系统可以同时运行多个处理器,并共享内存和其他的主机资源。

    十、二分搜索的妙用

    很多时候,内核的更新会带来bug,那么是哪一个内核版本带来的bug?可以使用二分法搜索。
    我们可以利用GIT来实现这一步骤。

    git bisect start    # 告知git要进行二分搜索
    git bisect bad <revision>   # 已知出现问题的最早内核版本
    git bisect bad  # 当前版本就是引发bug的最初版本的情况下使用这条命令
    git bisect good <revision>  # 最新的可正常运行的内核版本
    

    这之后,git就会利用二分搜索法在Linux源码树中,自动检测正常的版本内核和有bug的内核版本之间那个版本有隐患,然后再编译、运行以及测试正被检测的版本。

    如果这个版本正常:
    git bisect good
    
    如果这个版本运行有异常:
    git bisect bad
    

    对于每一个命令,GIT将在吗诶一个版本的基础上反复二分搜索源码树,并且返回所查的下一个内核版本,一直到不能再进行二分搜索位置,最终git会打印出有问题的版本号。

    总结:

    这一章中 ,我学到了有关内核bug和调试的很多知识,只是就目前看来它离我还比较远,需要在今后的学习过程中不断加深认识和理解。

  • 相关阅读:
    Vue项目问题-TypeError: this.getOptions is not a function
    JavaScript学习-JavaScript 如何工作:对引擎、运行时、调用堆栈的概述
    JavaScript学习-理解JavaScript中的执行上下文和执行栈
    Element-ui中的给el-row添加一个gutter间隔不生效
    Vue-cil3 配置路径别名详解
    常见的内存泄漏以及解决方案
    Vue学习-组件之间的8种通信方式
    JavaScript学习-WeakMap和Map的区别,WeakMap的原理,为什么能被GC?
    Javascript学习-WeakMap
    JavaScript学习-Map
  • 原文地址:https://www.cnblogs.com/20135202yjx/p/5315826.html
Copyright © 2020-2023  润新知