【树莓派学习】002 gpio trace
使用 ftrace 观测 GPIO 从用户调用到驱动的完整轨迹需要跟踪系统调用、VFS 层、设备驱动等多个层次。以下是详细的观测方案:
1. 准备环境和测试程序
1.1 简单的 GPIO 测试程序
1 | // trace_gpio_test.c |
编译运行: 1
2
3cd ~/code
gcc trace_gpio_test.c -o trace_gpio
./trace_gpio
2. 配置 ftrace 跟踪点
2.1 实际可用的跟踪脚本
1 |
|
3. 详细的函数跟踪配置
3.1 完整的已验证函数列表
1 |
|
4. 使用 trace-cmd 进行更精细控制
4.1 trace-cmd 配置(针对 GPIO)
1 | # 安装 trace-cmd |
1 |
|
或者使用更简单的方法: 1
2sudo trace-cmd record -e 'syscalls:*mmap*' -p function -F /home/pi/code/trace_gpio
sudo trace-cmd report
5. 分析 ftrace 输出日志
5.1 关键路径识别方法
根据 /tmp/gpio_mmap_complete_trace.log 分析 GPIO mmap
完整轨迹:
1. 识别系统调用入口 1
2# 查找 mmap 系统调用开始
grep -n "__arm64_sys_mmap\|ksys_mmap" /tmp/gpio_mmap_complete_trace.log
2. 跟踪内存管理调用链 1
2# 查看内存管理核心函数
grep -A10 -B5 "do_mmap\|mmap_region\|__mmap_region" /tmp/gpio_mmap_complete_trace.log
3. 定位驱动特定函数 1
2# 查找 GPIO 驱动函数
grep -A20 -B10 "rpi_gpiomem_mmap\|rpi_gpiomem_open" /tmp/gpio_mmap_complete_trace.log
5.2 性能热点分析
分析页表操作频率: 1
2
3# 统计 __pte_offset_map_lock 调用次数和总耗时
grep "__pte_offset_map_lock" /tmp/gpio_mmap_complete_trace.log | wc -l
grep "__pte_offset_map_lock" /tmp/gpio_mmap_complete_trace.log | awk '{sum += $3} END {print "总耗时:", sum, "us"}'
识别内存分配瓶颈: 1
2# 查看 VMA 分配耗时
grep -A5 "vm_area_alloc\|kmem_cache_alloc" /tmp/gpio_mmap_complete_trace.log
5.3 调用深度分析
重建完整调用路径: 1
2# 提取关键函数的时间线
awk '/__arm64_sys_mmap/,/rpi_gpiomem_mmap/' /tmp/gpio_mmap_complete_trace.log | head -50
验证驱动映射成功: 1
2# 检查 remap_pfn_range 是否执行
grep -A10 "remap_pfn_range" /tmp/gpio_mmap_complete_trace.log
1 | tail -100 /tmp/gpio_mmap_complete_trace.log |
当用户调用mmap()的时候,内核会进行如下处理 1. 在进程的虚拟空间查找一块VMA 2. 将这块VMA进行映射 3. 如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,则调用它。 4. 将这个VMA插入进程的VMA链表中
从 ftrace 输出可以清晰地验证 mmap 处理的四个步骤:
1. 查找 VMA (虚拟内存区域)
1 | # 在进程虚拟空间查找空闲区域 |
2. 分配和设置 VMA
1 | vm_area_alloc() { # 分配 VMA 结构体 |
3. 调用驱动/文件系统的 mmap 操作
1 | ext4_file_mmap() { # 文件系统的 mmap 操作 |
4. 将 VMA 插入进程链表
1 | vma_link_file() { # 将 VMA 链接到进程的 VMA 链表 |
关键证据总结:
- VMA 查找:
__get_unmapped_area()和vm_unmapped_area()负责在进程地址空间找到合适区域 - VMA 分配:
vm_area_alloc()明确分配了 VMA 结构体 - 驱动 mmap 调用:
ext4_file_mmap()显示了文件操作的具体 mmap 实现(在你的 GPIO 案例中应该是rpi_gpiomem_mmap) - VMA 链表插入:
vma_link_file()和vma_interval_tree_insert()完成了 VMA 到进程数据结构的链接
这个 trace 完美印证了 mmap 的四个处理步骤。
1. 在进程虚拟空间查找 VMA
源码路径: mm/mmap.c
1 | /* mm/mmap.c */ |
关键函数调用链: 1
2
3
4
5__arm64_sys_mmap
→ ksys_mmap_pgoff
→ vm_mmap_pgoff
→ do_mmap
→ __get_unmapped_area // 步骤1:查找空闲VMA区域
2. 分配和设置 VMA
源码路径: mm/mmap.c -
mmap_region() 函数
1 | /* mm/mmap.c */ |
VMA 分配细节: 1
2
3
4
5
6
7
8
9
10
11/* mm/vmalloc.c */
struct vm_area_struct *vm_area_alloc(struct mm_struct *mm)
{
// 从 slab 分配器分配 VMA 结构体
struct vm_area_struct *vma = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
if (vma) {
vma_init(vma, mm); // 初始化 VMA
vma->vm_rb = RB_CLEAR_NODE;
}
return vma;
}
3. 调用驱动/文件系统的 mmap 操作
源码路径: include/linux/fs.h
1 | /* include/linux/fs.h */ |
对于 GPIO 驱动: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/* drivers/char/broadcom/bcm2835-gpiomem.c */
static int bcm2835_gpiomem_mmap(struct file *file, struct vm_area_struct *vma)
{
struct bcm2835_gpiomem_dev *dev = file->private_data;
// 设置 VMA 标志
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
// 关键:建立物理地址到虚拟地址的映射
return remap_pfn_range(vma,
vma->vm_start,
dev->phys_addr >> PAGE_SHIFT, // 物理页帧号
vma->vm_end - vma->vm_start,
vma->vm_page_prot);
}
remap_pfn_range 内部实现: 1
2
3
4
5
6
7/* mm/memory.c */
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
unsigned long pfn, unsigned long size, pgprot_t prot)
{
// 建立页表映射
return remap_pfn_range_internal(vma, addr, pfn, size, prot);
}
4. 将 VMA 插入进程 VMA 链表
源码路径: mm/mmap.c -
__vma_link() 和 __vma_link_file()
1 | /* mm/mmap.c */ |
完整的 mmap_region 流程: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/* mm/mmap.c */
unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, vm_flags_t vm_flags, unsigned long pgoff,
struct list_head *uf)
{
// ... 前面的 VMA 分配和 mmap 调用
// 步骤4:将 VMA 插入进程数据结构
vma_link(mm, vma, prev, rb_link, rb_parent);
// 如果是文件映射,链接到文件的 VMA 树
if (file)
__vma_link_file(vma);
// 通知内存管理子系统
perf_event_mmap(vma);
return addr;
}
完整的调用链总结
1 | 用户空间: mmap() 系统调用 |
这个流程清晰地展示了 Linux 内核如何处理 mmap 请求:从虚拟地址空间的查找,到 VMA 结构的分配和设置,再到驱动特定映射操作的执行,最后将 VMA 整合到进程的内存管理数据结构中。


