1.写在前面

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

2.devmem使用

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

CONFIG_USER_BUSYBOX_DEVMEM:                                       

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

基本测试用法

# devmem 0x44e07134 16
0xFFEF
# devmem 0x44e07134 32
0xFFFFFFEF
# devmem 0x44e07134 8
0xEF

3.应用层

接口定义如下:

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
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]存在,需要映射读写权限;如果不存在,只需要映射读权限。

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

4.内核层

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

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

#define __NR_OABI_SYSCALL_BASE  0x900000

#if defined(__thumb__) || defined(__ARM_EABI__)
#define __NR_SYSCALL_BASE 0
#else
#define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE
#endif #define __NR_mmap (__NR_SYSCALL_BASE+ 90)
#define __NR_munmap (__NR_SYSCALL_BASE+ 91) #define __NR_mmap2 (__NR_SYSCALL_BASE+192)

arch/arm/kernel/entry-common.S

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

arch/arm/kernel/calls.S

/* 90 */    CALL(OBSOLETE(sys_old_mmap))    /* used by libc4 */
CALL(sys_munmap)
...
/* 190 */ CALL(sys_vfork)
CALL(sys_getrlimit)
CALL(sys_mmap2)

include/linux/syscalls.h

asmlinkage long sys_mmap_pgoff(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff);

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

SYSCALL_DEFINE6(mmap_pgoff, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, pgoff)
{
struct file *file = NULL;
unsigned long retval = -EBADF; if (!(flags & MAP_ANONYMOUS)) {
audit_mmap_fd(fd, flags);
file = fget(fd);
if (!file)
goto out;
if (is_file_hugepages(file))
len = ALIGN(len, huge_page_size(hstate_file(file)));
retval = -EINVAL;
if (unlikely(flags & MAP_HUGETLB && !is_file_hugepages(file)))
goto out_fput;
}
... flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); retval = vm_mmap_pgoff(file, addr, len, prot, flags, pgoff);
out_fput:
if (file)
fput(file);
out:
return retval;
}

mm/util.c

unsigned long vm_mmap_pgoff(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flag, unsigned long pgoff)
{
unsigned long ret;
struct mm_struct *mm = current->mm;
unsigned long populate; ret = security_mmap_file(file, prot, flag);
if (!ret) {
down_write(&mm->mmap_sem);
ret = do_mmap_pgoff(file, addr, len, prot, flag, pgoff,
&populate);
up_write(&mm->mmap_sem);
if (populate)
mm_populate(ret, populate);
}
return ret;
}

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

unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
unsigned long len, unsigned long prot,
unsigned long flags, unsigned long pgoff,
unsigned long *populate)
{ struct mm_struct * mm = current->mm;
vm_flags_t vm_flags; *populate = 0; //搜索进程地址空间,查找一个可以使用的线性地址区间,len指定区间的长度,非空addr参数指定从哪个地址开始进行查找
addr = get_unmapped_area(file, addr, len, pgoff, flags); vm_flags = calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) |
mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC; //file指针不为空,建立从文件到虚拟空间的映射,根据flags标志设定访问权限。
if (file) {
struct inode *inode = file_inode(file); switch (flags & MAP_TYPE) {
case MAP_SHARED:
vm_flags |= VM_SHARED | VM_MAYSHARE;
break;
...
} else { //file指针为空,仅创建虚拟空间,不做映射。
switch (flags & MAP_TYPE) {
case MAP_SHARED:
pgoff = 0;
vm_flags |= VM_SHARED | VM_MAYSHARE;
break;
case MAP_PRIVATE:
pgoff = addr >> PAGE_SHIFT;
break;
} //创建虚拟空间,并进行映射。
addr = mmap_region(file, addr, len, vm_flags, pgoff); return addr;
}
unsigned long mmap_region(struct file *file, unsigned long addr,
unsigned long len, vm_flags_t vm_flags, unsigned long pgoff)
{
...
//检查是否需要对该虚拟空间进行扩容
if (!may_expand_vm(mm, len >> PAGE_SHIFT)) {
unsigned long nr_pages; /*
* MAP_FIXED may remove pages of mappings that intersects with
* requested mapping. Account for the pages it would unmap.
*/
if (!(vm_flags & MAP_FIXED))
return -ENOMEM; nr_pages = count_vma_pages_range(mm, addr, addr + len); if (!may_expand_vm(mm, (len >> PAGE_SHIFT) - nr_pages))
return -ENOMEM;
} //扫描当前进程地址空间的vm_area_struct结构相关的红黑树,确定线性区域的位置,如果找到一个区域,说明addr所在的虚拟区间已经被使用,表示已经被映射;因此需要调用do_munmap把这个区域从进程地址空间中撤销。
munmap_back:
if (find_vma_links(mm, addr, addr + len, &prev, &rb_link, &rb_parent)) {
if (do_munmap(mm, addr, len))
return -ENOMEM;
goto munmap_back;
} vma = vma_merge(mm, prev, addr, addr + len, vm_flags, NULL, file, pgoff, NULL);
if (vma)
goto out; //分配映射虚拟空间
vma = kmem_cache_zalloc(vm_area_cachep, GFP_KERNEL);
if (!vma) {
error = -ENOMEM;
goto unacct_error;
} vma->vm_mm = mm;
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;
INIT_LIST_HEAD(&vma->anon_vma_chain); if (file) {
if (vm_flags & VM_DENYWRITE) {
error = deny_write_access(file);
if (error)
goto free_vma;
}
vma->vm_file = get_file(file);
error = file->f_op->mmap(file, vma);
if (error)
goto unmap_and_free_vma; /* Can addr have changed??
*
* Answer: Yes, several device drivers can do it in their
* f_op->mmap method. -DaveM
* Bug: If addr is changed, prev, rb_link, rb_parent should
* be updated for vma_link()
*/
WARN_ON_ONCE(addr != vma->vm_start); addr = vma->vm_start;
vm_flags = vma->vm_flags;
} else if (vm_flags & VM_SHARED) {
error = shmem_zero_setup(vma);
if (error)
goto free_vma;
} ...
}

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

static const struct file_operations mem_fops = {
.llseek = memory_lseek,
.read = read_mem,
.write = write_mem,
.mmap = mmap_mem,
.open = open_mem,
.get_unmapped_area = get_unmapped_area_mem,
}; static int mmap_mem(struct file *file, struct vm_area_struct *vma)
{
size_t size = vma->vm_end - vma->vm_start; if (!valid_mmap_phys_addr_range(vma->vm_pgoff, size))
return -EINVAL; if (!private_mapping_ok(vma))
return -ENOSYS; if (!range_is_allowed(vma->vm_pgoff, size))
return -EPERM; if (!phys_mem_access_prot_allowed(file, vma->vm_pgoff, size,
&vma->vm_page_prot))
return -EINVAL; vma->vm_page_prot = phys_mem_access_prot(file, vma->vm_pgoff,
size,
vma->vm_page_prot); vma->vm_ops = &mmap_mem_ops; /* Remap-pfn-range will mark the range VM_IO */
if (remap_pfn_range(vma,
vma->vm_start,
vma->vm_pgoff,
size,
vma->vm_page_prot)) {
return -EAGAIN;
}
return 0;
}

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

通过devmem访问物理地址的更多相关文章

  1. devmem读写物理内存和devkmem读取内核虚拟内存

    关键词:/dev/mem./dev/kmem.mmap.__va.__pa.remap_pfn_range等等. 在日常工作中常有直接操作寄存器或者某一物理地址的需求,busybox中提供了devme ...

  2. 【Linux开发】计算机底层是如何访问显卡的?

    1. 显卡驱动是怎么控制显卡的, 就是说, 使用那些指令控制显卡, 通过端口么? 2. DirectX 或 OpenGL 或 CUDA 或 OpenCL 怎么找到显卡驱动, 显卡驱动是不是要为他们提供 ...

  3. 1-学习前言&C语言概述

    [C语言]01-学习前言&C语言概述 参考自 http://www.cnblogs.com/mjios/archive/2013/03/12/2956508.html#label2 C语言是我 ...

  4. io端口与io内存详解

    (一)地址的概念 1)物理地址:CPU地址总线传来的地址,由硬件电路控制其具体含义.物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存.BIOS等).在程序指令中的虚拟地址 ...

  5. MIT JOS学习笔记02:kernel 01(2016.10.28)

    未经许可谢绝以任何形式对本文内容进行转载! 在文章开头不得不说的是,因为这部分的代码需要仔细理清的东西太多,所以导致这篇分析显得很啰嗦,还请谅解. 我们在上一篇文章已经分析了Boot Loader的功 ...

  6. ok6410,mmu,内存管理

    MMU 一.MMU学习 MMU其实就是一个页表.将虚拟地址通过查表的方式,对应到物理地址去他由一个或一组芯片组成,一般存在与协处理器中. 1.将虚拟地址转化为物理地址 2.访问权限管理 1.1得出mm ...

  7. 01-C语言概述

    本文目录 一.C语言简史 二.C语言的特点 三.C语言能做什么? 四.C语言的版本问题 五.C语言语法预览 回到顶部 一.C语言简史 C语言于1972年发明,首次使用是用于重写UINX操作系统(UNI ...

  8. 《转》简述c语言的优缺点

    C语言是1972年由美国的Dennis Ritchie设计发明的,到1978年由美国电话电报公司(AT&T)贝尔实验室正式发表了C语言.再到1970到80年代,C语言被广泛应用.这短短的几十年 ...

  9. 汇编语言--寄存器(cpu工作原理)

    本文地址:http://www.cnblogs.com/archimedes/p/assembly-register.html,转载请注明源地址. 本文主要将介绍的是8086 CPU中的寄存器, 寄存 ...

随机推荐

  1. Eclipse如何设置编译文件.class输出路径

    1.首先我发现我的eclipse中-->project-->build automatically 是勾选上的.好吧,把把前面的勾去掉. 2去掉以后我先clean --> Clean ...

  2. UIButton的titleLabel

    UIButton的titleLabel @property(nonatomic, readonly, retain) UILabel *titleLabel Description - 描述A vie ...

  3. TIAGO机器人传感器参数简介 手册翻译

    本来认为这篇文章是最没人气的,竟然收到了回复,看来要继续更新本文了.留下笔者联系方式,邮箱leop22@163.com,欢迎邮件交流. 防止不良爬虫,原文链接:http://www.cnblogs.c ...

  4. AutoHotkey的函数对象的Bind方法绑定参数的应用

    近期在写Excel数据批处理函数,想提取某列的每个数据是否匹配某某条件的所有单元格. 这种需求比较多,比如判断的值有:单元格值,字体颜色,单元格颜色等等, 判断条件有:相同,不同,正则,或在某多行字符 ...

  5. liunx screen使用简单实验

    liunx screen使用 今天因工作需要使用到screen工具,感觉挺有意思,记录一下 GNU Screen是一款由GNU计划开发的用于命令行终端切换的自由软件.用户可以通过该软件同时连接多个本地 ...

  6. July 12th 2017 Week 28th Wednesday

    No way is impossible to courage. 勇敢面前没有通不过的路. Without faith and courage, nothing is possible. With t ...

  7. IOS UIWebView(浏览器控件)

    什么是UIWebViewUIWebView是iOS内置的浏览器控件系统自带的Safari浏览器就是通过UIWebView实现的 UIWebView不但能加载远程的网页资源,还能加载绝大部分的常见文件h ...

  8. TypeScript----类

    一.类的属性 public: 公有, private: 私有,不能在声明它的类的外部访问,只能在类内部访问 protect: 保护,不能在声明它的类的外部访问,但继承者除外 readonly 只读属性 ...

  9. ECharts属性设置

    theme = { // 全图默认背景 // backgroundColor: ‘rgba(0,0,0,0)’, // 默认色板 color: ['#ff7f50','#87cefa','#da70d ...

  10. iOS - CocoaPods 和 Carthage 的比较

    CocoaPods 好处? 开发 iOS 项目不可避免地要使用第三方开源库,CocoaPods 的出现使得我们可以节省设置和更新第三方开源库的时间 在引入第三方库时它可以自动为我们完成各种各样的配置, ...