4. crash工具核心命令详解:struct、list、task、kmem、vm等命令的实战用法
crash工具提供了数十个调试命令,其中struct、list、task、kmem、vm是文件系统调试中使用频率最高的核心命令。本章将逐一剖析这些命令的语法、参数和实战场景,帮助你在内核崩溃转储或活动系统分析中快速定位问题。
4.1 struct命令:内核数据结构的“解剖刀”
struct命令用于显示内核数据结构的内存布局和成员值。在文件系统调试中,它常被用来查看inode、dentry、super_block等核心结构体的实时内容。
4.1.1 基本语法与常用选项
| 选项 | 说明 | 示例 |
|---|---|---|
-o |
显示结构体成员的偏移量 | struct -o inode |
-s |
显示结构体大小 | struct -s dentry |
地址 |
显示指定地址的结构体内容 | struct inode ffff88003a1c2c00 |
成员路径 |
只显示特定成员的值 | struct inode ffff88003a1c2c00 i_mode |
4.1.2 实战:查看inode结构体
假设我们怀疑某个inode的引用计数异常,导致文件系统无法卸载。使用以下命令查看inode的详细信息:
crash> struct inode ffff88003a1c2c00
struct inode {
i_mode = 0x81a4,
i_opflags = 0x1,
i_uid = {
val = 0x3e8
},
i_gid = {
val = 0x3e8
},
i_flags = 0x800,
i_count = {
counter = 0x2
},
i_nlink = 0x1,
i_size = 0x1000,
i_blocks = 0x8,
i_state = 0x1,
i_data = {
...
},
i_sb_list = {
next = 0xffff88003a1c2d08,
prev = 0xffff88003a1c2d08
}
}
i_count.counter值大于预期时,说明有进程未释放对该inode的引用。结合lsof或task命令可以找到持有引用的进程。
4.1.3 查看结构体偏移量
在编写内核模块或分析汇编代码时,结构体成员的偏移量至关重要:
crash> struct -o dentry
struct dentry {
[0] d_flags
[4] d_seq
[8] d_hash
[24] d_parent
[32] d_name
[48] d_inode
[56] d_iname
[72] d_count
[76] d_lock
[80] d_op
[88] d_sb
[96] d_time
[104] d_fsdata
[112] d_lru
[128] d_u
}
SIZE: 0x130
4.2 list命令:遍历内核链表的利器
内核中大量使用链表来管理对象(如inode链表、dentry链表、super_block链表)。list命令可以遍历这些链表,并显示每个节点的内容。
4.2.1 命令格式
list <结构体类型> -H <链表头地址> <链表成员>
常用选项:
-H:指定链表头地址-s:显示结构体内容-o:只显示偏移量-l:限制遍历数量
4.2.2 实战:遍历super_block的inode链表
当文件系统出现inode泄漏时,遍历super_block的inode链表可以快速统计inode数量:
crash> list super_block ffff88003a1c2c00 s_inodes -s inode
ffff88003a1c2c00
struct inode {
i_mode = 0x81a4,
i_count = { counter = 0x2 },
...
}
ffff88003a1c2e00
struct inode {
i_mode = 0x41ed,
i_count = { counter = 0x1 },
...
}
...
-l 100限制输出数量,避免终端卡死。
4.2.3 链表遍历的三种模式
| 模式 | 命令示例 | 适用场景 |
|---|---|---|
| 正向遍历 | list dentry -H ffff88003a1c2c00 d_child |
默认模式,从头到尾遍历 |
| 反向遍历 | list dentry -H ffff88003a1c2c00 d_child -r |
从尾到头遍历,用于分析最近添加的节点 |
| 条件遍历 | list inode -H ffff88003a1c2c00 i_sb_list -S i_state=1 |
只遍历满足条件的节点,减少输出量 |
4.3 task命令:进程与线程的调试窗口
task命令用于显示进程或线程的详细信息,包括进程状态、文件描述符、内存映射等。在文件系统调试中,它常被用来分析哪些进程持有文件引用。
4.3.1 查看进程信息
crash> task ffff88003a1c2c00
PID: 1234
COMMAND: "nginx"
TASK_STATE: 0 (TASK_RUNNING)
FLAGS: 0x4200000
MM: ffff88003a1c2e00
FILES: ffff88003a1c3000
...
4.3.2 查看进程的文件描述符表
当怀疑某个进程未关闭文件导致文件系统无法卸载时,可以查看其文件描述符表:
crash>> task ffff88003a1c2c00 files
PID: 1234
COMMAND: "nginx"
FILE FD FLAGS INODE
ffff88003a1c3200 3 0x8002 ffff88003a1c3400
ffff88003a1c3600 4 0x8002 ffff88003a1c3800
...
files字段可能为NULL,表示该进程是内核线程(如kworker),不持有用户空间文件描述符。
4.4 kmem命令:内核内存的透视镜
kmem命令用于查看内核内存分配情况,包括slab缓存、伙伴系统、vmalloc区域等。在文件系统调试中,它常被用来分析内存泄漏和碎片问题。
4.4.1 查看slab缓存信息
crash> kmem -s dentry
CACHE NAME OBJSIZE ALLOCATED TOTAL SLABS
ffff88003a1c4000 dentry 192 1024 2048 32
ffff88003a1c4200 dentry 192 512 1024 16
...
4.4.2 查看内存分配统计
| 选项 | 说明 | 输出示例 |
|---|---|---|
-i |
显示内存整体使用情况 | 总内存、空闲内存、缓存使用量 |
-s |
显示slab缓存统计 | 每个slab的名称、对象大小、分配数 |
-v |
显示vmalloc区域 | vmalloc起始地址、大小、用途 |
-p |
显示物理内存页状态 | 页框号、标志、引用计数 |
4.4.3 实战:分析dentry缓存泄漏
当系统出现“dentry cache”过大时,使用以下命令定位问题:
crash> kmem -s dentry | grep -v "TOTAL"
CACHE NAME OBJSIZE ALLOCATED TOTAL SLABS
ffff88003a1c4000 dentry 192 1048576 1048576 16384
ffff88003a1c4200 dentry 192 1048576 1048576 16384
如果ALLOCATED接近TOTAL且持续增长,说明dentry缓存存在泄漏。此时可以结合list命令遍历dentry链表,查找异常节点。
4.5 vm命令:虚拟内存的详细地图
vm命令用于查看进程的虚拟内存布局,包括代码段、数据段、堆、栈、共享库等。在文件系统调试中,它常被用来分析内存映射文件(mmap)和进程地址空间。
4.5.1 查看进程虚拟内存
crash> vm ffff88003a1c2c00
PID: 1234
COMMAND: "nginx"
MM: ffff88003a1c2e00
START END FLAGS FILE
00400000 00401000 r-xp /usr/sbin/nginx
00600000 00601000 rw-p /usr/sbin/nginx
00601000 00622000 rw-p [heap]
7f0000000000 7f0000001000 rw-p [stack]
7f0000001000 7f0000002000 r-xp /lib/x86_64-linux-gnu/libc.so.6
...
4.5.2 查看文件的内存映射
当怀疑某个文件被mmap后未释放时,可以查看其映射信息:
crash> vm -p ffff88003a1c2c00
PID: 1234
COMMAND: "nginx"
VMA START END FLAGS FILE
ffff88003a1c4000 7f0000000000 7f0000001000 rw-p /var/log/nginx/access.log
ffff88003a1c4200 7f0000002000 7f0000003000 rw-p /var/log/nginx/error.log
vm -p可以显示每个VMA(虚拟内存区域)的内核结构体地址,结合struct vm_area_struct命令可以查看更详细的信息。
4.6 命令组合实战:文件系统卸载失败分析
当umount命令返回“设备正忙”时,可以使用以下命令组合快速定位问题:
- 查看super_block引用计数:
struct super_block ffff88003a1c2c00 s_count - 遍历inode链表:
list super_block ffff88003a1c2c00 s_inodes -s inode i_count - 查找持有引用的进程:
task -R files ffff88003a1c2c00 - 查看进程的文件描述符:
files ffff88003a1c2c00 - 检查内存映射:
vm ffff88003a1c2c00 | grep /mnt
list命令发现一个inode的i_count为3,进一步使用task命令找到持有该inode的进程,发现是systemd-journald未关闭日志文件句柄。使用kill -HUP重启该进程后,文件系统成功卸载。
4.7 命令对比总结
| 命令 | 核心功能 | 文件系统调试场景 | 常用组合 |
|---|---|---|---|
| struct | 查看结构体内容 | 检查inode/dentry/super_block成员 | 与list组合遍历链表 |
| list | 遍历内核链表 | 统计inode/dentry数量,查找异常节点 | 与struct组合显示节点内容 |
| task | 查看进程信息 | 查找持有文件引用的进程 | 与files命令组合查看文件描述符 |
| kmem | 查看内存分配 | 分析slab缓存泄漏、内存碎片 | 与list组合定位泄漏源 |
| vm | 查看虚拟内存 | 分析mmap文件映射、进程地址空间 | 与task组合查看特定进程 |