/dev/mem mmap失败 -- 使用ftrace function_graph来查看代码运行路径

本文详述了一位工程师在4.4和4.19内核下,针对应用通过/dev/mem使用mmap映射内存时遇到的问题。通过ftrace功能图跟踪,发现在4.19内核中mmap_mem函数未被调用,原因是页面大小选择导致的offset_in_page错误。解决方案是调整映射地址以匹配64k页面对齐,从而成功映射设备寄存器地址。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

客户反应应用在打开/dev/mem,然后通过mmap来映射内存的时候,mmap会失效。还有一个信息就是该应用在4.4内核运行正常,在4.19内核运行失效

我对/dev/mem这个设备代码不是很了解,最多也就是直到代码在/driver/char/mem.c, file operations里面mmap对应的函数是mmap_mem.

我在4.19内核的mmap_mem加了printk,但很不幸,printk的打印并没有出现,说明没有运行mmap_mem函数。那么,从用户空间调用mmap到内核设备的mmap(本例是mmap_mem)这一段的代码路径是什么呢?这部分代码我不熟,但我想到了function graph.

1,首先,我先使用4.4内核查看一下:

trace-cmd list -f | grep mmap --- 系统调用函数的符号为sys_map

trace-cmd start -p function_graph -g sys_mmap;.user_process;trace-cmd stop

trace-cmd show

得到log如下:

1)               |  sys_mmap() {
 1)               |    SyS_mmap_pgoff() {
 1)   0.083 us    |      __audit_mmap_fd();
 1)               |      fget() {
 1)   0.083 us    |        __fget();
 1)   0.708 us    |      }
 1)   0.063 us    |      is_file_shm_hugepages();
 1)               |      vm_mmap_pgoff() {
 1)               |        security_mmap_file() {
 1)   0.062 us    |          cap_mmap_file();
 1)   0.062 us    |          extend_mmap_file();
 1)   0.062 us    |          ima_file_mmap();
 1)   2.145 us    |        }
 1)               |        down_write() {
 1)   0.063 us    |          _cond_resched();
 1)   0.708 us    |        }
 1)               |        do_mmap() {
 1)               |          get_unmapped_area() {
 1)               |            arch_get_unmapped_area_topdown() {
 1)   0.229 us    |              unmapped_area_topdown();
 1)   0.896 us    |            }
 1)               |            security_mmap_addr() {
 1)   0.063 us    |              cap_mmap_addr();
 1)   0.063 us    |              extend_mmap_addr();
 1)   1.333 us    |            }
 1)   4.521 us    |          }
 1)   0.062 us    |          path_noexec();
 1)               |          mmap_region() {
 1)   0.042 us    |            is_file_shm_hugepages();
 1)               |            vma_merge() {
 1)   0.063 us    |              can_vma_merge_before();
 1)   0.708 us    |            }
 1)               |            kmem_cache_alloc() {
 1)   0.083 us    |              _cond_resched();
 1)   0.771 us    |            }
 1)               |            mmap_mem() {

....

可以看到调用顺序为sys_mmap -- SyS_mmap_pgoff -- vm_mmap_pgoff -- do_mmap -- mmap_region -- call_mmap -- file->f_op->mmap --mmap_mem

2,得到了代码的调用路径,我们开分析一下4.19

trace-cmd start -p function_graph -g __arm64_sys_mmap;user-process;trace-cmd stop

trace-cmd show

从结果中没有找到 mmap_mem,也验证了代码并没有走到函数mmap_mem。但是有了调用路径,就可以使用printk来分析代码了。

3,最后发现问题如下:

1)代码

SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
        unsigned long, prot, unsigned long, flags,
        unsigned long, fd, unsigned long, off)
{
    if (offset_in_page(off) != 0)
        return -EINVAL;

    return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}

在4.19中offset_in_page返回值不为0,发生错误,直接返回,没有调用ksys_mmap_pgoff,进一步分析offset_in_page,发现其与PAGE_MASK有关,而PAGE_MASK的值为

~PAGE_SIZE-1,PAGE_SIZE为1<<PAGE_SHIFT, 比较了一下我们项目的PAGE_SHIFT, 由于page size选择了4k和64k,导致了page shift分别为12和16,进而导致了mmap结果的不同。

4,总结

ftrace对实时应用的分析是一把利器,同时,对代码的阅读也有很好的帮助,特别是使用function graph,大家可以多试一下。

5,新发现

如果系统为64k页面,如何避免这个问题。

1)看一下应用程序的代码

map_addr = mmap(0, MAP_SIZE, PROT_READ|PROT_WRITE,MAP_SHARED,fd,(0x28006000) & ~MAP_MASK);

    if(map_addr == MAP_FAILED)
        return -1;
    virt_addr = map_addr +((0x28006000) & MAP_MASK);

    if(lednum==5)
    {
        gpio_dr_addr=virt_addr+0x0c;

....

要映射的物理地址为0x28006000,该地址4k页面可以对齐,但64k页面不能对齐,所以在offset_in_page中返回无效值。那么我们把需要映射的物理地址改为64k页面对齐,即:

map_addr = mmap(0, MAP_SIZE, PROT_READ|PROT_WRITE,MAP_SHARED,fd,(0x28000000) & ~MAP_MASK);

使用时再加上0x6000的偏移量,即:

virt_addr = map_addr + 0x6000;

这样,该设备的寄存器地址就可以正常映射和使用了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值