Linux内核中ioremap映射的透彻理解(二)

2014-11-24 08:17:53 · 作者: · 浏览: 7
,这个宏定义为:


#define bRTC(Nb) __REG(0x57000000 + (Nb))


  其中又借助了宏__REG,而__REG又定义为:


# define __REG(x) io_p2v(x)


  最后的io_p2v才是真正"玩"虚拟地址和物理地址转换的地方:


#define io_p2v(x) ((x) | 0xa0000000)


  与__REG对应的有个__PREG:


# define __PREG(x) io_v2p(x)


  与io_p2v对应的有个io_v2p:


#define io_v2p(x) ((x) & ~0xa0000000)


  可见有没有出现ioremap是次要的,关键问题是有无虚拟地址和物理地址的转换!


  下面的程序在启动的时候保留一段内存,然后使用ioremap将它映射到内核虚拟空间,同时又用remap_page_range映射到用户虚拟空间,这样一来,内核和用户都能访问。如果在内核虚拟地址将这段内存初始化串"abcd",那么在用户虚拟地址能够读出来:


/************mmap_ioremap.c**************/
#include
#include
#include
#include
#include /* for mem_map_(un)reserve */
#include /* for virt_to_phys */
#include /* for kmalloc and kfree */


MODULE_PARM(mem_start, "i");
MODULE_PARM(mem_size, "i");


static int mem_start = 101, mem_size = 10;
static char *reserve_virt_addr;
static int major;


int mmapdrv_open(struct inode *inode, struct file *file);
int mmapdrv_release(struct inode *inode, struct file *file);
int mmapdrv_mmap(struct file *file, struct vm_area_struct *vma);


static struct file_operations mmapdrv_fops =
{
 owner: THIS_MODULE, mmap: mmapdrv_mmap, open: mmapdrv_open, release:
 mmapdrv_release,
};


int init_module(void)
{
 if ((major = register_chrdev(0, "mmapdrv", &mmapdrv_fops)) < 0)
 {
  printk("mmapdrv: unable to register character device\n");
  return ( - EIO);
 }
 printk("mmap device major = %d\n", major);


 printk("high memory physical address 0x%ldM\n", virt_to_phys(high_memory) /
1024 / 1024);


 reserve_virt_addr = ioremap(mem_start *1024 * 1024, mem_size *1024 * 1024);
 printk("reserve_virt_addr = 0x%lx\n", (unsigned long)reserve_virt_addr);
 if (reserve_virt_addr)
 {
  int i;
  for (i = 0; i < mem_size *1024 * 1024; i += 4)
  {
   reserve_virt_addr[i] = 'a';
   reserve_virt_addr[i + 1] = 'b';
   reserve_virt_addr[i + 2] = 'c';
   reserve_virt_addr[i + 3] = 'd';
  }
 }
 else
 {
  unregister_chrdev(major, "mmapdrv");
  return - ENODEV;
 }
 return 0;
}


/* remove the module */
void cleanup_module(void)
{
 if (reserve_virt_addr)
  iounmap(reserve_virt_addr);


 unregister_chrdev(major, "mmapdrv");
 return ;
}


int mmapdrv_open(struct inode *inode, struct file *file)
{
 MOD_INC_USE_COUNT;
 return (0);
}


int mmapdrv_release(struct inode *inode, struct file *file)
{
 MOD_DEC_USE_COUNT;
 return (0);
}


int mmapdrv_mmap(struct file *file, struct vm_area_struct *vma)
{
 unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
 unsigned long size = vma->vm_end - vma->vm_start;


 if (size > mem_size *1024 * 1024)
 {
  printk("size too big\n");
  return ( - ENXIO);
 }


 offset = offset + mem_start * 1024 * 1024;


 /* we do not want to have this area swapped out, lock it */
 vma->vm_flags |= VM_LOCKED;
 if (remap_page_range(vma, vma->vm_start, offset, size, PAGE_SHARED))
 {
  printk("remap page range failed\n");
  return - ENXIO;
 }
 return (0);
}


  remap_page_range函数的功能是构造用于映射一段物理地址的新页表,实现了内核空间与用户空间的映射,其原型如下:


int remap_page_range(vma_area_struct *vma, unsigned long from, unsigned long to, unsigned long size, pgprot_tprot);


  使用mmap最典型的例子是显示卡的驱动,将显存空间直接从内核映射到用户空间将可提供显存的读写效率。


(在内核驱动程序的初始化阶段,通过ioremap()将物理地址映射到内核虚拟空间;在驱动程序的mmap系统调用中,使用remap_page_range()将该块ROM映射到用户虚拟空间。这样内核空间和用户空间都能访问这段被映射后的虚拟地址。)