【IMX6ULL驱动开发学习】19.mmap内存映射

mmap将一个文件或者其它对象映射进内存,使得应用层可以直接读取到驱动层的数据,无需通过copy_to_user函数
可以用于像LCD这样的外设, 需要读写大量数据的

一、应用层

mmap用法:

  • 用open系统调用打开文件, 并返回描述符fd.
  • 用mmap建立内存映射, 并返回映射首地址指针start.
  • 对映射(文件)进行各种操作, 显示(printf), 修改(strcpy、memncpy、sprintf、直接修改等).
  • munmap(void *start, size_t lenght)关闭内存映射.
  • close系统调用关闭文件fd.

mmap函数:

void *mmap(void *addr, size_t length, int prot, int flags,
                  int fd, off_t offset);
参数 含义
addr 指向欲映射的内存起始地址,通常设为 NULL
代表让系统自动选定地址,映射成功后返回该地址
length 代表将文件中多大的部分映射到内存。
prot 映射区域的保护方式。可以为以下几种方式的组合:
PROT_EXEC 映射区域可被执行
PROT_READ 映射区域可被读取
PROT_WRITE 映射区域可被写入
PROT_NONE 映射区域不能存取
flags 影响映射区域的各种特性。
在调用mmap()时必须要指定MAP_SHAREDMAP_PRIVATE
fd 要映射到内存中的文件描述符。
如果使用匿名内存映射时,即flags中设置了MAP_ANONYMOUS,fd设为-1。
有些系统不支持匿名内存映射,则可以使用fopen打开/dev/zero文件,
然后对该文件进行映射,可以同样达到匿名内存映射的效果。
offset 文件映射的偏移量,通常设置为0
代表从文件最前方开始对应,offset必须是分页大小的整数倍。

内存映射之后对内存进行读写,可以用write、read,但是效率低,可以直接对mmap函数返回的地址进行操作
使用 strcpy 等字符串处理函数即可


应用程序示例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>

#define   MMSIZE   128

int main(int argc, char *argv[])
{
    
    
    int len;
    char wr_buf[] = "hello mmap";
	char read_buf[1024];
	char *start;

	...
	...
	
    /*open*/
    int fd;
    fd = open(argv[1], O_RDWR);
    if(fd < 0){
    
    
        printf("open failed\n");
        return -2;
    }
	printf("pid = %d\n", getpid());
	/* 将文件映射到进程的虚拟内存空间 */
	start = mmap(NULL, MMSIZE, PROT_READ | PROT_WRITE, 
		         MAP_SHARED, fd, 0);
	printf("mmap address = 0x%x\n", start);
	/* 写数据 */
	strcpy(start, wr_buf);
	/* 读数据 */
	strcpy(read_buf, start);
//	read(fd, read_buf, MMSIZE);

	printf("new data = %s\n", start);
	printf("old data = %s\n", read_buf);
	while(1){
    
    
		sleep(5);
	}
	munmap(start, MMSIZE);
    close(fd);
    return 0;
}

二、驱动层

  • 首先,驱动程序先分配好一段内存
  • 接着用户进程通过库函数mmap()来告诉内核要将多大的内存映射到内核空间
  • 内核经过一系列函数调用后调用对应的驱动程序的file_operation中指定的xxx_mmap函数
  • 在xxx_mmap函数中调用remap_pfn_range()来建立映射关系。

驱动程序示例:

......
......

#define   MM_SIZE   1024 * 8
#define   MIN(x,y)  (x < y ? x : y)

static char *buf = NULL;

static ssize_t hello_read (struct file *filp, char  *buff, size_t size, loff_t *offset)
{
    
    
	int err;
	err = copy_to_user(buff, buf, MIN(MM_SIZE, size));
	return MIN(MM_SIZE, size);
}

static int hello_mmap (struct file *filp, struct vm_area_struct *vma)
{
    
    	
	/*设置属性: cache,buffer */
	vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);

	if(remap_pfn_range(vma,//虚拟内存区域,即设备地址将要映射到这里  
                       vma->vm_start,//虚拟空间的起始地址  
                       virt_to_phys(buf)>>PAGE_SHIFT,//与物理内存对应的页帧号,物理地址右移12位  
                       vma->vm_end - vma->vm_start,//映射区域大小,一般是页大小的整数倍  
                       vma->vm_page_prot))//保护属性,  
    {
    
      
        return -EAGAIN;  
    }  
    return 0;
}

/*定义 file_operations 结构体*/
static const struct file_operations hello_drv = {
    
    
	.read		= hello_read,
	.open		= hello_open,
	.mmap 		= hello_mmap,
	......
	......
};

/*入口函数*/
static int hello_init(void)
{
    
    
	//内核申请内存只能按页申请,申请该内存以便后面把它当作虚拟设备
	buf = kmalloc(MM_SIZE, GFP_KERNEL);
	strcpy(buf, "abc");  //先放入一些数据
	......
	......
	
	return 0;
}

/*退出函数*/
static void hello_exit(void)
{
    
    
	kfree(buf);
	......
	......
}	

......
......


三、测试

直接口述吧
当mmap的参数为MAP_SHARED时,使用strcpy和read读取的数据和写入的相同;
当mmap的参数为MAP_PRIVATE时,使用strcpy读取的数据和写入的相同,read的数据和写入的不同(因为read到的是原内存的数据,MAP_PRIVATE标志会使得在写入内存数据时,内核会自动把内存空间拷贝一份,从而read到的是原来的内存,而写入的数据写入到了拷贝的新内存中)
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/HuangChen666/article/details/131683860