Memory Mapped File
     
    
      Posted on 
      
        May 31, 2018
       
    
   
  
    内存映射文件( memory mapped file )与磁盘上的文件存在直接的对应关系。内存映射 I/O 将磁盘上的文件映射到用户进程地址空间中,这样,当我们从映射内存中获取字节时,会读取文件的相应字节。同样的,当我们将数据存储在映射内存中时,相应的字节会自动写入文件中。这样以来就可以在不使用 read() 或 write() 系统调用的情况下执行 I/O 操作 。
mmap & munmap mmap 系统调用将文件或设备映射到内存中,当调用成功时,它会返回映射内存的起始地址。第一个参数 addr 表示文件要映射到内存中的虚拟地址,一般都为 NULL ,表示由内核决定合适的映射地址。第二个参数 len 指定映射的大小(以字节为单位),通常情况下,内核创建的映射内存的大小是内存页面大小的整数倍。第三个参数 prot 指定访问权限,可以是 PROT_READ,PROT_WRITE ,PROT_EXEC 。第四个参数 flags 可以是 MAP_PRIVATE,MAP_SHARED 。第五个参数 fd 标识映射文件的文件描述符。第六个参数 offset 指定了文件映射的起点。为了映射整个文件,我们将 offset 指定为0,将 len 指定为整个文件的大小。
1 2 3 4 5 6 7 #include  <sys/mman.h>  void  *mmap(void  *addr, size_t  len, int  prot, int  flags, int  fd, off_t  offset); int  munmap(void  *addr, size_t  len); 
Golang中系统调用参数有些许不同,但本质是一样的。
1 2 3 4 5 package  syscallfunc  Mmap (fd int , offset int64 , length int , prot int , flags int )  (data []byte , err error) func  Munmap (b []byte )  (err error) 
GPIO 当然了,如果光介绍 mmap 这个系统调用就太无聊了,我们下面来看一下 mmap 能帮助我们干什么事情,如果你玩树莓派的话,我们可以用 mmap 来写一个 GPIO 驱动。
在树莓派的 /dev 目录下存在 mem 和 gpiomem 这两个文件。通过 mmap /dev/gpiomem 文件我们可以在没有 root 权限的情况下访问 GPIO 寄存器。打开 /dev/gpiomem 设备文件并使用 mmap() 函数可将 GPIO 寄存器映射到进程内存空间中去。/dev/mem 代表整个系统的内存空间。/dev/gpiomem 仅允许访问 GPIO 外设寄存器, /dev/mem 允许访问所有外设寄存器以及所有内存,相对来说更加危险。为了保护内存空间,最好使用 /dev/gpiomem 而非 /dev/mem 来控制 GPIO 寄存器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25           Rev 2 and 3 Raspberry Pi  +-----+---------+----------+---------+-----+ | BCM |   Name  | Physical | Name    | BCM | +-----+---------+----++----+---------+-----+  |     |    3.3v |  1 || 2  | 5v      |     | |   2 |   SDA 1 |  3 || 4  | 5v      |     |  |   3 |   SCL 1 |  5 || 6  | 0v      |     | |   4 | GPIO  7 |  7 || 8  | TxD     | 14  | |     |      0v |  9 || 10 | RxD     | 15  |  |  17 | GPIO  0 | 11 || 12 | GPIO  1 | 18  |  |  27 | GPIO  2 | 13 || 14 | 0v      |     | |  22 | GPIO  3 | 15 || 16 | GPIO  4 | 23  | |     |    3.3v | 17 || 18 | GPIO  5 | 24  |   |  10 |    MOSI | 19 || 20 | 0v      |     |   |   9 |    MISO | 21 || 22 | GPIO  6 | 25  |  |  11 |    SCLK | 23 || 24 | CE0     | 8   | |     |      0v | 25 || 26 | CE1     | 7   |  |   0 |   SDA 0 | 27 || 28 | SCL 0   | 1   |  |   5 | GPIO 21 | 29 || 30 | 0v      |     | |   6 | GPIO 22 | 31 || 32 | GPIO 26 | 12  | |  13 | GPIO 23 | 33 || 34 | 0v      |     | |  19 | GPIO 24 | 35 || 36 | GPIO 27 | 16  | |  26 | GPIO 25 | 37 || 38 | GPIO 28 | 20  | |     |      0v | 39 || 40 | GPIO 29 | 21  | +-----+---------+----++----+---------+-----+ 
如果通过 /dev/mem 访问 GPIO 外设寄存器,那么我们需要确定其内存中的基地址,我们通过读取 /proc/device-tree/soc/ranges 来确定基地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 const  (    bcm2835Base = 0x20000000      gpioOffset  = 0x200000      memLength = 4096  ) var  gpioBase int64 var  (    memLock sync.Mutex     gpioMem  []byte  ) func  init ()     base := getBase()     gpioBase = base + gpioOffset  } func  getBase ()  (base int64 )     base = bcm2835Base     ranges, err := os.Open("/proc/device-tree/soc/ranges" )     defer  ranges.Close()     if  err != nil  {         return      }     b := make ([]byte , 4 )     n, err := ranges.ReadAt(b, 4 )     if  n != 4  || err != nil  {         return      }     buf := bytes.NewReader(b)     var  out uint32      err = binary.Read(buf, binary.BigEndian, &out)     if  err != nil  {         return      }     return  int64 (out) } 
 Open 函数通过映射 /dev/mem 文件来将 GPIO 寄存器映射到内存中,这样以来我们就可以通过直接改变 gpioMem 的值来操控 GPIO 寄存器了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 func  Open ()  error          file, err := os.OpenFile("/dev/mem" , os.O_RDWR|os.O_SYNC, 0 )     if  os.IsPermission(err) {                  file, err = os.OpenFile("/dev/gpiomem" , os.O_RDWR|os.O_SYNC, 0 )     }          defer  file.Close()     memLock.Lock()     defer  memLock.Unlock()          gpioMem, err = syscall.Mmap(         int (file.Fd()),         gpioBase,         memLength,         syscall.PROT_READ|syscall.PROT_WRITE,         syscall.MAP_SHARED,     )     if  err != nil  {         return      } } 
Close 函数使用 syscall.Munmap 系统调用来解除内存映射。
1 2 3 4 5 6 7 8 func  Close ()  error     memLock.Lock()     defer  memLock.Unlock()     if  err := syscall.Munmap(mem); err != nil  {         return  err     }     return  nil  }