《Linux内核设计与实现》课本第十八章自学笔记

By20135203齐岳

通过打印来调试

printk()是内核提供的格式化打印函数,除了和C库提供的printf()函数功能相同外还有一些资深的特殊功能

健壮性

在任何时候内核的任何地方都能调用printk()函数,只有在终端还未初始化的时候不能调用。

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

解决办法是提供一个变体函数early _ printk(),但这种办法在某些硬件体系结构上无法实现,缺少可移植性。

日志等级

printk()可以指定一个日志级别,内核把级别比某个特定值低的所有消息显示在终端上。如果没有指定一个等级记录,函数会选用默认的DEFAULT _ MESSAGE _ LOGLEVEL,现在的默认等级为KERN _ WARNING。但默认值将来存在变化性,所以还是应该指定一个记录等级。

内核将最重要的记录等级KERN _ EMERG定为<0>,无关紧要的记录等级KERN _ DEBUG定为<7>

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

  • 保持终端的默认记录等级不变,给所有调试信息KERN _ CRIT或更低的等级。
  • 给所有调试信息KERN _ DEBUG等级,调整终端的默认记录等级。

记录缓冲区

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

  • 优点:健壮性,在中断上下文中也可以方便的使用;简单性,使记录维护起来更容易。
  • 缺点:可能会丢失消息。

syslogd和klogd

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

klogd:

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

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

oops

oops是内核告知用户有不幸发生的最常用的方式。

内核很难自我修复,也不能将自己杀死,只能发布oops,过程为:向终端上输出错误消息、输出寄存器中保存的信息、输出可供跟踪的回溯线索。通常发布oops之后,内核会处于一种不稳定状态。

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

寄存器上下文和回溯线索

  • 回溯线索:显示了导致错误发生的函数调用链,以便我们观察发生了什么。
  • 寄存器上下文信息:帮助冲进引发问题的现场。

ksymoops

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

kysmoop saved_oops.txt

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并打印信息

一些内调用可以用来方便标记bug,提供断言并输出信息。

  • BUG()和BUG_ON()

    被调用时会引发oops,导致栈的回溯和错误信息的打印。

    可以把这些调用当做断言使用,想要断言某种情况不该发生:

      if (bad_thing)
    BUG();
    或更好的形式:
    BUG_ON(bad_thing);
  • BUILD _ BUG_ ON()

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

  • panic()

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

  • dump_stack()

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

神奇的系统请求键

这个功能可以通过定义CONFIG _ MAGIC _ SYSRQ配置选项来启用。SysRq(系统请求)键在大多数键盘上都是标准键。

该功能被启用时,无论内核出于什么状态,都可以通过特殊的组合键和内核进行通信。

除了配置选项以外,还要通过一个sysctl用来标记该特性的开或关,启动命令如下:

echo 1 > /proc/sys/kernel/sysrq

Sysrq的命令:

内核调试器的传奇

gdb(与本周实验内容结合)

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

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

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

p global_variable//打印一个变量的值

disassemble function//反汇编一个函数

-g参数还可以提供更多的信息。

局限性:

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

kgdb

是一个补丁 ,可以让我们在远程主机上通过串口利用gdb的所有功能对内核进行调试。

需要两台计算机:仪态运行带有kgdb补丁的内核,第二胎通过串行线使用gdb对第一台进行调试。

通过kgdb,gdb的所有功能都能使用:

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

探测系统

使用uid作为选择条件

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

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

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

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

使用条件变量

如果代码与进程无关,或者希望有一个针对所有情况都能使用的机制来控制某个特性,可以使用条件变量。

这种方式比使用UID更简单,只需要创建一个全局变量作为一个条件选择开关:

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

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

使用统计量

这种方法常用于使用者需要掌握某个特定事件的发生规律的时候。

方法是创建统计量,并提供某种机制访问其统计结果。

定义全局变量

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

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

重复频率限制

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

  • 重复频率限制:

    就是限制调试信息,最多几秒打印一次,可以根据自己的需要调节频率。

    例如printk()函数的调节频率,可以用printk_ratelimit()函数限制。

  • 发生次数限制

    这种方法是要调试信息至多输出几次,超过次数限制后就不能再输出。

    这种方法可以用来确认在特定情况下某段代码的确被执行了。

注:用到的变量需要是静态的、局部的。不是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会打印出有问题的版本号。

《Linux内核设计与实现》课本第十八章自学笔记——20135203齐岳的更多相关文章

  1. 《Linux内核设计与实现》课本第四章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第四章自学笔记 进程调度 By20135203齐岳 4.1 多任务 多任务操作系统就是能同时并发的交互执行多个进程的操作系统.多任务操作系统使多个进程处于堵 ...

  2. 《Linux内核设计与实现》课本第三章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第三章自学笔记 进程管理 By20135203齐岳 进程 进程:处于执行期的程序.包括代码段和打开的文件.挂起的信号.内核内部数据.处理器状态一个或多个具有 ...

  3. 《Linux内核设计与实现》第十八章学习笔记

    第十八章 调试 [学习时间:1小时 总结博客时间:1小时15分] [学习内容:出现bug的原因.内核调试器gdb.使用Git进行二分查找] 内核级开发的调试工作远比用户级开发艰难,它带来的风险比用户级 ...

  4. 《Linux内核设计与实现》第十八章读书笔记

    1.内核中的bug 内核中的bug表现得不像用户级程序中那么清晰——因为内核.用户以及硬件之间的交互会很微妙: 从隐藏在源代码中的错误到展现在目击者面前的bug,往往是经历一系列连锁反应的事件才可能触 ...

  5. 《Linux内核设计与实现》 第十八章学习笔记

    调  试 一.准备开始 一个bug 一个藏匿bug的内核版本 相关内核代码的知识和运气 知道这个bug最早出现在哪个内核版本中. 1.想要成功进行调试: 让这些错误重现 抽象出问题 从代码中搜索 二. ...

  6. 《Linux内核设计与实现》课本第五章学习笔记——20135203齐岳

    <Linux内核设计与实现>课本第五章学习笔记 By20135203齐岳 与内核通信 用户空间进程和硬件设备之间通过系统调用来交互,其主要作用有三个. 为用户空间提供了硬件的抽象接口. 保 ...

  7. 《linux内核设计与实现》第十八章

    第十八章 调试 调试工作艰难是内核级开发区别于用户级开发的一个显著特点. 一.准备开始 1.内和调试需要什么 一个bug(大部分bug通常都不是行为可靠而且定义明确的) 一个藏匿bug的内核版本(知道 ...

  8. 【读书笔记】Linux内核设计与实现(第十八章)

    18.1 准备开始 需要: 1.一个确定的bug.但是,大部分bug通常都不是行为可靠定义明确的. 2.一个藏匿bug的内核版本. 18.2 内核中的bug bug发作时的症状: 明白无误的错误代码( ...

  9. 《Linux内核设计与实现》第八周读书笔记——第四章 进程调度

    <Linux内核设计与实现>第八周读书笔记——第四章 进程调度 第4章 进程调度35 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配 ...

随机推荐

  1. 我的Android第四章

    本章需掌握 1.连接真机的过程. 2.dx指令的作用 3.adb是什么? 4.adb有什么用(有什么好处)? 5.adb的一些常用命令 1.连接真机的过程. 需要:一部Android机.usb连接线, ...

  2. sql的各种join连接

    SELECT * FROM TableA INNER JOIN TableB ON TableA.name = TableB.name id name id name -- ---- -- ---- ...

  3. Xcode 提高效率的几个快捷键

    1.一次性修改一个scope里的变量名: 点击该变量,出现下划虚线,然后command+control+E激活所有相同变量,然后进行修改. 2.删除一个词:option+delete 删除一句话:co ...

  4. [Machine-Learning] K临近算法-简单例子

    k-临近算法 算法步骤 k 临近算法的伪代码,对位置类别属性的数据集中的每个点依次执行以下操作: 计算已知类别数据集中的每个点与当前点之间的距离: 按照距离递增次序排序: 选取与当前点距离最小的k个点 ...

  5. JAVA中用堆和栈的概念来理解equals() "=="和hashcode()

    在学习java基本数据类型和复杂数据类型的时候,特别是equals()"=="和hashcode()部分时,不是很懂,也停留了很长时间,最后终于有点眉目了. 要理解equals() ...

  6. angular手势事件之on-Hold

    .controller( 'actionsheetCtl',['$scope',function($scope){ $scope.onHold=function(){ console.log(even ...

  7. php 安装yar扩展

    git:https://github.com/laruence/yar 先克隆 如果没有 git 需要先安装 yum install git 然后 克隆 git clone https://githu ...

  8. IMPORT FROM 表数据导入

    Syntax IMPORT FROM [<file_type>] <file_path> [INTO <table_name>] [WITH <import_ ...

  9. BW知识问答锦集2

    PM面试分为BW.BO两部分,根据顾问的简历和应聘的岗位所侧重的问题不同. BW包括基础知识.增量.增强.LO抽取.数据源. BO包括 CR.CR.WEBI.UNI. 一. 基础知识 技术面试 1. ...

  10. 调度系统任务创建---创建一个JoinTrigger的依赖任务(五)

    有时候我们需要创建一个任务,这个任务有多个下游任务,在所有下游任务执行成功后再触发一个join操作. 这种场景可以使用JoinTrigger的触发器来实现. 该场景对应的拓扑结构如下: 该触发器的详细 ...