客户反应应用在打开/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;
这样,该设备的寄存器地址就可以正常映射和使用了。