使用 ftrace 观测 GPIO 从用户调用到驱动的完整轨迹需要跟踪系统调用、VFS
层、设备驱动等多个层次。以下是详细的观测方案:
1. 准备环境和测试程序
1.1 简单的 GPIO 测试程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <stdio.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <stdint.h> int main () { int fd; void *gpio_map; printf ("GPIO mmap 测试开始...\n" ); fd = open("/dev/gpiomem" , O_RDWR | O_SYNC); if (fd < 0 ) { perror("open /dev/gpiomem failed" ); return -1 ; } printf ("GPIO设备打开成功: fd=%d\n" , fd); gpio_map = mmap(NULL , 4096 , PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0 ); if (gpio_map == MAP_FAILED) { perror("mmap failed" ); close(fd); return -1 ; } printf ("GPIO映射成功: %p\n" , gpio_map); volatile uint32_t *gpio = (volatile uint32_t *)gpio_map; uint32_t value = gpio[0 ]; printf ("GPIO寄存器值: 0x%08x\n" , value); printf ("按回车键继续...\n" ); getchar(); munmap(gpio_map, 4096 ); close(fd); printf ("测试完成\n" ); return 0 ; }
编译运行: 1 2 3 cd ~/codegcc trace_gpio_test.c -o trace_gpio ./trace_gpio
2. 配置 ftrace 跟踪点
2.1 实际可用的跟踪脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #!/bin/bash echo "=== GPIO mmap 完整轨迹跟踪 ===" mount -t debugfs nodev /sys/kernel/debug/tracing 2>/dev/null cd /sys/kernel/debug/tracingecho 0 | sudo tee tracing_on > /dev/nullecho | sudo tee trace > /dev/nullecho | sudo tee set_ftrace_filter > /dev/nullecho | sudo tee set_graph_function > /dev/nullecho function_graph | sudo tee current_tracer > /dev/nullecho "__arm64_sys_mmap" | sudo tee -a set_graph_function > /dev/nullecho "ksys_mmap_pgoff" | sudo tee -a set_graph_function > /dev/nullecho "vm_mmap_pgoff" | sudo tee -a set_graph_function > /dev/nullecho "do_mmap" | sudo tee -a set_graph_function > /dev/nullecho "mmap_region" | sudo tee -a set_graph_function > /dev/nullecho "__mmap_region" | sudo tee -a set_graph_function > /dev/nullecho "rpi_gpiomem_mmap" | sudo tee -a set_graph_function > /dev/nullecho "rpi_gpiomem_open" | sudo tee -a set_graph_function > /dev/nullecho "vm_area_alloc" | sudo tee -a set_graph_function > /dev/nullecho "vma_merge_new_range" | sudo tee -a set_graph_function > /dev/nullecho "vma_link_file" | sudo tee -a set_graph_function > /dev/nullecho "vma_set_page_prot" | sudo tee -a set_graph_function > /dev/nullecho "remap_pfn_range" | sudo tee -a set_graph_function > /dev/nullecho "remap_pfn_range_internal" | sudo tee -a set_graph_function > /dev/nullecho "__pte_offset_map_lock" | sudo tee -a set_graph_function > /dev/nullecho 1 | sudo tee options/func_stack_trace > /dev/nullecho 1 | sudo tee options/sleep-time > /dev/nullecho 1 | sudo tee options/graph-time > /dev/nullecho 1 | sudo tee tracing_on > /dev/nullecho "运行测试程序..." /home/pi/code/trace_gpio & sleep 3echo 0 | sudo tee tracing_on > /dev/nullsudo cat trace > /tmp/gpio_mmap_complete_trace.log echo "跟踪结果保存到: /tmp/gpio_mmap_complete_trace.log" echo "=== GPIO 驱动调用路径 ===" sudo cat trace | grep -A30 -B5 "rpi_gpiomem" | head -80
3. 详细的函数跟踪配置
3.1 完整的已验证函数列表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 #!/bin/bash cd /sys/kernel/debug/tracingecho 0 | sudo tee tracing_on > /dev/nullecho nop | sudo tee current_tracer > /dev/nullecho | sudo tee set_ftrace_filter > /dev/nullecho | sudo tee set_graph_function > /dev/nullecho function_graph | sudo tee current_tracer > /dev/nullecho "__arm64_sys_mmap" | sudo tee -a set_graph_function > /dev/nullecho "ksys_mmap_pgoff" | sudo tee -a set_graph_function > /dev/nullecho "vm_mmap_pgoff" | sudo tee -a set_graph_function > /dev/nullecho "do_mmap" | sudo tee -a set_graph_function > /dev/nullecho "mmap_region" | sudo tee -a set_graph_function > /dev/nullecho "__mmap_region" | sudo tee -a set_graph_function > /dev/nullecho "vm_area_alloc" | sudo tee -a set_graph_function > /dev/nullecho "vma_merge_new_range" | sudo tee -a set_graph_function > /dev/nullecho "vma_link_file" | sudo tee -a set_graph_function > /dev/nullecho "vma_set_page_prot" | sudo tee -a set_graph_function > /dev/nullecho "fget" | sudo tee -a set_graph_function > /dev/nullecho "fput" | sudo tee -a set_graph_function > /dev/nullecho "rpi_gpiomem_mmap" | sudo tee -a set_graph_function > /dev/nullecho "rpi_gpiomem_open" | sudo tee -a set_graph_function > /dev/nullecho "remap_pfn_range" | sudo tee -a set_graph_function > /dev/nullecho "remap_pfn_range_internal" | sudo tee -a set_graph_function > /dev/nullecho "__pte_offset_map_lock" | sudo tee -a set_graph_function > /dev/nullecho "security_mmap_file" | sudo tee -a set_graph_function > /dev/nullecho "security_mmap_addr" | sudo tee -a set_graph_function > /dev/nullecho "cap_mmap_file" | sudo tee -a set_graph_function > /dev/nullecho "cap_mmap_addr" | sudo tee -a set_graph_function > /dev/nullecho 1 | sudo tee options/func_stack_trace > /dev/nullecho 1 | sudo tee options/graph-time > /dev/nullecho 1 | sudo tee tracing_on > /dev/nullecho "跟踪开始,运行测试程序..." /home/pi/code/trace_gpio & sleep 3echo 0 | sudo tee tracing_on > /dev/nullsudo cat trace | head -100
4. 使用 trace-cmd
进行更精细控制
4.1 trace-cmd 配置(针对 GPIO)
1 2 3 4 5 6 sudo apt update sudo apt install trace-cmd trace-cmd --version
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #!/bin/bash echo "使用 trace-cmd 跟踪 GPIO mmap..." timeout 10 sudo trace-cmd record \ -e syscalls:sys_enter_mmap \ -e syscalls:sys_exit_mmap \ -p function_graph \ -g __arm64_sys_mmap \ -g rpi_gpiomem_mmap \ -g remap_pfn_range \ -F /home/pi/code/trace_gpio echo "=== GPIO 相关事件 ===" sudo trace-cmd report | grep -i gpio echo -e "\n=== mmap 系统调用 ===" sudo trace-cmd report | grep -i mmap echo -e "\n=== 函数图分析 ===" sudo trace-cmd report | grep -A10 -B5 "rpi_gpiomem_mmap\|__arm64_sys_mmap"
或者使用更简单的方法: 1 2 sudo 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 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 grep -A20 -B10 "rpi_gpiomem_mmap\|rpi_gpiomem_open" /tmp/gpio_mmap_complete_trace.log
5.2 性能热点分析
分析页表操作频率: 1 2 3 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 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 grep -A10 "remap_pfn_range" /tmp/gpio_mmap_complete_trace.log
## mmap分析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 tail -100 /tmp/gpio_mmap_complete_trace.log 3) 0.593 us | _raw_spin_lock(); 3) 3.037 us | } 3) | __pte_offset_map_lock () { 3) 0.685 us | __rcu_read_lock(); 3) 0.685 us | _raw_spin_lock(); 3) 3.019 us | } 3) | __arm64_sys_mmap () { 3) | ksys_mmap_pgoff () { 3) | fget () { 3) 0.574 us | __rcu_read_lock(); 3) 0.667 us | __rcu_read_unlock(); 3) 3.056 us | } 3) | vm_mmap_pgoff () { 3) | security_mmap_file () { 3) 0.556 us | cap_mmap_file(); 3) 1.870 us | } 3) 0.722 us | down_write_killable(); 3) | do_mmap () { 3) | __get_unmapped_area () { 3) | mm_get_unmapped_area_vmflags () { 3) | generic_get_unmapped_area_topdown () { 3) 1.352 us | vm_unmapped_area(); 3) 2.574 us | } 3) 3.889 us | } 3) | security_mmap_addr () { 3) 0.593 us | cap_mmap_addr(); 3) 1.852 us | } 3) 7.463 us | } 3) | memfd_file_seals_ptr () { 3) 0.556 us | shmem_mapping(); 3) 1.834 us | } 3) 0.685 us | path_noexec(); 3) | mmap_region () { 3) | __mmap_region () { 3) 0.666 us | may_expand_vm(); 3) 0.648 us | vms_clean_up_area(); 3) | vma_merge_new_range () { 3) 0.592 us | can_vma_merge_right(); 3) 1.870 us | } 3) | vm_area_alloc () { 3) 0.667 us | kmem_cache_alloc_noprof(); 3) 0.685 us | kmem_cache_alloc_noprof(); 3) 0.574 us | __init_rwsem(); 3) 4.463 us | } 3) 0.574 us | vm_get_page_prot(); 3) 0.629 us | kmem_cache_alloc_noprof(); 3) | ext4_file_mmap () { 3) | touch_atime () { 3) | atime_needs_update () { 3) 0.574 us | make_vfsuid(); 3) 0.555 us | make_vfsgid(); 3) 3.000 us | } 3) 4.297 us | } 3) 5.408 us | } 3) 0.611 us | down_write(); 3) 0.703 us | up_write(); 3) | call_rcu () { 3) | __call_rcu_common.constprop.0 () { 3) 0.574 us | rcu_segcblist_enqueue(); 3) 1.870 us | } 3) 3.186 us | } 3) | vma_link_file () { 3) 0.611 us | down_write(); 3) 0.796 us | vma_interval_tree_insert(); 3) 0.593 us | up_write(); 3) 4.481 us | } 3) 0.574 us | perf_event_mmap(); 3) 0.574 us | vms_complete_munmap_vmas(); 3) | vma_set_page_prot () { 3) 0.667 us | vm_get_page_prot(); 3) 0.648 us | vma_wants_writenotify(); 3) 2.907 us | } 3) + 37.925 us | } 3) + 39.185 us | } 3) + 52.093 us | } 3) 0.574 us | up_write(); 3) + 58.315 us | } 3) 0.574 us | fput(); 3) + 65.111 us | } 3) + 66.555 us | } 3) | __pte_offset_map_lock () { 3) 0.667 us | __rcu_read_lock(); 3) 0.686 us | _raw_spin_lock(); 3) 2.982 us | } 3) | __pte_offset_map_lock () { 3) 0.685 us | __rcu_read_lock(); 3) 0.666 us | _raw_spin_lock(); 3) 2.963 us | } 3) | __pte_offset_map_lock () { 3) 0.574 us | __rcu_read_lock(); 3) 0.593 us | _raw_spin_lock(); 3) 3.074 us | } 3) | __pte_offset_map_lock () { 3) 0.575 us | __rcu_read_lock(); 3) 0.592 us | _raw_spin_lock(); 3) 3.093 us | } 3) | __pte_offset_map_lock () { 3) 0.575 us | __rcu_read_lock(); 3) 0.574 us | _raw_spin_lock(); 3) 2.963 us | }
当用户调用mmap()的时候,内核会进行如下处理 1.
在进程的虚拟空间查找一块VMA 2. 将这块VMA进行映射 3.
如果设备驱动程序或者文件系统的file_operations定义了mmap()操作,则调用它。
4. 将这个VMA插入进程的VMA链表中
从 ftrace 输出可以清晰地验证 mmap 处理的四个步骤:
1. 查找 VMA (虚拟内存区域)
1 2 3 4 5 6 7 8 9 __get_unmapped_area () { mm_get_unmapped_area_vmflags () { generic_get_unmapped_area_topdown () { vm_unmapped_area(); } } } security_mmap_addr();
2. 分配和设置 VMA
1 2 3 4 5 6 7 8 vm_area_alloc () { kmem_cache_alloc_noprof(); } vm_get_page_prot(); vma_set_page_prot () { vm_get_page_prot(); vma_wants_writenotify(); }
3. 调用驱动/文件系统的 mmap
操作
1 2 3 4 5 6 7 8 9 ext4_file_mmap () { touch_atime () { atime_needs_update () { make_vfsuid(); make_vfsgid(); } } }
4. 将 VMA 插入进程链表
1 2 3 4 5 vma_link_file () { down_write(); vma_interval_tree_insert(); up_write(); }
关键证据总结:
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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 unsigned long __get_unmapped_area(struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) { if (flags & MAP_FIXED) return addr; if (addr) return mm_get_unmapped_area_vmflags(file, addr, len, pgoff, flags); else return mm_get_unmapped_area(file, addr, len, pgoff, flags); } unsigned long mm_get_unmapped_area (struct file *file, unsigned long addr, unsigned long len, unsigned long pgoff, unsigned long flags) { if (current->mm->get_unmapped_area) return current->mm->get_unmapped_area(file, addr, len, pgoff, flags); return generic_get_unmapped_area_topdown(file, addr, len, pgoff, flags); }
关键函数调用链: 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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) { struct mm_struct *mm = current->mm; struct vm_area_struct *vma ; vma = vm_area_alloc(mm); if (!vma) return -ENOMEM; vma->vm_start = addr; vma->vm_end = addr + len; vma->vm_flags = vm_flags; vma->vm_page_prot = vm_get_page_prot(vm_flags); vma->vm_pgoff = pgoff; if (file) { vma->vm_file = get_file(file); error = call_mmap(file, vma); if (error) goto unmap_and_free_vma; } vma_set_page_prot(vma); }
VMA 分配细节: 1 2 3 4 5 6 7 8 9 10 11 struct vm_area_struct *vm_area_alloc (struct mm_struct *mm) { struct vm_area_struct *vma = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL); if (vma) { vma_init(vma, mm); vma->vm_rb = RB_CLEAR_NODE; } return vma; }
3. 调用驱动/文件系统的 mmap
操作
源码路径: include/linux/fs.h
1 2 3 4 5 6 static inline int call_mmap (struct file *file, struct vm_area_struct *vma) { return file->f_op->mmap(file, vma); }
对于 GPIO 驱动: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static int bcm2835_gpiomem_mmap (struct file *file, struct vm_area_struct *vma) { struct bcm2835_gpiomem_dev *dev = file->private_data; 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 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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static void __vma_link(struct mm_struct *mm, struct vm_area_struct *vma, struct vm_area_struct *prev, struct rb_node **rb_link, struct rb_node *rb_parent) { __vma_link_rb(mm, vma, rb_link, rb_parent); __vma_link_list(mm, vma, prev); } static void __vma_link_file(struct vm_area_struct *vma){ struct file *file = vma->vm_file; if (file) { struct address_space *mapping = file->f_mapping; vma_interval_tree_insert(vma, &mapping->i_mmap); flush_dcache_mmap_lock(mapping); mapping->i_mmap_writable++; flush_dcache_mmap_unlock(mapping); } }
完整的 mmap_region 流程: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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_link(mm, vma, prev, rb_link, rb_parent); if (file) __vma_link_file(vma); perf_event_mmap(vma); return addr; }
完整的调用链总结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 用户空间: mmap() 系统调用 ↓ __arm64_sys_mmap (arch/arm64/kernel/sys.c) ↓ ksys_mmap_pgoff (mm/mmap.c) ↓ vm_mmap_pgoff (mm/mmap.c) ↓ do_mmap (mm/mmap.c) ↓ __get_unmapped_area() // 步骤1:查找空闲VMA区域 ↓ mmap_region (mm/mmap.c) ↓ vm_area_alloc() // 步骤2:分配VMA结构体 ↓ call_mmap() → file->f_op->mmap() // 步骤3:调用驱动mmap操作 ↓ remap_pfn_range() // 建立页表映射 ↓ vma_link() // 步骤4:插入VMA链表 ↓ __vma_link_rb() // 插入红黑树 ↓ __vma_link_list() // 插入双向链表
这个流程清晰地展示了 Linux 内核如何处理 mmap
请求:从虚拟地址空间的查找,到 VMA
结构的分配和设置,再到驱动特定映射操作的执行,最后将 VMA
整合到进程的内存管理数据结构中。