Skip to content

Commit 7601e56

Browse files
authored
Merge pull request #1 from nsip/mmap-windows
windows support
2 parents e87a6e4 + 1220e67 commit 7601e56

File tree

3 files changed

+378
-0
lines changed

3 files changed

+378
-0
lines changed

consts_win.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// +build windows
2+
3+
package gommap
4+
5+
const (
6+
// RDONLY maps the memory read-only.
7+
// Attempts to write to the MMap object will result in undefined behavior.
8+
RDONLY = 0
9+
// RDWR maps the memory as read-write. Writes to the MMap object will update the
10+
// underlying file.
11+
RDWR = 1 << iota
12+
// COPY maps the memory as copy-on-write. Writes to the MMap object will affect
13+
// memory, but the underlying file will remain unchanged.
14+
COPY
15+
// If EXEC is set, the mapped memory is marked as executable.
16+
EXEC
17+
)
18+
19+
type ProtFlags uint
20+
21+
const (
22+
// PROT_NONE ProtFlags = 0x0
23+
// PROT_READ ProtFlags = 0x1
24+
// PROT_WRITE ProtFlags = 0x2
25+
// PROT_EXEC ProtFlags = 0x4
26+
27+
// PROT_NONE ProtFlags = 0x0 /* No PROT_NONE on windows */
28+
PROT_READ ProtFlags = RDONLY
29+
PROT_WRITE ProtFlags = RDWR
30+
PROT_COPY ProtFlags = COPY /* PROT_COPY Only on windows, use this flag to implement MAP_PRIVATE */
31+
PROT_EXEC ProtFlags = EXEC
32+
)
33+
34+
/* on win, use ProtFlags to simulate MapFlags */
35+
36+
type MapFlags uint
37+
38+
const (
39+
MAP_SHARED MapFlags = 0x1
40+
MAP_PRIVATE MapFlags = 0x2
41+
MAP_FIXED MapFlags = 0x10
42+
MAP_ANONYMOUS MapFlags = 0x20
43+
MAP_GROWSDOWN MapFlags = 0x100
44+
MAP_LOCKED MapFlags = 0x2000
45+
MAP_NONBLOCK MapFlags = 0x10000
46+
MAP_NORESERVE MapFlags = 0x4000
47+
MAP_POPULATE MapFlags = 0x8000
48+
)
49+
50+
type SyncFlags uint
51+
52+
const (
53+
MS_SYNC SyncFlags = 0x4
54+
MS_ASYNC SyncFlags = 0x1
55+
MS_INVALIDATE SyncFlags = 0x2
56+
)
57+
58+
type AdviseFlags uint
59+
60+
const (
61+
MADV_NORMAL AdviseFlags = 0x0
62+
MADV_RANDOM AdviseFlags = 0x1
63+
MADV_SEQUENTIAL AdviseFlags = 0x2
64+
MADV_WILLNEED AdviseFlags = 0x3
65+
MADV_DONTNEED AdviseFlags = 0x4
66+
MADV_REMOVE AdviseFlags = 0x9
67+
MADV_DONTFORK AdviseFlags = 0xa
68+
MADV_DOFORK AdviseFlags = 0xb
69+
)

gommap_win.go

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// This package offers the MMap type that manipulates a memory mapped file or
2+
// device.
3+
//
4+
// IMPORTANT NOTE (1): The MMap type is backed by an unsafe memory region,
5+
// which is not covered by the normal rules of Go's memory management. If a
6+
// slice is taken out of it, and then the memory is explicitly unmapped through
7+
// one of the available methods, both the MMap value itself and the slice
8+
// obtained will now silently point to invalid memory. Attempting to access
9+
// data in them will crash the application.
10+
11+
// +build windows
12+
13+
package gommap
14+
15+
import (
16+
"errors"
17+
"os"
18+
"reflect"
19+
"syscall"
20+
"unsafe"
21+
)
22+
23+
// The MMap type represents a memory mapped file or device. The slice offers
24+
// direct access to the memory mapped content.
25+
//
26+
// IMPORTANT: Please see note in the package documentation regarding the way
27+
// in which this type behaves.
28+
type MMap []byte
29+
30+
// In order to implement 'Protect', use this to get back the original MMap properties from the memory address.
31+
var mmapAttrs = map[uintptr]*struct {
32+
fd uintptr
33+
offset int64
34+
length int64
35+
prot ProtFlags
36+
flags MapFlags
37+
}{}
38+
39+
// GetFileSize gets the file length from its fd
40+
func GetFileSize(fd uintptr) (int64, error) {
41+
fh := syscall.Handle(fd)
42+
fsize, err := syscall.Seek(syscall.Handle(fh), 0, 2)
43+
syscall.Seek(fh, 0, 0)
44+
return fsize, err
45+
}
46+
47+
// Map creates a new mapping in the virtual address space of the calling process.
48+
// This function will attempt to map the entire file by using the fstat system
49+
// call with the provided file descriptor to discover its length.
50+
func Map(fd uintptr, prot ProtFlags, flags MapFlags) (MMap, error) {
51+
return MapRegion(fd, 0, -1, prot, flags)
52+
}
53+
54+
// MapRegion creates a new mapping in the virtual address space of the calling
55+
// process, using the specified region of the provided file or device. If -1 is
56+
// provided as length, this function will attempt to map until the end of the
57+
// provided file descriptor by using the fstat system call to discover its
58+
// length.
59+
func MapRegion(fd uintptr, offset, length int64, prot ProtFlags, flags MapFlags) (MMap, error) {
60+
if offset%int64(os.Getpagesize()) != 0 {
61+
return nil, errors.New("offset parameter must be a multiple of the system's page size")
62+
}
63+
if length == -1 {
64+
length, _ = GetFileSize(fd)
65+
}
66+
/* on windows, use PROT_COPY to do the same thing as linux MAP_PRIVATE flag do */
67+
if flags == MAP_PRIVATE {
68+
prot = PROT_COPY
69+
}
70+
// return mmap(length, uintptr(prot), uintptr(flags), fd, offset)
71+
72+
/*******************************/
73+
m, e := mmap(length, uintptr(prot), uintptr(flags), fd, offset)
74+
dh := (*reflect.SliceHeader)(unsafe.Pointer(&m))
75+
mmapAttrs[dh.Data] = &struct {
76+
fd uintptr
77+
offset int64
78+
length int64
79+
prot ProtFlags
80+
flags MapFlags
81+
}{fd, offset, length, prot, flags}
82+
return m, e
83+
}
84+
85+
func (mmap *MMap) header() *reflect.SliceHeader {
86+
return (*reflect.SliceHeader)(unsafe.Pointer(mmap))
87+
}
88+
89+
// UnsafeUnmap deletes the memory mapped region defined by the mmap slice. This
90+
// will also flush any remaining changes, if necessary. Using mmap or any
91+
// other slices based on it after this method has been called will crash the
92+
// application.
93+
func (mmap MMap) UnsafeUnmap() error {
94+
dh := mmap.header()
95+
return unmap(dh.Data, uintptr(dh.Len))
96+
}
97+
98+
// Sync flushes changes made to the region determined by the mmap slice
99+
// back to the device. Without calling this method, there are no guarantees
100+
// that changes will be flushed back before the region is unmapped. The
101+
// flags parameter specifies whether flushing should be done synchronously
102+
// (before the method returns) with MS_SYNC, or asynchronously (flushing is just
103+
// scheduled) with MS_ASYNC.
104+
func (mmap MMap) Sync(flags SyncFlags) error {
105+
dh := mmap.header()
106+
return flush(dh.Data, uintptr(dh.Len))
107+
}
108+
109+
// // Advise advises the kernel about how to handle the mapped memory
110+
// // region in terms of input/output paging within the memory region
111+
// // defined by the mmap slice.
112+
// func (mmap MMap) Advise(advice AdviseFlags) error {
113+
// // rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap))
114+
// // _, _, err := syscall.Syscall(syscall.SYS_MADVISE, uintptr(rh.Data), uintptr(rh.Len), uintptr(advice))
115+
// // if err != 0 {
116+
// // return err
117+
// // }
118+
// // return nil
119+
// }
120+
121+
// Protect changes the protection flags for the memory mapped region
122+
// defined by the mmap slice.
123+
// We use unmap & map again to implement this on windows. So can only change the protect flags on the whole
124+
func (mmap *MMap) Protect(prot ProtFlags) (err error) {
125+
dh := mmap.header()
126+
var m MMap
127+
if err = mmap.UnsafeUnmap(); err == nil {
128+
fd, offset, length, flags := mmapAttrs[dh.Data].fd, mmapAttrs[dh.Data].offset, mmapAttrs[dh.Data].length, mmapAttrs[dh.Data].flags
129+
mmapAttrs[dh.Data] = nil
130+
if m, err = MapRegion(fd, offset, length, prot, flags); err == nil {
131+
mmap = &m
132+
}
133+
}
134+
return
135+
}
136+
137+
// Lock locks the mapped region defined by the mmap slice,
138+
// preventing it from being swapped out.
139+
func (mmap MMap) Lock() error {
140+
dh := mmap.header()
141+
return lock(dh.Data, uintptr(dh.Len))
142+
}
143+
144+
// Unlock unlocks the mapped region defined by the mmap slice,
145+
// allowing it to swap out again.
146+
func (mmap MMap) Unlock() error {
147+
dh := mmap.header()
148+
return unlock(dh.Data, uintptr(dh.Len))
149+
}
150+
151+
// // IsResident returns a slice of booleans informing whether the respective
152+
// // memory page in mmap was mapped at the time the call was made.
153+
// func (mmap MMap) IsResident() ([]bool, error) {
154+
// pageSize := os.Getpagesize()
155+
// result := make([]bool, (len(mmap)+pageSize-1)/pageSize)
156+
// rh := *(*reflect.SliceHeader)(unsafe.Pointer(&mmap))
157+
// resulth := *(*reflect.SliceHeader)(unsafe.Pointer(&result))
158+
// _, _, err := syscall.Syscall(syscall.SYS_MINCORE, uintptr(rh.Data), uintptr(rh.Len), uintptr(resulth.Data))
159+
// for i := range result {
160+
// *(*uint8)(unsafe.Pointer(&result[i])) &= 1
161+
// }
162+
// if err != 0 {
163+
// return nil, err
164+
// }
165+
// return result, nil
166+
// }

mmap_windows.go

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Copyright 2011 Evan Shaw. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// +build windows
6+
7+
package gommap
8+
9+
import (
10+
"errors"
11+
"os"
12+
"sync"
13+
"syscall"
14+
)
15+
16+
// mmap on Windows is a two-step process.
17+
// First, we call CreateFileMapping to get a handle.
18+
// Then, we call MapviewToFile to get an actual pointer into memory.
19+
// Because we want to emulate a POSIX-style mmap, we don't want to expose
20+
// the handle -- only the pointer. We also want to return only a byte slice,
21+
// not a struct, so it's convenient to manipulate.
22+
23+
// We keep this map so that we can get back the original handle from the memory address.
24+
var handleLock sync.Mutex
25+
var handleMap = map[uintptr]syscall.Handle{}
26+
var addrLocked = map[uintptr]bool{}
27+
28+
func mmap(len int64, prot, flags, hfile uintptr, off int64) ([]byte, error) {
29+
flProtect := uint32(syscall.PAGE_READONLY)
30+
dwDesiredAccess := uint32(syscall.FILE_MAP_READ)
31+
switch {
32+
case prot&COPY != 0:
33+
flProtect = syscall.PAGE_WRITECOPY
34+
dwDesiredAccess = syscall.FILE_MAP_COPY
35+
case prot&RDWR != 0:
36+
flProtect = syscall.PAGE_READWRITE
37+
dwDesiredAccess = syscall.FILE_MAP_WRITE
38+
}
39+
if prot&EXEC != 0 {
40+
flProtect <<= 4
41+
dwDesiredAccess |= syscall.FILE_MAP_EXECUTE
42+
}
43+
44+
// The maximum size is the area of the file, starting from 0,
45+
// that we wish to allow to be mappable. It is the sum of
46+
// the length the user requested, plus the offset where that length
47+
// is starting from. This does not map the data into memory.
48+
maxSizeHigh := uint32((off + len) >> 32)
49+
maxSizeLow := uint32((off + len) & 0xFFFFFFFF)
50+
// TODO: Do we need to set some security attributes? It might help portability.
51+
h, errno := syscall.CreateFileMapping(syscall.Handle(hfile), nil, flProtect, maxSizeHigh, maxSizeLow, nil)
52+
if h == 0 {
53+
if errno == syscall.ERROR_ACCESS_DENIED {
54+
return nil, syscall.EACCES
55+
}
56+
return nil, os.NewSyscallError("CreateFileMapping", errno)
57+
}
58+
59+
// Actually map a view of the data into memory. The view's size
60+
// is the length the user requested.
61+
fileOffsetHigh := uint32(off >> 32)
62+
fileOffsetLow := uint32(off & 0xFFFFFFFF)
63+
addr, errno := syscall.MapViewOfFile(h, dwDesiredAccess, fileOffsetHigh, fileOffsetLow, uintptr(len))
64+
if addr == 0 {
65+
return nil, os.NewSyscallError("MapViewOfFile", errno)
66+
}
67+
handleLock.Lock()
68+
handleMap[addr] = h
69+
handleLock.Unlock()
70+
71+
m := MMap{}
72+
dh := m.header()
73+
dh.Data = addr
74+
dh.Len = int(len)
75+
dh.Cap = dh.Len
76+
77+
return m, nil
78+
}
79+
80+
func flush(addr, len uintptr) error {
81+
errno := syscall.FlushViewOfFile(addr, len)
82+
if errno != nil {
83+
return os.NewSyscallError("FlushViewOfFile", errno)
84+
}
85+
86+
handleLock.Lock()
87+
defer handleLock.Unlock()
88+
handle, ok := handleMap[addr]
89+
if !ok {
90+
// should be impossible; we would've errored above
91+
return errors.New("unknown base address")
92+
}
93+
94+
errno = syscall.FlushFileBuffers(handle)
95+
return os.NewSyscallError("FlushFileBuffers", errno)
96+
}
97+
98+
func lock(addr, len uintptr) error {
99+
if addrLocked[addr] {
100+
return nil
101+
}
102+
errno := syscall.VirtualLock(addr, len)
103+
if errno == nil {
104+
addrLocked[addr] = true
105+
}
106+
return os.NewSyscallError("VirtualLock", errno)
107+
}
108+
109+
func unlock(addr, len uintptr) error {
110+
if !addrLocked[addr] {
111+
return nil
112+
}
113+
errno := syscall.VirtualUnlock(addr, len)
114+
if errno == nil {
115+
addrLocked[addr] = false
116+
}
117+
return os.NewSyscallError("VirtualUnlock", errno)
118+
}
119+
120+
func unmap(addr, len uintptr) error {
121+
flush(addr, len)
122+
// Lock the UnmapViewOfFile along with the handleMap deletion.
123+
// As soon as we unmap the view, the OS is free to give the
124+
// same addr to another new map. We don't want another goroutine
125+
// to insert and remove the same addr into handleMap while
126+
// we're trying to remove our old addr/handle pair.
127+
handleLock.Lock()
128+
defer handleLock.Unlock()
129+
err := syscall.UnmapViewOfFile(addr)
130+
if err != nil {
131+
return err
132+
}
133+
134+
handle, ok := handleMap[addr]
135+
if !ok {
136+
// should be impossible; we would've errored above
137+
return errors.New("unknown base address")
138+
}
139+
delete(handleMap, addr)
140+
141+
e := syscall.CloseHandle(syscall.Handle(handle))
142+
return os.NewSyscallError("CloseHandle", e)
143+
}

0 commit comments

Comments
 (0)