一文让你详细了解Linux内核的整体架构(图文演示)
前言: 本文和其它的“Linux内核分析”文章都基于如下约定: 内核版本为Linux 3.10.29(该版本是一个long term的版本,会被Linux社区持续维护至少2年),可以从下面的链接获取:https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.10.29.tar.xz ,鉴于嵌入式系统大多使用ARM处理器,因此涉及到体系结构部分的内容,都以ARM为分析对象。
一,Linux内核的核心功能
如下图所示,Linux内核只是Linux操作系统一部分。对下,它管理系统的所有硬件设备;对上,它通过系统调用,向Library Routine(例如C库)或者其它应用程序提供接口。
因此,其核心功能就是:管理硬件设备,供应用程序使用 。而现代计算机(无论是PC还是嵌入式系统)的标准组成,就是CPU、Memory(内存和外存)、输入输出设备、网络设备和其它的外围设备。所以为了管理这些设备,Linux内核提出了如下的架构。
二,Linux内核的整体架构
整体架构和子系统划分
上图说明了Linux内核的整体架构。根据内核的核心功能,Linux内核提出了5个子系统,分别负责如下的功能:
1. Process Scheduler,也称作进程管理、进程调度。负责管理CPU资源,以便让各个进程可以以尽量公平的方式访问CPU。
2. Memory Manager,内存管理。负责管理Memory(内存)资源,以便让各个进程可以安全地共享机器的内存资源。另外,内存管理会提供虚拟内存的机制,该机制可以让进程使用多于系统可用Memory的内存,不用的内存会通过文件系统保存在外部非易失存储器中,需要使用的时候,再取回到内存中。
3. VFS(Virtual File System),虚拟文件系统。Linux内核将不同功能的外部设备,例如Disk设备(硬盘、磁盘、NAND Flash、Nor Flash等)、输入输出设备、显示设备等等,抽象为可以通过统一的文件操作接口(open、close、read、write等)来访问。这就是Linux系统“一切皆是文件”的体现(其实Linux做的并不彻底,因为CPU、内存、网络等还不是文件,如果真的需要一切皆是文件,还得看贝尔实验室正在开发的"Plan 9”的)。
4. Network,网络子系统。负责管理系统的网络设备,并实现多种多样的网络标准。
5. IPC(Inter-Process Communication),进程间通信。IPC不管理任何的硬件,它主要负责Linux系统中进程之间的通信。
进程调度(Process Scheduler)
进程调度是Linux内核中最重要的子系统,它主要提供对CPU的访问控制。因为在计算机中,CPU资源是有限的,而众多的应用程序都要使用CPU资源,所以需要“进程调度子系统”对CPU进行调度管理。
进程调度子系统包括4个子模块(见下图),它们的功能如下:
1. Scheduling Policy,实现进程调度的策略,它决定哪个(或哪几个)进程将拥有CPU。
2. Architecture-specific Schedulers,体系结构相关的部分,用于将对不同CPU的控制,抽象为统一的接口。这些控制主要在suspend和resume进程时使用,牵涉到CPU的寄存器访问、汇编指令操作等。
3. Architecture-independent Scheduler,体系结构无关的部分。它会和“Scheduling Policy模块”沟通,决定接下来要执行哪个进程,然后通过“Architecture-specific Schedulers模块”resume指定的进程。
4. System Call Interface,系统调用接口。进程调度子系统通过系统调用接口,将需要提供给用户空间的接口开放出去,同时屏蔽掉不需要用户空间程序关心的细节。
内存管理(Memory Manager, MM)
内存管理同样是Linux内核中最重要的子系统,它主要提供对内存资源的访问控制。Linux系统会在硬件物理内存和进程所使用的内存(称作虚拟内存)之间建立一种映射关系,这种映射是以进程为单位,因而不同的进程可以使用相同的虚拟内存,而这些相同的虚拟内存,可以映射到不同的物理内存上。
内存管理子系统包括3个子模块(见下图),它们的功能如下:
1. Architecture Specific Managers,体系结构相关部分。提供用于访问硬件Memory的虚拟接口。
2. Architecture Independent Manager,体系结构无关部分。提供所有的内存管理机制,包括:以进程为单位的memory mapping;虚拟内存的Swapping。
3. System Call Interface,系统调用接口。通过该接口,向用户空间程序应用程序提供内存的分配、释放,文件的map等功能。
虚拟文件系统(Virtual Filesystem, VFS)
传统意义上的文件系统,是一种存储和组织计算机数据的方法。它用易懂、人性化的方法(文件和目录结构),抽象计算机磁盘、硬盘等设备上冰冷的数据块,从而使对它们的查找和访问变得容易。因而文件系统的实质,就是“存储和组织数据的方法”,文件系统的表现形式,就是“从某个设备中读取数据和向某个设备写入数据”。
随着计算机技术的进步,存储和组织数据的方法也是在不断进步的,从而导致有多种类型的文件系统,例如FAT、FAT32、NTFS、EXT2、EXT3等等。而为了兼容,操作系统或者内核,要以相同的表现形式,同时支持多种类型的文件系统,这就延伸出了虚拟文件系统(VFS)的概念。VFS的功能就是管理各种各样的文件系统,屏蔽它们的差异,以统一的方式,为用户程序提供访问文件的接口。
我们可以从磁盘、硬盘、NAND Flash等设备中读取或写入数据,因而最初的文件系统都是构建在这些设备之上的。这个概念也可以推广到其它的硬件设备,例如内存、显示器(LCD)、键盘、串口等等。我们对硬件设备的访问控制,也可以归纳为读取或者写入数据,因而可以用统一的文件操作接口访问。Linux内核就是这样做的,除了传统的磁盘文件系统之外,它还抽象出了设备文件系统、内存文件系统等等。这些逻辑,都是由VFS子系统实现。
VFS子系统包括6个子模块(见下图),它们的功能如下:
1. Device Drivers,设备驱动,用于控制所有的外部设备及控制器。由于存在大量不能相互兼容的硬件设备(特别是嵌入式产品),所以也有非常多的设备驱动。因此,Linux内核中将近一半的Source Code都是设备驱动,大多数的Linux底层工程师(特别是国内的企业)都是在编写或者维护设备驱动,而无暇估计其它内容(它们恰恰是Linux内核的精髓所在)。
2. Device Independent Interface, 该模块定义了描述硬件设备的统一方式(统一设备模型),所有的设备驱动都遵守这个定义,可以降低开发的难度。同时可以用一致的形势向上提供接口。
3. Logical Systems,每一种文件系统,都会对应一个Logical System(逻辑文件系统),它会实现具体的文件系统逻辑。
4. System Independent Interface,该模块负责以统一的接口(快设备和字符设备)表示硬件设备和逻辑文件系统,这样上层软件就不再关心具体的硬件形态了。
5. System Call Interface,系统调用接口,向用户空间提供访问文件系统和硬件设备的统一的接口。
网络子系统(Net)
网络子系统在Linux内核中主要负责管理各种网络设备,并实现各种网络协议栈,最终实现通过网络连接其它系统的功能。在Linux内核中,网络子系统几乎是自成体系,它包括5个子模块(见下图),它们的功能如下:
1. Network Device Drivers,网络设备的驱动,和VFS子系统中的设备驱动是一样的。
2. Device Independent Interface,和VFS子系统中的是一样的。
3. Network Protocols,实现各种网络传输协议,例如IP, TCP, UDP等等。
4. Protocol Independent Interface,屏蔽不同的硬件设备和网络协议,以相同的格式提供接口(socket)。
5. System Call interface,系统调用接口,向用户空间提供访问网络设备的统一的接口。
Linux内核源代码的目录结构:
Linux内核源代码包括三个主要部分:
1. 内核核心代码,包括第3章所描述的各个子系统和子模块,以及其它的支撑子系统,例如电源管理、Linux初始化等
2. 其它非核心代码,例如库文件(因为Linux内核是一个自包含的内核,即内核不依赖其它的任何软件,自己就可以编译通过)、固件集合、KVM(虚拟机技术)等
3. 编译脚本、配置文件、帮助文档、版权说明等辅助性文件
下图示使用ls命令看到的内核源代码的顶层目录结构,具体描述如下。
include/ ---- 内核头文件,需要提供给外部模块(例如用户空间代码)使用。kernel/ ---- Linux内核的核心代码,包含了3.2小节所描述的进程调度子系统,以及和进程调度相关的模块。mm/ ---- 内存管理子系统(3.3小节)。fs/ ---- VFS子系统(3.4小节)。net/ ---- 不包括网络设备驱动的网络子系统(3.5小节)。ipc/ ---- IPC(进程间通信)子系统。arch// ---- 体系结构相关的代码,例如arm, x86等等。arch//mach- ---- 具体的machine/board相关的代码。arch//include/asm ---- 体系结构相关的头文件。arch//boot/dts ---- 设备树(Device Tree)文件。init/ ---- Linux系统启动初始化相关的代码。block/ ---- 提供块设备的层次。sound/ ---- 音频相关的驱动及子系统,可以看作“音频子系统”。drivers/ ---- 设备驱动(在Linux kernel 3.10中,设备驱动占了49.4的代码量)。lib/ ---- 实现需要在内核中使用的库函数,例如CRC、FIFO、list、MD5等。crypto/ ----- 加密、解密相关的库函数。security/ ---- 提供安全特性(SELinux)。virt/ ---- 提供虚拟机技术(KVM等)的支持。usr/ ---- 用于生成initramfs的代码。firmware/ ---- 保存用于驱动第三方设备的固件。samples/ ---- 一些示例代码。tools/ ---- 一些常用工具,如性能剖析、自测试等。Kconfig, Kbuild, Makefile, scripts/ ---- 用于内核编译的配置文件、脚本等。COPYING ---- 版权声明。MAINTAINERS ----维护者名单。CREDITS ---- Linux主要的贡献者名单。REPORTING-BUGS ---- Bug上报的指南。Documentation, README ---- 帮助、说明文档。原创:一文让你详细了解Linux内核的整体架构(图文演示) - 知乎
说说几个我偶尔会用到的调试方法 Linux 内核
大家好,我是你们的工具人老吴。
今天,和大家分享一下几个 Linux 内核的调试小技巧。
当你遇到一个 bug,你调试了 1 年半载都解决不了,这其实一件好事。
因为它会时刻提醒你平时写代码时要谨慎、要多看书、多去认识一些更资深的人,别问我为什么会有这样的感受,因为是亲身经历~
掌握一个调试工具是需要学习成本的,这里只是列举我自己会用到的工具,如果有某个你觉得特别牛逼的工具而我没提到的话,请原谅我。
好,下面开始正文。
最重要的是:思路
调试 bug 时不要急着做实验,先梳理一下思路。
一般可以总结成如下步骤:
1、理解问题;
2、重现问题;
3、定位问题,找到相关的代码;
4、尝试修复问题;
5、如果失败,回到第 1 步;
bug 一般分为这几类:
1、Crash,最常遇到的,可能是因为我是做设备驱动开发的缘故;
2、Lockup,比较少,这类问题预防比事后调试更重要;
3、Logic/implementation error,这个也比较容易遇到,一般是运行不报错,但是运行的结果不符合预期;
4、Resource leak,偶尔会遇到;
5、Performance,偶尔会遇到,对于做驱动开发的话,一般是先考虑功能,当性能达不到要求时,再考虑优化性能。
调试工具的类别:
1、很多人不知道,调试最重要的工具是:我们的大脑。换句话说,也就是我们对内核个子系统、驱动开发的理解;
2、Logs and dump analysis。内核很贴心,许多异常发生时都会有一堆的 Kernel Panic 的信息,经常能让我们直接定位到引起异常的代码;
3、Tracing/profiling。这类工具一般能让我们理解程序的运行流程,不仅适合用来调试问题,也适合用来学习和理解内核的各种功能实现。
4、Interactive debugging。主要就是 gdb,我个人用得很少。
5、Debugging frameworks。许多的调试工具经过不断地发展和完善后,就慢慢地形成了一整套的调试框架,例如 Ftrace、SystemTap。
下面是几个我常用的调试技巧 / 工具。
最常用的方法:打印
点击查看大图关于打印的工具,主要是这 3 种:
1、printk()
最原始的打印 api,可以用但是主流观点已经不推荐使用了。
与之相关的是启动参数 loglevel,它决定了可以被打印出来的信息的最低优先级。
2、pr_*()
推荐用 pr_*() 来代替 printk(),这是一个函数族:
pr_emerg(), pr_alert(), pr_crit(), pr_err(), pr_warning(), pr_notice(), pr_info(), pr_cont(), pr_debug()
例如:
pr_info("Booting CPU %d\n", cpu);
内核会打印:
[ 202.350064] Booting CPU 1
3、dev_*()
同样是一个函数族:
dev_emerg(), dev_alert(), dev_crit(), dev_err(), dev_warn(), dev_notice(), dev_info(), dev_dbg()
它们的最大特点是需要传入一个 struct device 的参数,并且会打印出这个 device 的名字,一边是在驱动相关的代码里使用。
例如:
dev_info(&pdev->dev, "in probe\n");
内核会打印:
[ 25.878382] serial 48024000.serial: in probe
关于 pr_debug() and dev_dbg()
要使用这两个 api,需要在对应的代码里 #deinfe DEBUG。
当内核使能了 CONFIG_DYNAMIC_DEBUG,我们就可以通过 /sys/kernel/debug/dynamic_debug/control 动态地是否要打印 log,以及打印哪些 log。
使用方法,大致如下:
$ mount -t debugfs none /sys/kernel/debug/$ cd /sys/kernel/debug/dynamic_debug/$ echo “file xxx.c +p” > control$ echo “file svcsock.c line 1603 +p” > control$ echo “file drivers/usb/core/* +p” > control$ echo “file xxx.c -p” > control
具体地,可以参考:
https://training.ti.com/sites/default/files/docs/Kernel-Debug-Series-Part4-dynamic-debug.pdf
分析 Kernel Panic 的 信息
举个例子,下面是一次 Kernel Panic:
$ cat /sys/class/gpio/gpio504/value[23.688107] Unable to handle kernel pointer dereference at virtual address 00000000[23.696431] pgd = (ptrval)[23.699167] [00000000] *pgd=28bd4831, *pte=00000000, *ppte=00000000[23.705596] Internal error: Oops: 17 [#1] SMP ARM[23.710316] Modules linked in:[23.713394] CPU: 1 PID: 177 Comm: cat Not tainted 4.19.17 #8[23.719060] Hardware name: Freescale i.MX6 Quad/DualLite (Device Tree)[23.725606] PC is at mcp23sxx_spi_read+0x34/0x84[23.730241] LR is at _regmap_raw_read+0xfc/0x384[23.734866] pc : [<c0539c44>]lr : [<c067d894>]psr: 60040013[23.741142] sp : d8c6da48 ip : 00000009 fp : d8c6da6c[23.746375] r10: 00000040 r9 : d8a94000 r8 : d8c6db30[23.751608] r7 : c12ed9d4 r6 : 00000001 r5 : c0539c10 r4 : c1208988[23.758145] r3 : d8789f41 r2 : 2afb07c1 r1 : d8789f40 r0 : 00000000[...] // 省略
关键信息:
PC is at mcp23sxx_spi_read+0x34pc : [<c0539c44>]PC 是当前执行的指令的地址。
接下来,我们可以借助 addr2line, 定位到具体是哪一行代码引起了panic:
$ arm-linux-addr2line -f -e vmlinux 0xc0539c44mcp23sxx_spi_read/home/sprado/elce/linux/drivers/pinctrl/pinctrl-mcp23s08.c:357
另外,还可以用 gdb 来定位代码:
$ arm-linux-gdb vmlinux(gdb) list *(mcp23sxx_spi_read+0x34)0xc0539c44 is in mcp23sxx_spi_read (drivers/pinctrl/pinctrl-mcp23s08.c:357)
earlyprintk
earlyprintk 一般用来处理一些发生在启动初期时的异常。
最常见的现象就是系统打印完 Starting Kernel... 后就 hang 住了。
用法:
1、配置内核:
CONFIG_EARLY_PRINTKCONFIG_DEBUG_LL
2、设置启动参数,类似:
root=/dev/mmcblk0p2 rootwait rw earlyprintk console=ttyS0,115200
WARN_ON()
这个函数可以打印出当前的函数调用栈。
我一般会在高度可疑的地方使用它。
举个例子:
static int sun6i_spi_probe(struct platform_device *pdev){struct spi_master *master;struct sun6i_spi *sspi; [...]// 用于调试WARN_ON(1); master = spi_alloc_master(&pdev->dev, sizeof(struct sun6i_spi)); [...]
当运行到 WARN_ON(1) 时,内核会打印:
[ 1.847018] WARNING: CPU: 1 PID: 1 at drivers/spi/spi-sun6i.c:549 sun6i_spi_probe+0x20/0x3ac[ 1.855454] Modules linked in:[ 1.858525] CPU: 1 PID: 1 Comm: swapper/0 Not tainted 4.14.111 #196[ 1.864781] Hardware name: sun8i[ 1.868032] [<c02287fc>] (unwind_backtrace) from [<c0225398>] (show_stack+0x10/0x14)[ 1.875776] [<c0225398>] (show_stack) from [<c0a1ba3c>] (dump_stack+0x94/0xa8)[ 1.882997] [<c0a1ba3c>] (dump_stack) from [<c0240c24>] (__warn+0xe8/0x100)[ 1.889953] [<c0240c24>] (__warn) from [<c0240cec>] (warn_slowpath_+0x20/0x28)[ 1.897517] [<c0240cec>] (warn_slowpath_) from [<c06a03c0>] (sun6i_spi_probe+0x20/0x3ac)[ 1.905953] [<c06a03c0>] (sun6i_spi_probe) from [<c0617980>] (platform_drv_probe+0x4c/0xb0)[ 1.914299] [<c0617980>] (platform_drv_probe) from [<c06160dc>] (driver_probe_device+0x234/0x2f0)[ 1.923162] [<c06160dc>] (driver_probe_device) from [<c0616244>] (__driver_attach+0xac/0xb0)[ 1.931592] [<c0616244>] (__driver_attach) from [<c06144ec>] (bus_for_each_dev+0x68/0x9c)[ 1.939762] [<c06144ec>] (bus_for_each_dev) from [<c0615654>] (bus_add_driver+0x198/0x210)[ 1.948020] [<c0615654>] (bus_add_driver) from [<c0616aec>] (driver_register+0x78/0xf8)[ 1.956017] [<c0616aec>] (driver_register) from [<c0201a70>] (do_one_initcall+0x40/0x16c)[ 1.964193] [<c0201a70>] (do_one_initcall) from [<c1000e6c>] (kernel_init_freeable+0x1c8/0x264)[ 1.972884] [<c1000e6c>] (kernel_init_freeable) from [<c0a2ef4c>] (kernel_init+0x8/0x114)[ 1.981054] [<c0a2ef4c>] (kernel_init) from [<c0222058>] (ret_from_fork+0x14/0x3c)[ 1.988686] ---[ end trace dc4e090f55ad2de8 ]---
我们可以很清晰地看到 sun6i_spi_probe() 被调用的流程。
这个方法跑起来很简单,但是每次使用都得编译和更新内核,非常不方便,只适合轻度使用。
Pstore
如果发生 Kernel panic 时,我们并没有连接串口终端,那么这一次的崩溃信息就丢失了。
Pstore (persistent storage) 就可以用来处理这种情况。
当发生 Kernel painic 时,Pstore 会自动保存 oops 和 panic 的 log,并且在软重启后仍可以查看 log 信息。
默认情况下,log 是存储在 RAM 的某个保留区域中,但也可以使用存储设备,例如闪存。
用法:
1、配置内核:
CONFIG_PSTORECONFIG_PSTORE_RAM
2、配置 dts,为 Pstore 预留一块内存,类似:
reserved-memory {#address-cells = <1>;#size-cells = <1>;ranges;ramoops: ramoops@0b000000 {compatible = "ramoops";reg = <0x20000000 0x200000>; /* 2MB */record-size = <0x4000>; /* 16kB */console-size = <0x4000>; /* 16kB */};};
3、假设刚发生了一次 Panic,并且已经软重启:
$ mount -t pstore pstore /sys/fs/pstore/$ ls /sys/fs/pstore/dmesg-ramoops-0dmesg-ramoops-1
通过上面这两个文件就可以看到内核的崩溃信息了。
内核文档:
Documentation/admin-guide/ramoops.rst
devmem2
这是一个命令行工具,它可以在用户空间去读写内存。
大多数情况,我是用它来读写寄存器,简单粗暴。
用法:
$ apt-get install devmem2
1、查看寄存器 TMR_IRQ_EN_REG:
$ devmem2 0x0x01C20C00/dev/mem opened.Memory mapped at address 0xb6f38000.Value at address 0x0 (0xb6f38000): 0xEA000016
2、修改 TMR_IRQ_EN_REG:
# devmem2 0x0x01C20C00 w 0xEA000018/dev/mem opened.Memory mapped at address 0xb6fe8000.Value at address 0x0 (0xb6fe8000): 0xEA000016Written 0xEA000018; readback 0xEA000018
GDB
如果你想完全控制内核的运行,例如单步执行、查看变量等,可以用 GDB。
点击查看大图这里采用的是 C/S 架构,在板子上运行 server (kgdb),在 PC 机上运行 client (gdb),通讯的方式可以是串口,或者网络,我一般是用串口。
如何配置:
1、配置内核:
CONFIG_KGDBCONFIG_KGDB_SERIAL_CONSOLECONFIG_KGDB_KDB
2、设置启动参数:kgdoc
console=ttyS0,115200 kgdboc=ttyS0,115200 earlyprintk root=/dev/mmcblk0p2 oops=panic panic=0
ttyS0 是板子的调试串口。
要使用 kgdb,必须为其设置一个 I/O driver,我一般使用 kgdb over serial console (简称 kgdboc)
oops=panic panic=0 很重要。
另外,也通过在启动后设置 kgdboc:
echo ttyS0 > /sys/module/kgdboc/parameters/kgdboc
3、让内核进入 debug 模式:
$ echo g > /proc/sysrq-trigger[ 1958.025927] sysrq: SysRq : DEBUG[ 1958.029191] KGDB: Entering KGDB
4、让 PC 机连接板子
$ arm-linux-gdb vmlinux(gdb) set serial baud 115200(gdb) target remote /dev/ttyUSB0Remote debugging using /dev/ttyUSB00xc02c3540 in kgdb_breakpoint ()
举个例子:
配置好之后,通过 gdb 调试内核跟通过 gdb 调试应用的操作是一样的。
这里我举一个小例子。
首先,人为让内核 Crash:
$ echo WRITE_KERN > /sys/kernel/debug/provoke-crash/DIRECTEntering kdb (current=0xffffffc0de55f040, pid 1470) on processor 4 Oops: ()due to oops @ 0xffffff80108bfa48CPU: 4 PID: 1470 Comm: bash Not tainted 5.3.0-rc2+ #13pc : __memcpy+0x48/0x180lr : lkdtm_WRITE_KERN+0x4c/0x90...
下面开始调试。
1、查看调用栈:
(gdb) btCall trace:dump_backtrace+0x0/0x138show_stack+0x20/0x2ckdb_show_stack+0x60/0x84...do_mem_abort+0x4c/0xb4el1_da+0x20/0x94__memcpy+0x48/0x180lkdtm_do_action+0x24/0x44direct_entry+0x130/0x178
2、查看栈帧的内容:
(gdb) frame 1#1 0xffffff801056584c in lkdtm_WRITE_KERN () at .../drivers/misc/lkdtm/perms.c:116116memcpy(ptr, (unsigned char *)do_nothing, size);
基本可以确定是使用 memcpy() 时导致 Crash。
3、查看相关代码:
(gdb) list112 size = (unsigned long)do_overwritten - (unsigned long)do_nothing;[...]116 memcpy(ptr, (unsigned char *)do_nothing, size);
需要核查一下 ptr、do_nothing、size,这 3 个参数是否合法。
4、打印变量值:
(gdb) print size$3 = 18446744073709551584(gdb) print do_overwritten - do_nothing$4 = -32
最后发现 18446744073709551584 其实就是 (unsigned long) 的 -32。memcpy 的数据大小是 -32,导致了内核崩溃。
Ftrace
Ftrace 的作用是帮助开发人员了解 Linux 内核的运行时行为,以便进行故障调试或性能分析。
最早 Ftrace 是一个 function tracer,仅能够记录内核的函数调用流程。如今 ftrace 已经成为一个 framework,采用 plugin 的方式支持开发人员添加更多种类的 trace 功能。
用法:
$ mount -t tracefs none /sys/kernel/tracing$ cd /sys/kernel/tracing/$ cat available_tracershwlat blk mmiotrace function_graph wakeup_dl wakeup_rt wakeup function nop
跟踪器 tracer 表示的是要跟踪的目标。
假设我们抓一次 spi 传输的过程:
echo 0 > tracing_onecho function_graph > current_tracerecho *spi* > set_ftrace_filterecho *dma* >> set_ftrace_filterecho *spin* >> set_ftrace_notraceecho 1 > tracing_on./spidev_testecho 0 > tracing_oncat trace
得到的信息:
1) + 41.292 us | spidev_open(); 1) | spidev_ioctl() { 1) | spi_setup() { 1) 0.417 us | __spi_validate_bits_per_word.isra.0(); 1) | sunxi_spi_setup() { 1) 0.834 us | sunxi_spi_check_cs(); 1) 0.875 us | spi_set_cs(); 1) 0.625 us | sunxi_spi_cs_control(); 1) + 17.125 us | } 1) 0.833 us | spi_set_cs(); 1) + 30.458 us | } 1) ! 699.875 us | } [...]
相关参考:
https://blog.csdn.net/Guet_Kite/article/details/101791125
Kdump
这个工具我没有用过,但是它似乎很强大,所以我觉得应该简单介绍一下。
kdump 是一种基于 kexec 系统调用 的内核崩溃转储机制。
当系统崩溃时,kdump 使用 kexec 启动进入到第二个内核 (dump-capture kernel),从而获得 coredump 信息。
用法:
1、设置启动参数:
crashkernel=64M
2、运行 kexec:
$ kexec --type zImage -p /boot/zImage \ --initrd=<initrd-for-dump-capture-kernel> \ --dtb=<dtb-for-dump-capture-kernel> \ --command-line="XXX"
运行完 kexec 后,dump-capture kernel 就被加载进内存了。
以后如果发生了 kernel panic,dump-capture kernel 会被加载并运行。
我们可以在 dump-capture kernel 下,获得 coredump 文件:
$ cp /proc/vmcore <dump-file>
然后就可以在 PC 上使用 gdb/crash 来调试分析了:
$ arm-linux-gdb path/to/vmlinux -c path/to//vmcore$ crash path/to/vmlinux path/to/vmcore
内核文档:
Documentation/kdump/kdump.txt
总结
预防为主,调试为辅。
软件开发没有银弹,同样的,bug 调试也没有银弹。但是多熟悉一些调试工具,是有好处的。
当然还有很多调试工具、技巧是我不知道了,欢迎大家分享给我。
Anyway, what we know is a drop, what we don't know is an ocean.
祝周末愉快。
—— The End ——
相关问答
有哪些高质量的美剧推荐?罗马导演:迈克尔·艾普特/艾伦·考特勒/艾伦·保尔编剧:BrunoHeller/WilliamJ.MacDonald/约翰·米利厄斯主演:凯文·麦克基德/雷·史蒂...
微软是否打算在Azure数据中心部署基于AMD霄龙处理器的虚拟机平台?不过在数据中心GPU业务方面,AMD仍有较大的上升空间。好消息是,本周开始,微软将提供基于AMDEypc+RadeonInstinct加速器的实例,它就是AzureNVv4。....
nshm是啥文件?nshm是一种操作系统内核中的机制,用于管理共享内存。共享内存是一种进程间通信的方式,它可以让多个进程在同一块物理内存中共享数据,从而提高进程间通信的效率...
体育场上违背体育精神的不齿行为,哪一次最让你怒火中烧?2002年世界杯韩国队闯进了四强创造了亚洲国家世界杯历史最好成绩,但除了韩国却没几个亚洲人觉得骄傲,因为他们的“晋级之路”充满着太多的争议、误判和交易,对...
为什么会兴起腐剧热,耽美热,会产生什么影响?我有个朋友就喜欢这种耽美剧,最近都在发泰国的这种剧,她和她老公是同学,从校园一直到出社会,这种爱情其实很让人羡慕,有多少人可以一路是你的走下来呢?那个...
linux2 4 内核 共有几个版本NCARD内核1.45+2.53好久没更新了不过也不是太要紧,游戏基本都能玩。2008年1月24日发布最新的稳定版,版本号为2.6.24。以此为例,2代表主版本号,6代...
linux 内核 怎么进入写代码的界面?要进入Linux内核的代码编写界面,您需要进行以下步骤:获取Linux内核源代码:首先,您需要获取Linux内核的源代码。您可以通过官方网站或使用版本控制系统...
内核 和CPU之间有什么关系? - 152****4731 的回答 - 懂得一个CPU可以有N个核心,即所谓的双核CPU。八核等,但一个核心只能对一个CPU。内核是CPU的核心,一个CPU可以有多个内核(也就是多核处理器),而一个内核...
有什么开源方案 - OSCHINA - 中文开源技术交流社区如果说SERVER有N个内核用不上,完全可以讲同样的一个服务模块,开多个,这比做成一个多进程的模块要舒服多,也更高效。如果只是通信层的话...直接用socket好了..开...
有无适合多人基友组队的联机游戏推荐?《使命召唤10:幽灵》(CallofdutyGhosts)(配图有中文是因为有支持正版的内核汉化补丁)也能怼起来?可事实就是如此,COD10有独特的战队系统,每个玩家都可...四...