#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/mman.h> #include <sys/types.h> #include <fcntl.h> int main(int argc, char *argv[]) { int fd; char *addr; char *str = "Hello World"; fd = open("./a",O_CREAT|O_RDWR|O_TRUNC,0666); if(fd == -1) { perror("open file fail:"); exit(1); } if(ftruncate(fd,4096)==-1) { perror("ftruncate fail:"); close(fd); exit(1); } addr =(char *) mmap(NULL,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(addr == (char *)MAP_FAILED) { perror("mmap fail:"); exit(1); } memset(addr,' ',4096); memcpy(addr,str,strlen(str)); //hello world 1 close(fd); memcpy(addr+strlen(str),str,strlen(str)); //hello world 2 if(msync(addr,4096,MS_SYNC)==-1) { perror("msync fail:"); exit(1); } munmap(addr,4096); return 0; }
//下面的代码把文件1.ls中的内容通过mmap函数写入2.ls中,忽略出错处理
int fd=open("1.ls",O_RDONLY);
int fd2=open("2.ls",O_CREAT|O_RDWR|O_TRUNC,S_IRUSR|S_IWUSR);//必须设置读写权限,若只有写权限,会产生SIGSEGV信号
//mmap进行文件映射时必须先读取文件`
struct stat st;
fstat(fd,&st);
lseek(fd2,st.st_size-1,SEEK_SET);
write(fd2,"",1); //必须的,如果不设置,当写入数据的时候会遇到文件结束符,产生SIGBUS信号
void *_src=mmap(NULL,st.st_size,PROT_READ,MAP_SHARED, fd,0);
void *_des=mmap(NULL,st.st_size,PROT_WRITE,MAP_SHARED,fd2,0);
close(fd); //关闭文件后 依然可修改文件内容
close(fd2);
memcpy(_des,_src,st.st_size);
总结一下,可能产生的问题如下:
1.进行文件映射的描述符必须拥有读权限,否则会产生SIGSEGV信号
2.把内存内容写入映射文件时,必须确保被写文件当前位置到文件结尾的长度不小于所写内容长度,否则产生SIGBUS信号
3.关闭文件描述符并不能保证文件内容不被修改
4.munmap并不能使映射的内容写回磁盘
在编写设备驱动程序的时候,如果要想把设备内存映射到用户空间,那需要我们实现mmap,通过看ldd3上面的介绍,对实现mmap有了一点了解.
书上介绍主要是利用
int remap_pfn_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long pfn, unsigned long size, pgprot_t prot) 函数或者
int io_remap_page_range(struct vm_area_struct *vma, unsigned long virt_addr, unsigned long phys_addr, unsigned long size, pgprot_t prot)函数来实现,它们负责建立新的页表.这两个函数的区别是第一个函数是在参数pfn指向实际系统RAM的时候使用,而第二个函数是在 phys_addr指向I/O内存的时候使用.对于ARM平台来说,系统内存和端口应该是统一编址的,所以两个函数是等价的.
还有另外一个函数就是nopage函数,是VMA结构体中可以填充的一个函数,在虚拟页没有所对应的物理页的时候,会调用此函数,来分配一个物理页给虚拟页.
int remap_page_range(unsigned long from, unsigned long phys_addr, unsigned long size, pgprot_t prot)
其中from是映射开始的虚拟地址。这个函数为虚拟地址空间from和from+size之间的范围构造页表;
phys_addr是虚拟地址应该映射到的物理地址;
size是被映射区域的大小;
prot是保护标志。
remap_page_range的处理过程是对from到form+size之间的每一个页面,查找它所在的页目录和页表(必要时建立页表),清除页表项旧的内容,重新填写它的物理地址与保护域。
remap_page_range可以对多个连续的物理页面进行处理。<<Linux设备驱动程序>>指出,
remap_page_range只能给予对保留的页和物理内存之上的物理地址的访问,当对非保留的页使用
remap_page_range时,缺省的nopage处理控制映射被访问的虚地址处的零页。所以在分配内存后,就要对所分配的内存置保留位,它是通过函数mem_map_reserve实现的,它就是对相应物理页面置
PG_reserved标志位。
void* __mmap2(void*, size_t, int, int, int, size_t); // bionic/libc/arch-arm/syscalls/__mmap2.S ( __NR_mmap2 = __NR_SYSCALL_BASE + 192 )
| ( arch/arm/kernel/calls.S )
sys_mmap2 // ( arch/arm/kernel/entry-common.S )
|
sys_mmap_pgoff //( mm/mmap.c ) SYSCALL_DEFINE6(mmap_pgoff, )
|do_mmap_pgoff(file, addr, len, prot, flags, pgoff); // mm/mmap.c
/*!
* V4L interface - mmap function
* @param file structure file *
* @param vma structure vm_area_struct *
* @return status 0 Success, EINTR busy lock error, ENOBUFS remap_page error
*/
static int mxc_mmap(struct file *file, struct vm_area_struct *vma)
{
struct video_device *dev = video_devdata(file);
unsigned long size;
int res = 0;
cam_data *cam = video_get_drvdata(dev);
pr_err("=============================camera mmap\n");
pr_err("In MVC:mxc_mmap\n");
pr_err(" pgoff=0x%lx, start=0x%lx, end=0x%lx\n",
vma->vm_pgoff, vma->vm_start, vma->vm_end);
/* make this _really_ smp-safe */
if (down_interruptible(&cam->busy_lock))
return -EINTR;
size = vma->vm_end - vma->vm_start;
vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
if (remap_pfn_range(vma, vma->vm_start,
vma->vm_pgoff, size, vma->vm_page_prot)) {
pr_err("ERROR: v4l2 capture: mxc_mmap: "
"remap_pfn_range failed\n");
res = -ENOBUFS;
goto mxc_mmap_exit;
}
vma->vm_flags &= ~VM_IO;/* using shared anonymous pages */
mxc_mmap_exit:
up(&cam->busy_lock);
return res;
}