通过devmem访问物理地址

目录

  • 1.写在前面
  • 2.devmem使用
  • 3.应用层
  • 4.内核层

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的参数相互对应,现在就可以通过应用层访问物理地址了。

原文地址:https://www.cnblogs.com/tinylaker/p/9823517.html

时间: 2024-10-07 23:26:15

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

虚拟地址到物理地址的地址变换过程

2015-05-09  青岛  张俊浩 内容部分来自<Unix内核源码剖析> 软件环境:UNIX V6 硬件环境:PDP-11/40(16位计算机) 第二章<进程>一节阐述了PDP-11/40的虚拟地址到物理地址的地址变换过程. MMU通过APR(Active Page Register)寄存器将虚拟地址变换为物理地址. APR寄存器由一个PAR(Page Address Register)寄存器和一个PDR(Page Description Register)寄存器构成. 内核通

物理地址与虚拟地址、统一编址与独立编址以及 I/O 端口与 I/O 内存

[摘要]从CPU连出来一把线:数据总线.地址总线.控制总线,这把线上挂着N个接口,有相同的,有不同的,名字叫做存储器接口.中断控制接口.DMA接口.并行接口.串行接口.AD接口--一个设备要想接入,就用自己的接口和总线上的某个匹配接口对接--于是总线上出现了各种设备:内存.硬盘,鼠标.键盘,显示器-- 对于CPU而言,如果它要发数据到某个设备,其实是发到对应的接口,接口电路里有多个寄存器(也称为端口),访问设备实际上是访问相关的端口,所有的信息会由接口转给它的设备.那么CPU会准备数据到数据总线

c语言

java c++面向对象的. c语言是面向过程的. c--> c++ -->java c语言特点 ansic一共有32个关键字,主要用小写. 运算符丰富.公有34种. 数据结构类型丰富. c语言允许直接访问物理地址,能进行位操作.(汇编中嵌入c语言) 面向过程与面向对象区别? 机器人行走. 面向过程:先出左右脚?左脚太高,屈膝,前倾多少度落下,然后右脚. 面向对象:走封装成一个类,类中有上面的步骤.使用时直接调用走这个类. 面向对象相当于是面向过程的封装.

C语言有什么优点什么缺点?有什么特别之处?

优点 1. 简洁紧凑.灵活方便 C语言一共只有32个关键字,9种控制语句,程序书写形式自由,主要用小写字母表示.它把高级语言的基本结构和语句与低级语言的实用性结合起来. C 语言可以像汇编语言一样对位.字节和地址进行操作,而这三者是计算机最基本的工作单元. 2. 运算符丰富 C语言的运算符包含的范围很广泛,共有34种运算符.C语言把括号.赋值.强制类型转换等都作为运算符处理.从而使C语言的运算类型极其丰富,表达式类型多样化.灵活使用各种运算符可以实现在其它高级语言中难以实现的运算. 3. 数据结

话说:学好C语言,走遍天下都不怕

学好C语言,走遍天下都不怕 ·为什么要学习C语言,学习C语言的重要性 C语言是现代通用编程语言的鼻祖语言,也是所有操作系统必须支持的语言,在全世界编程社区的排行榜中常年稳居榜首.不学好C语言,其它编程语言难于到精通的程度. 我们不想重复"C语言是编程的基础"."学好C语言,走遍天下都不怕"等等.C作为一门工程实用性极强的语言,提供了对操作系统和内存的精准控制,高性能的运行时环境,源码级的跨平台编译等优点,这才是我们学习C的理由. C语言也是个有趣的东西,对编程的认知

C语言笔记(一)

笑话一枚:程序员 A:“哥们儿,最近手头紧,借点钱?”程序员 B:“成啊,要多少?”程序员 A:“一千行不?”程序员 B:“咱俩谁跟谁!给你凑个整,1024,拿去吧.” ========================= 我 是 分 割 线 ========================= 前言 C语言允许直接访问物理地址,可以直接对硬件进行操作,非常适合开发内核和硬件驱动. 书上看来一句话:普通人用 C 语言在 3 年之下,一般来说,还没掌握 C 语言: 5 年之下,一般来说还没熟悉 C 语

C语言学习笔记(1)

由于项目要求,需要学习iOS移动端开发.iOS开发的核心语言是Objective-C,Objective-C是在C语言的基础加了一层面向对象的语法.为了能够更好地掌握Objective-C,故先学习C语言,再在C语言的基础上升华到Objective-C. 一.初认C语言 C语言简史 C语言于1972年发明,首次使用是用于重写UINX操作系统(UNIX以前是用汇编写的): 随着UNIX操作系统的成功,C语言也得到了大幅度地推广,至今还是世界上最流行.使用最广泛的高级程序设计语言之一: C语言是一门

第一节 为什么学习C语言

一  c语言的发展 : 1.1C语言的发展过程 C语言是在 70 年代初问世的.一九七八年由美国电话电报公司(AT&T)贝尔实验室正式发表了C语言.目的 改写 UNIX操作系统. 后来由美国国家标准协会(American National Standards Institute)在此基础上制定了一个C 语言标准,于一九八三年发表.通常称之为ANSI C. 1.2C语言的特点 1 C语言简洁.紧凑,使用方便.灵活. 高级语言 : a+b 汇编语言 :ADD AX,BX 机器语言 : 0000 00

20150222 IO端口映射和IO内存映射(详解S3C24XX_GPIO驱动)

20150222 IO端口映射和IO内存映射(详解S3C24XX_GPIO驱动) 2015-02-22 李海沿 刚刚我们实现了linux系统内存的分配,读写,释放功能,下面,我们一鼓作气将IO端口映射及IO内存映射搞定,加油! (一)地址的概念 1)物理地址:CPU地址总线传来的地址,由硬件电路控制其具体含义.物理地址中很大一部分是留给内存条中的内存的,但也常被映射到其他存储器上(如显存.BIOS等).在程序指令中的虚拟地址经过段映射和页面映射后,就生成了物理地址,这个物理地址被放到CPU的地址