github.com/scottcagno/storage@v1.8.0/pkg/mmap/mmap_linux_amd64.go (about)

     1  package mmap
     2  
     3  import (
     4  	"os"
     5  	"reflect"
     6  	"runtime"
     7  	"syscall"
     8  	"unsafe"
     9  )
    10  
    11  // Mapping is a mapping of the file into the memory.
    12  type Mapping struct {
    13  	mapping
    14  	// alignedAddress specifies the start address of the the mapped memory
    15  	// aligned by the memory page size.
    16  	alignedAddress uintptr
    17  	// alignedLength specifies the length of the mapped memory, in bytes,
    18  	// aligned by the memory page size.
    19  	alignedLength uintptr
    20  	// locked specifies whether the mapped memory is locked.
    21  	locked bool
    22  }
    23  
    24  // Open opens and returns a new mapping of the given file into the memory.
    25  // The given file descriptor will be duplicated. It means that
    26  // if the parent file will be closed the mapping will still be valid.
    27  // Actual offset and length may be different than the given
    28  // by the reason of aligning to the memory page size.
    29  func Open(fd uintptr, offset int64, length uintptr, mode Mode, flags Flag) (*Mapping, error) {
    30  
    31  	// Using int64 (off_t) for the offset and uintptr (size_t) for the length
    32  	// by the reason of the compatibility.
    33  	if offset < 0 {
    34  		return nil, ErrBadOffset
    35  	}
    36  	if length > uintptr(MaxInt) {
    37  		return nil, ErrBadLength
    38  	}
    39  
    40  	m := &Mapping{}
    41  	prot := syscall.PROT_READ
    42  	mmapFlags := syscall.MAP_SHARED
    43  	if mode < ModeReadOnly || mode > ModeWriteCopy {
    44  		return nil, ErrBadMode
    45  	}
    46  	if mode > ModeReadOnly {
    47  		prot |= syscall.PROT_WRITE
    48  		m.writable = true
    49  	}
    50  	if mode == ModeWriteCopy {
    51  		flags = syscall.MAP_PRIVATE
    52  	}
    53  	if flags&FlagExecutable != 0 {
    54  		prot |= syscall.PROT_EXEC
    55  		m.executable = true
    56  	}
    57  
    58  	// The mapping address range must be aligned by the memory page size.
    59  	pageSize := int64(os.Getpagesize())
    60  	if pageSize < 0 {
    61  		return nil, os.NewSyscallError("getpagesize", syscall.EINVAL)
    62  	}
    63  	outerOffset := offset / pageSize
    64  	innerOffset := offset % pageSize
    65  	// ASSERT: uintptr is of the 64-bit length on the amd64 architecture.
    66  	m.alignedLength = uintptr(innerOffset) + length
    67  
    68  	var err error
    69  	m.alignedAddress, err = mmap(0, m.alignedLength, prot, mmapFlags, fd, outerOffset)
    70  	if err != nil {
    71  		return nil, os.NewSyscallError("mmap", err)
    72  	}
    73  	m.address = m.alignedAddress + uintptr(innerOffset)
    74  
    75  	// Wrapping the mapped memory by the byte slice.
    76  	slice := reflect.SliceHeader{}
    77  	slice.Data = m.address
    78  	slice.Len = int(length)
    79  	slice.Cap = slice.Len
    80  	m.memory = *(*[]byte)(unsafe.Pointer(&slice))
    81  
    82  	runtime.SetFinalizer(m, (*Mapping).Close)
    83  	return m, nil
    84  }
    85  
    86  // Lock locks the mapped memory pages.
    87  // All pages that contain a part of the mapping address range
    88  // are guaranteed to be resident in RAM when the call returns successfully.
    89  // The pages are guaranteed to stay in RAM until later unlocked.
    90  // It may need to increase process memory limits for operation success.
    91  // See working set on Windows and rlimit on Linux for details.
    92  func (m *Mapping) Lock() error {
    93  	if m.memory == nil {
    94  		return ErrClosed
    95  	}
    96  	if m.locked {
    97  		return ErrLocked
    98  	}
    99  	if err := mlock(m.alignedAddress, m.alignedLength); err != nil {
   100  		return os.NewSyscallError("mlock", err)
   101  	}
   102  	m.locked = true
   103  	return nil
   104  }
   105  
   106  // Unlock unlocks the previously locked mapped memory pages.
   107  func (m *Mapping) Unlock() error {
   108  	if m.memory == nil {
   109  		return ErrClosed
   110  	}
   111  	if !m.locked {
   112  		return ErrNotLocked
   113  	}
   114  	if err := munlock(m.alignedAddress, m.alignedLength); err != nil {
   115  		return os.NewSyscallError("munlock", err)
   116  	}
   117  	m.locked = false
   118  	return nil
   119  }
   120  
   121  // Sync synchronizes the mapped memory with the underlying file.
   122  func (m *Mapping) Sync() error {
   123  	if m.memory == nil {
   124  		return ErrClosed
   125  	}
   126  	if !m.writable {
   127  		return ErrReadOnly
   128  	}
   129  	return os.NewSyscallError("msync", msync(m.alignedAddress, m.alignedLength))
   130  }
   131  
   132  // Close closes this mapping and frees all resources associated with it.
   133  // Mapped memory will be synchronized with the underlying file and unlocked automatically.
   134  // Close implements the io.Closer interface.
   135  func (m *Mapping) Close() error {
   136  	if m.memory == nil {
   137  		return ErrClosed
   138  	}
   139  	var errs []error
   140  
   141  	// Maybe unnecessary.
   142  	if m.writable {
   143  		if err := m.Sync(); err != nil {
   144  			errs = append(errs, err)
   145  		}
   146  	}
   147  	if m.locked {
   148  		if err := m.Unlock(); err != nil {
   149  			errs = append(errs, err)
   150  		}
   151  	}
   152  
   153  	if err := munmap(m.alignedAddress, m.alignedLength); err != nil {
   154  		errs = append(errs, os.NewSyscallError("munmap", err))
   155  	}
   156  	*m = Mapping{}
   157  	runtime.SetFinalizer(m, nil)
   158  	if len(errs) > 0 {
   159  		return errs[0]
   160  	}
   161  	return nil
   162  }
   163  
   164  // errno returns a system error code.
   165  func errno(err error) error {
   166  	if err != nil {
   167  		if en, ok := err.(syscall.Errno); ok && en == 0 {
   168  			return syscall.EINVAL
   169  		}
   170  		return err
   171  	}
   172  	return syscall.EINVAL
   173  }
   174  
   175  // mmap wraps the system call for mmap.
   176  func mmap(addr, length uintptr, prot, flags int, fd uintptr, offset int64) (uintptr, error) {
   177  	if prot < 0 || flags < 0 || offset < 0 {
   178  		return 0, syscall.EINVAL
   179  	}
   180  	result, _, err := syscall.Syscall6(syscall.SYS_MMAP, addr, length, uintptr(prot), uintptr(flags), fd, uintptr(offset))
   181  	if err != 0 {
   182  		return 0, errno(err)
   183  	}
   184  	return result, nil
   185  }
   186  
   187  // mlock wraps the system call for mlock.
   188  func mlock(addr, length uintptr) error {
   189  	_, _, err := syscall.Syscall(syscall.SYS_MLOCK, addr, length, 0)
   190  	if err != 0 {
   191  		return errno(err)
   192  	}
   193  	return err
   194  }
   195  
   196  // munlock wraps the system call for munlock.
   197  func munlock(addr, length uintptr) error {
   198  	_, _, err := syscall.Syscall(syscall.SYS_MUNLOCK, addr, length, 0)
   199  	if err != 0 {
   200  		return errno(err)
   201  	}
   202  	return nil
   203  }
   204  
   205  // msync wraps the system call for msync.
   206  func msync(addr, length uintptr) error {
   207  	_, _, err := syscall.Syscall(syscall.SYS_MSYNC, addr, length, syscall.MS_SYNC)
   208  	if err != 0 {
   209  		return errno(err)
   210  	}
   211  	return nil
   212  }
   213  
   214  // munmap wraps the system call for munmap.
   215  func munmap(addr, length uintptr) error {
   216  	_, _, err := syscall.Syscall(syscall.SYS_MUNMAP, addr, length, 0)
   217  	if err != 0 {
   218  		return errno(err)
   219  	}
   220  	return nil
   221  }