经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 数据库运维 » LinuxShell » 查看文章
通过devmem访问物理地址
来源:cnblogs  作者:tinylaker  时间:2018/10/21 20:13:23  对本文有异议

目录

1.写在前面

最近在调试时需要在用户层访问物理内存,发现应用层可以使用devmem工具访问物理地址。查看源码,实际上是对/dev/mem操作,通过mmap可以将物理地址映射到用户空间的虚拟地址上,在用户空间完成对设备寄存器的读写。藉由此原因,想深入理解下mmap的具体实现。

2.devmem使用

devmem的配置,可以在busybox的杂项中找到。

  1. CONFIG_USER_BUSYBOX_DEVMEM:
  2. devmem is a small program that reads and writes from physical
  3. memory using /dev/mem.
  4. Symbol: USER_BUSYBOX_DEVMEM [=y]
  5. Prompt: devmem
  6. Defined at ../user/busybox/busybox-1.23.2/miscutils/Kconfig:216
  7. Depends on: USER_BUSYBOX_BUSYBOX
  8. Location:
  9. -> BusyBox (USER_BUSYBOX_BUSYBOX [=y])
  10. -> Miscellaneous Utilities
  1. # busybox devmem
  2. BusyBox v1.23.2 (2018-08-02 11:08:33 CST) multi-call binary.
  3. Usage: devmem ADDRESS [WIDTH [VALUE]]
  4. Read/write from physical address
  5. ADDRESS Address to act upon
  6. WIDTH Width (8/16/...)
  7. VALUE Data to be written
参数 详细说明
ADDRESS 需要进行读写访问的物理地址
WIDTH 访问数据类型
VALUE 如果是读操作省略;如果是写操作,表示需要写入的数据

基本测试用法

  1. # devmem 0x44e07134 16
  2. 0xFFEF
  3. # devmem 0x44e07134 32
  4. 0xFFFFFFEF
  5. # devmem 0x44e07134 8
  6. 0xEF

3.应用层

接口定义如下:

  1. #include <sys/mman.h>
  2. void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  3. int munmap(void *addr, size_t length);

详细参数如下:

参数 详细说明
addr 需要映射的虚拟内存地址;如果为NULL,系统会自动选定。映射成功后返回该地址
length 需要映射多大的数据量
prot 描述映射区域内存保护方式,包括:PROT_EXEC、PROT_READ、PROT_WRITE、PROT_NONE.
flags 描述映射区域的特性,比如是否对其他进程共享,是否建立匿名映射,是否创建私有的cow.
fd 要映射到内存中的文件描述符
offset 文件映射的偏移量

devmem的实现为例,

如果argv[3]存在,需要映射读写权限;如果不存在,只需要映射读权限。

  1. map_base = mmap(NULL,
  2. mapped_size,
  3. argv[3] ? (PROT_READ | PROT_WRITE) : PROT_READ,
  4. MAP_SHARED,
  5. fd,
  6. target & ~(off_t)(page_size - 1));

4.内核层

因篇幅有限,这里不在表述glibc、系统调用的关系,直接查找系统调用的代码实现。

arch/arm/include/uapi/asm/unistd.h

  1. #define __NR_OABI_SYSCALL_BASE 0x900000
  2. #if defined(__thumb__) || defined(__ARM_EABI__)
  3. #define __NR_SYSCALL_BASE 0
  4. #else
  5. #define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE
  6. #endif
  7. #define __NR_mmap (__NR_SYSCALL_BASE+ 90)
  8. #define __NR_munmap (__NR_SYSCALL_BASE+ 91)
  9. #define __NR_mmap2 (__NR_SYSCALL_BASE+192)

arch/arm/kernel/entry-common.S

  1. /*=============================================================================
  2. * SWI handler
  3. *-----------------------------------------------------------------------------
  4. */
  5. .align 5
  6. ENTRY(vector_swi)
  7. #ifdef CONFIG_CPU_V7M
  8. v7m_exception_entry
  9. #else
  10. sub sp, sp, #S_FRAME_SIZE
  11. stmia sp, {r0 - r12} @ Calling r0 - r12
  12. ARM( add r8, sp, #S_PC )
  13. ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr
  14. THUMB( mov r8, sp )
  15. THUMB( store_user_sp_lr r8, r10, S_SP ) @ calling sp, lr
  16. mrs r8, spsr @ called from non-FIQ mode, so ok.
  17. str lr, [sp, #S_PC] @ Save calling PC
  18. str r8, [sp, #S_PSR] @ Save CPSR
  19. str r0, [sp, #S_OLD_R0] @ Save OLD_R0
  20. #endif
  21. zero_fp
  22. #ifdef CONFIG_ALIGNMENT_TRAP
  23. ldr ip, __cr_alignment
  24. ldr ip, [ip]
  25. mcr p15, 0, ip, c1, c0 @ update control register
  26. #endif
  27. enable_irq
  28. ...
  1. /*
  2. * Note: off_4k (r5) is always units of 4K. If we can't do the requested
  3. * offset, we return EINVAL.
  4. */
  5. sys_mmap2:
  6. #if PAGE_SHIFT > 12
  7. tst r5, #PGOFF_MASK
  8. moveq r5, r5, lsr #PAGE_SHIFT - 12
  9. streq r5, [sp, #4]
  10. beq sys_mmap_pgoff
  11. mov r0, #-EINVAL
  12. mov pc, lr
  13. #else
  14. str r5, [sp, #4]
  15. b sys_mmap_pgoff
  16. #endif
  17. ENDPROC(sys_mmap2)

arch/arm/kernel/calls.S

  1. /* 90 */ CALL(OBSOLETE(sys_old_mmap)) /* used by libc4 */
  2. CALL(sys_munmap)
  3. ...
  4. /* 190 */ CALL(sys_vfork)
  5. CALL(sys_getrlimit)
  6. CALL(sys_mmap2)

include/linux/syscalls.h

  1. asmlinkage long sys_mmap_pgoff(unsigned long addr, unsigned long len,
  2. unsigned long prot, unsigned long flags,
  3. unsigned long fd, unsigned long pgoff);

搜索mmap_pgoff函数定义,位于mm/mmap.c,省略一些我们不太关心的代码。

  1. SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,
  2. unsigned long, prot, unsigned long, flags,
  3. unsigned long, fd, unsigned long, pgoff)
  4. {
  5. struct file *file = NULL;
  6. unsigned long retval = -EBADF;
  7. if (!(flags & MAP_ANONYMOUS)) {
  8. audit_mmap_fd(fd, flags);
  9. file = fget(fd);
  10. if (!file)
  11. goto out;
  12. if (is_file_hugepages(file))
  13. len = ALIGN(len, huge_page_size(hstate_file(file)));
  14. retval = -EINVAL;
  15. if (unlikely(flags & MAP_HUGETLB && !is_file_hugepages(file)))
  16. goto out_fput;
  17. }
  18. ...
  19. flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE);
  20. retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
  21. out_fput:
  22. if (file)
  23. fput(file);
  24. out:
  25. return retval;
  26. }

mm/util.c

  1. unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr,
  2. unsigned long len, unsigned long prot,
  3. unsigned long flag, unsigned long pgoff)
  4. {
  5. unsigned long ret;
  6. struct mm_struct *mm = current->mm;
  7. unsigned long populate;
  8. ret = security_mmap_file(file, prot, flag);
  9. if (!ret) {
  10. down_write(&mm->mmap_sem);
  11. ret = do_mmap_pgoff(file, addr, len, prot, flag, pgoff,
  12. &populate);
  13. up_write(&mm->mmap_sem);
  14. if (populate)
  15. mm_populate(ret, populate);
  16. }
  17. return ret;
  18. }

vm_area_struct结构用来描述进程的虚拟内存区域,和进程的内存描述符mm_struct关联,通过链表和红黑树进行管理。

  1. unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
  2. unsigned long len, unsigned long prot,
  3. unsigned long flags, unsigned long pgoff,
  4. unsigned long *populate)
  5. {
  6. struct mm_struct * mm = current->mm;
  7. vm_flags_t vm_flags;
  8. *populate = 0;
  9. //搜索进程地址空间,查找一个可以使用的线性地址区间,len指定区间的长度,非空addr参数指定从哪个地址开始进行查找
  10. addr = get_unmapped_area(file, addr, len, pgoff, flags);
  11. vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |
  12. mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
  13. //file指针不为空,建立从文件到虚拟空间的映射,根据flags标志设定访问权限。
  14. if (file) {
  15. struct inode *inode = file_inode(file);
  16. switch (flags & MAP_TYPE) {
  17. case MAP_SHARED:
  18. vm_flags |= VM_SHARED | VM_MAYSHARE;
  19. break;
  20. ...
  21. } else { //file指针为空,仅创建虚拟空间,不做映射。
  22. switch (flags & MAP_TYPE) {
  23. case MAP_SHARED:
  24. pgoff = 0;
  25. vm_flags |= VM_SHARED | VM_MAYSHARE;
  26. break;
  27. case MAP_PRIVATE:
  28. pgoff = addr >> PAGE_SHIFT;
  29. break;
  30. }
  31. //创建虚拟空间,并进行映射。
  32. addr = mmap_region(file, addr, len, vm_flags, pgoff);
  33. return addr;
  34. }
  1. unsigned long mmap_region(struct file *file, unsigned long addr,
  2. unsigned long len, vm_flags_t vm_flags, unsigned long pgoff)
  3. {
  4. ...
  5. //检查是否需要对该虚拟空间进行扩容
  6. if (!may_expand_vm(mm, len >> PAGE_SHIFT)) {
  7. unsigned long nr_pages;
  8. /*
  9. * MAP_FIXED may remove pages of mappings that intersects with
  10. * requested mapping. Account for the pages it would unmap.
  11. */
  12. if (!(vm_flags & MAP_FIXED))
  13. return -ENOMEM;
  14. nr_pages = count_vma_pages_range(mm, addr, addr + len);
  15. if (!may_expand_vm(mm, (len >> PAGE_SHIFT) - nr_pages))
  16. return -ENOMEM;
  17. }
  18. //扫描当前进程地址空间的vm_area_struct结构相关的红黑树,确定线性区域的位置,如果找到一个区域,说明addr所在的虚拟区间已经被使用,表示已经被映射;因此需要调用do_munmap把这个区域从进程地址空间中撤销。
  19. munmap_back:
  20. if (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) {
  21. if (do_munmap(mm, addr, len))
  22. return -ENOMEM;
  23. goto munmap_back;
  24. }
  25. vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL);
  26. if (vma)
  27. goto out;
  28. //分配映射虚拟空间
  29. vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
  30. if (!vma) {
  31. error = -ENOMEM;
  32. goto unacct_error;
  33. }
  34. vma->vm_mm = mm;
  35. vma->vm_start = addr;
  36. vma->vm_end = addr + len;
  37. vma->vm_flags = vm_flags;
  38. vma->vm_page_prot = vm_get_page_prot(vm_flags);
  39. vma->vm_pgoff = pgoff;
  40. INIT_LIST_HEAD(&vma->anon_vma_chain);
  41. if (file) {
  42. if (vm_flags & VM_DENYWRITE) {
  43. error = deny_write_access(file);
  44. if (error)
  45. goto free_vma;
  46. }
  47. vma->vm_file = get_file(file);
  48. error = file->f_op->mmap(file, vma);
  49. if (error)
  50. goto unmap_and_free_vma;
  51. /* Can addr have changed??
  52. *
  53. * Answer: Yes, several device drivers can do it in their
  54. * f_op->mmap method. -DaveM
  55. * Bug: If addr is changed, prev, rb_link, rb_parent should
  56. * be updated for vma_link()
  57. */
  58. WARN_ON_ONCE(addr != vma->vm_start);
  59. addr = vma->vm_start;
  60. vm_flags = vma->vm_flags;
  61. } else if (vm_flags & VM_SHARED) {
  62. error = shmem_zero_setup(vma);
  63. if (error)
  64. goto free_vma;
  65. }
  66. ...
  67. }

mmap_region函数实现中的file->f_op->mmap(file, vma),对应mmap_mem,位于/drivers/char/mem.c,代码如下:

  1. static const struct file_operations mem_fops = {
  2. .llseek = memory_lseek,
  3. .read = read_mem,
  4. .write = write_mem,
  5. .mmap = mmap_mem,
  6. .open = open_mem,
  7. .get_unmapped_area = get_unmapped_area_mem,
  8. };
  9. static int mmap_mem(struct file *file, struct vm_area_struct *vma)
  10. {
  11. size_t size = vma->vm_end - vma->vm_start;
  12. if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size))
  13. return -EINVAL;
  14. if (!private_mapping_ok(vma))
  15. return -ENOSYS;
  16. if (!range_is_allowed(vma->vm_pgoff, size))
  17. return -EPERM;
  18. if (!phys_mem_access_prot_allowed(file, vma->vm_pgoff, size,
  19. &vma->vm_page_prot))
  20. return -EINVAL;
  21. vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
  22. size,
  23. vma->vm_page_prot);
  24. vma->vm_ops = &mmap_mem_ops;
  25. /* Remap-pfn-range will mark the range VM_IO */
  26. if (remap_pfn_range(vma,
  27. vma->vm_start,
  28. vma->vm_pgoff,
  29. size,
  30. vma->vm_page_prot)) {
  31. return -EAGAIN;
  32. }
  33. return 0;
  34. }

remap_pfn_range函数建立物理地址与虚拟地址页表。其中vm_pgoff代表要映射的物理地址,vm_page_prot代表该页的权限。这些参数和mmap的参数相互对应,现在就可以通过应用层访问物理地址了。

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号