【Linux基础】mmap与DDR读写应用代码详解

【一】mmap函数

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
参数:
	addr: 指定映射区的首地址。通常传 NULL,表示让系统自动分配
	length:共享内存映射区的大小。(<= 文件的实际大小)
    *如果使用length > 文件大小,那么就会出现总线错误*
	prot: 共享内存映射区的读写属性。PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
	flags: 标注共享内存的共享属性。MAP_SHARED、MAP_PRIVATE  
    		shared 意思是修改会反映到磁盘上
			private 表示修改不反映到磁盘上
	fd: 用于创建共享内存映射区的那个文件的 文件描述符。
offset:默认 0,表示映射文件全部。偏移位置。需是 4k 的整数倍。
返回值:
	成功:映射区的首地址。
	失败:MAP_FAILED (void*(-1)), errno

函数注释写的非常详尽了,其中DDR的单页大小也是4k与mmap保持一致。
【二】DDR读数据代码(写是差不多)

#define MAP_SIZE 4096UL
#define MAP_MASK (MAP_SIZE -1)
/**
* @brief 从实际物理地址读取数据。
* @details 通过 mmap 映射关系,找到对应的实际物理地址对应的虚拟地址,然后读取数据。
* 读取长度,每次最低4字节。
* @param[in] readAddr, unsigned long, 需要操作的物理地址。
* @param[out] buf,unsigned char *, 读取数据的buf地址。
* @param[in] bufLen,unsigned long , buf 参数的容量,4字节为单位,如 unsigned long buf[100],那么最大能接收100个4字节。
* 用于避免因为buf容量不足,导致素组越界之类的软件崩溃问题。
* @return len,unsigned long, 读取的数据长度,字节为单位。如果读取出错,则返回0,如果正确,则返回对应的长度。
*/
static int Devmem_Read(unsigned long readAddr, unsigned long* buf, unsigned long len)
{
    unsigned long i = 0;
    int fd;
    long unsigned int offset_len = 0;
    void *map_base, *virt_addr;
    off_t addr = readAddr;

    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) == -1)
    {
        printf("Error at line %d, file %s\n", __LINE__, __FILE__);
        return 0;
    }

    /* Map one page */ //将内核空间映射到用户空间
    map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
    if(map_base == (void *) -1)
    {
        printf("Error at line %d, file %s\n", __LINE__, __FILE__);
        close(fd);
        return 0;
    }

    for (i = 0; i < len; i++)
    {
        // 翻页处理
        if(offset_len >= MAP_MASK)
        {
            offset_len = 0;
            if(munmap(map_base, MAP_SIZE) == -1)
            {
                printf("Error at line %d, file %s\n", __LINE__, __FILE__);
                close(fd);
                return 0;
            }
            map_base = mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, addr & ~MAP_MASK);
            if(map_base == (void *) -1)
            {
                printf("Error at line %d, file %s\n", __LINE__, __FILE__);
                close(fd);
                return 0;
            }
        }

        virt_addr= map_base + (addr & MAP_MASK);	// 将内核空间映射到用户空间操作
        buf[i] = *((unsigned long *) virt_addr);	// 读取数据
        addr += 4;
        offset_len += 4;
    }

    if(munmap(map_base, MAP_SIZE) == -1)
    {
        printf("Error at line %d, file %s\n", __LINE__, __FILE__);
        close(fd);
        return 0;
    }
    close(fd);
    return i;
}

这里有几个变量需要注意,map_base(映射后基地址),virt_addr(映射后访问地址),Readaddr(需要读的物理地址)。
我们来细分一下读取的步骤:
(1)打开/dev/mem设备
(2)mmap映射物理地址
因为一次性映射4k,多余4k则需要翻页(重新映射),所以在offset中填入的是[ addr & ~MAP_MASK],原式是:addr & ~(4096- 1)→addr整除4096后的起始数。
得到了基地址map_base之后,则根据读取位置的偏移来进行地址偏移:virt_addr= map_base + (addr & MAP_MASK),即在map_base的基础上加上addr整除4096后的余数。

如果用公式表示addr,设addr = 4096*a + b,可得:
addr &~ (4096 -1) = 4096*a。
addr & (4096 - 1) = b。

用图形来表示的话则如下图:
mmap映射详解
如果你需要操作的内容长度大于4k了,则在指针运行至4096*(a+1)时就需要进行跨页,然后被映射的扇区就是4096*(a+1)了。
(3)对于映射的片区进行操作,读/写都可
值得注意的是,这个demo的读写都是按照uint32的形式来做的,即每四个字节存到一个buf里,从而地址的偏移为每操作一次+4。这里需要区分len和offset_len,len是按照uint32的长度来取的,在循环中填写的为循环执行len次。offset_len则是按照uint8来记录目前操作的长度,当offset_len超过了MAP_MASK时,则进行翻页。
疑点:
(1)根据目前针对于MMAP的学习来看,length长度只需要小于文件实际大小/长度即可,那么对于DDR来说,是否可以意味着我可以一次性映射8k、16k甚至以mb为单位的长度,然后按照映射以4k为单位,假如我设长度为6k,实际上分配的空间就是8k。这样的话,是否意味着我可以不用翻页?现在尚不知能否在长度上超过/dev/mem的大小,如果以4G为大小的话,基本上不存在设置大小大于文件大小的情况。无论是文件大小本身等于0,还是设置大小大于文件大小,这两种情况都可以视为出错,不在讨论的范围内。经过尝试,我已在DDR上一次性mmap 16K的空间,并对其进行连续的读写,事实证明没必要以4K为大小进行循环映射。但是在mmap的最后一个参数,一定要是0或者4k的倍数,因为需要进行对齐!
(2)offset_len这里的处理有待商榷,如果我的理解是对的,那么在读取地址有余数时,假设为0x10001010,则addr & MAP_MASK = 0x10,那么我从这个地址开始写,offset_len初始为0,addr写过了4K扇区的区域,offset_len依然没有大于 等于MAP_MASK。假如循环进行1020次,那么此时addr为:0x10001010 + 0xFF0 = 0x10002000,但是offset = 1020*4 = 0xFF0 < 0xFFF,那么此时按道理说就已经需要跨页了,但并没有触发跨页的条件。因此,个人认为offset_len应该在第一次mmap之后,赋初值为:offset_len = addr & MAP_MASK,再在循环内进行+4
【后记】
经过了思考与对比,最后对mmap进行一个总结,个人认为最为重要的就是“取多少,用多少”,往往大部分segment fault都出现在
取的少,用的多
,哪怕你是取的多,用的少都无所谓。下面我将用图去详解mmap应该注意的问题:
(1)取出大小和offset对齐之间的关系
(2)map_base的真实含义与偏移取值
假如我这样设置代码:

#define MAP_SIZE 1024*16
#define MAP_MASK (4096UL-1)
#define PL_WRITE_DDR_ADDR 0x10000100
offset_len = PL_WRITE_DDR_ADDR&MAP_MASK;
map_base = mmap(0,MAP_SIZE,PROT_READ | PROT_WRITE, MAP_SHARED, fd,PL_WRITE_DDR_ADDR & ~MAP_MASK);
for(i = 0;i<LEN;i++)
 {
   virt_addr = map_base + i*4 + offset_len;
   *((unsigned long *)virt_addr) = buf[i];
   printf("addr = 0x%08x,Write buf[%d] = %d\n",addr,i,buf[i]);//运行到addr = 0x0x10003ffc时报错
   addr += 4;
 }

乍一看,就是从PL_WRITE_DDR_ADDR 映射16K的空间嘛,结果呢,在操作到0x10004000时,发生了segment fault,为什么?因为offset的4K对齐限制,所以我们在mmap的offset填写的是(PL_WRITE_DDR_ADDR &~ MAP_MASK),看起来很合理,这里没有问题。但是就是因为如此,实质上的offset的值是0x10000000,也就是说,实际上,我取出的空间是:0x10000000~0x10004000。但是我读写的时候,是不是要严格按照PL_WRITE_DDR_ADDR 来?否则它的设置就没有意义了,因此,我在使用时在写入第一个数据的时候是不是就对map_base进行了偏移,偏移量是PL_WRITE_DDR_ADDR&MAP_MASK = 0x100。那么我在循环里,还是按照文件已有的长度16k去寻址的话,是不是就是从0x10000100~0x10004100。很明显,我使用的空间超过了我取出的空间。
图形说明如下:
在这里插入图片描述
所以正确的操作应该是,我应该取出(MAP_SIZE + offset_len)的空间,这样就可以取到后面的数据了,这也是为什么,当ADDR是4k的整数倍时程序正常运行,但稍微剩余一点程序就报错。

offset_len = PL_WRITE_DDR_ADDR&MAP_MASK;
map_base = mmap(0,(MAP_SIZE+offset_len),PROT_READ | PROT_WRITE, MAP_SHARED, fd,PL_WRITE_DDR_ADDR & ~MAP_MASK);
  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
为了加速 Linux 文件的读写,可以使用 MMAP 文件加速读写技巧。MMAP 是一种内存映射文件的技术,它可以将一个文件映射到进程的虚拟地址空间,从而可以通过内存读写来访问文件。 使用 MMAP 文件加速读写的步骤如下: 1. 使用 open 函数打开文件,并使用 fstat 函数获取文件大小。 2. 使用 mmap 函数将文件映射到进程的虚拟地址空间。 3. 使用 memcpy 函数将文件数据从内存复制到缓冲区或者将缓冲区的数据写入到文件中。 4. 使用 munmap 函数解除映射关系,并使用 close 函数关闭文件。 需要注意的是,使用 MMAP 文件加速读写需要注意以下几点: 1. MMAP 文件映射只适用于较小的文件,因为它会将整个文件映射到内存中,如果文件太大,会导致内存占用过高。 2. MMAP 文件映射需要占用虚拟地址空间,如果虚拟地址空间不够,会导致映射失败。 3. MMAP 文件映射的效率受到内存访问速度的影响,对于大量随机访问的文件,可能比传统的读写方式效率低。 除了使用 MMAP 文件加速读写,还可以通过一些其他技巧来加速文件的读写,比如: 1. 使用缓冲区,可以减少文件系统的 I/O 操作次数,从而提高文件读写的效率。 2. 使用多线程或者多进程并发读写,可以充分利用多核 CPU 的性能,提高文件读写的效率。 3. 使用 DMA 技术,可以将数据直接从磁盘读取到内存中,避免了 CPU 的中介操作,从而提高了文件读取的效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值