├── LICENSE ├── README.md ├── memcpy.go ├── random.go ├── shm.go ├── shm_generic.go ├── shm_linux.go ├── shm_test.go ├── slice.go └── sudo_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014 Vastech SA (PTY) LTD 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #shm 2 | 3 | The shm package provides various functions to assist with performing shared memory operations in go. 4 | 5 | Dependency: 6 | github.com/jsgilmore/mount 7 | 8 | [![Build Status](https://drone.io/github.com/jsgilmore/shm/status.png)](https://drone.io/github.com/jsgilmore/shm/latest) 9 | -------------------------------------------------------------------------------- /memcpy.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build linux 16 | 17 | package shm 18 | 19 | //#include 20 | import "C" 21 | import "unsafe" 22 | 23 | func memcpy(dest, src []byte) int { 24 | n := len(src) 25 | if len(dest) < len(src) { 26 | n = len(dest) 27 | } 28 | if n == 0 { 29 | return 0 30 | } 31 | C.memcpy(unsafe.Pointer(&dest[0]), unsafe.Pointer(&src[0]), C.size_t(n)) 32 | return n 33 | } 34 | 35 | func memmove(dest, src []byte) int { 36 | n := len(src) 37 | if len(dest) < len(src) { 38 | n = len(dest) 39 | } 40 | if n == 0 { 41 | return 0 42 | } 43 | C.memmove(unsafe.Pointer(&dest[0]), unsafe.Pointer(&src[0]), C.size_t(n)) 44 | return n 45 | } 46 | -------------------------------------------------------------------------------- /random.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shm 16 | 17 | import ( 18 | "math/rand" 19 | "sync" 20 | "time" 21 | ) 22 | 23 | type lockedSource struct { 24 | lk sync.Mutex 25 | src rand.Source 26 | } 27 | 28 | func (r *lockedSource) Int63() (n int64) { 29 | r.lk.Lock() 30 | n = r.src.Int63() 31 | r.lk.Unlock() 32 | return 33 | } 34 | 35 | func (r *lockedSource) Seed(seed int64) { 36 | r.lk.Lock() 37 | r.src.Seed(seed) 38 | r.lk.Unlock() 39 | } 40 | 41 | var rng = rand.New(&lockedSource{src: rand.NewSource(time.Now().UnixNano())}) 42 | -------------------------------------------------------------------------------- /shm.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Package shm allows for opening and operating on shared memory devices 16 | package shm 17 | 18 | import ( 19 | "errors" 20 | "fmt" 21 | "os" 22 | "runtime" 23 | "syscall" 24 | ) 25 | 26 | // The Buffer interface represents a shared memory buffer 27 | type Buffer interface { 28 | Bytes() []byte 29 | Len() int 30 | Close() error 31 | CloseFile() error 32 | String() string 33 | File() *os.File 34 | LocalFile() *os.File 35 | Advise(offset, length, advice int) error 36 | Lock(offset, length int) error 37 | LockAll() error 38 | Unlock(offset, length int) error 39 | UnlockAll() error 40 | } 41 | 42 | type sharedBuffer struct { 43 | file *os.File 44 | localFile *os.File 45 | buf []byte 46 | stack []byte 47 | } 48 | 49 | // String identifies the shared memory buffer by its address and file descriptor 50 | func (buf *sharedBuffer) String() string { 51 | if buf == nil { 52 | return "SharedBuffer:" 53 | } 54 | fd := -1 55 | if buf.file != nil { 56 | fd = int(buf.file.Fd()) 57 | } 58 | return fmt.Sprintf("SharedBuffer[%p]{Fd:%d}", buf, fd) 59 | } 60 | 61 | const keepStack = false 62 | 63 | func stackToKeep() []byte { 64 | if !keepStack { 65 | return nil 66 | } 67 | buf := make([]byte, 16384) 68 | n := runtime.Stack(buf, false) 69 | return buf[:n] 70 | } 71 | 72 | func (buf *sharedBuffer) finalize() { 73 | if buf.stack != nil { 74 | panic("shm: finalize: open buffer allocated at: " + string(buf.stack)) 75 | } 76 | panic("shm: finalize: open buffer") 77 | } 78 | 79 | // Buffer length in bytes 80 | func (buf *sharedBuffer) Len() int { 81 | return len(buf.buf) 82 | } 83 | 84 | // Bytes returns the contents of the buffer 85 | func (buf *sharedBuffer) Bytes() []byte { 86 | if buf.buf == nil { 87 | panic("shm: Bytes: buffer closed") 88 | } 89 | // an unexpected fault address error here can be caused by a 90 | // PROT_READ buffer being read without having been written 91 | return buf.buf 92 | } 93 | 94 | // File returns the file descriptor 95 | func (buf *sharedBuffer) File() *os.File { 96 | return buf.file 97 | } 98 | 99 | // File returns the local file descriptor 100 | func (buf *sharedBuffer) LocalFile() *os.File { 101 | return buf.localFile 102 | } 103 | 104 | // LockAll performs an Mlock of all buffer bytes 105 | func (buf *sharedBuffer) LockAll() error { 106 | return buf.Lock(0, buf.Len()) 107 | } 108 | 109 | // UnlockAll performs an Mulock of all buffer bytes 110 | func (buf *sharedBuffer) UnlockAll() error { 111 | return buf.Unlock(0, buf.Len()) 112 | } 113 | 114 | var errOffsetLength = errors.New("invalid offset/length") 115 | 116 | func checkOffsetLength(b []byte, offset, length int) error { 117 | // negative checks, zero/overflow check, bounds check 118 | if offset < 0 || length < 0 || offset+length <= offset || offset+length > len(b) { 119 | return errOffsetLength 120 | } 121 | return nil 122 | } 123 | 124 | var errWrongSize = errors.New("shm: NewBufferFile: wrong size") 125 | 126 | // NewBufferFile maps a file to shared memory and returns a handle to the shared memory buffer 127 | func NewBufferFile(file *os.File, size, prot int) (Buffer, error) { 128 | fi, err := file.Stat() 129 | if err != nil { 130 | return nil, err 131 | } 132 | sys := fi.Sys().(*syscall.Stat_t) 133 | if sys.Size != int64(size) { 134 | return nil, errWrongSize 135 | } 136 | 137 | // Dup to allow file parameter to be closed regardless 138 | fd, err := syscall.Dup(int(file.Fd())) 139 | if err != nil { 140 | return nil, err 141 | } 142 | const flags = syscall.MAP_SHARED 143 | b, err := syscall.Mmap(fd, 0, size, prot, flags) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | // localFile is nil because fd is from somewhere else 149 | buf := &sharedBuffer{os.NewFile(uintptr(fd), ""), nil, b, stackToKeep()} 150 | runtime.SetFinalizer(buf, (*sharedBuffer).finalize) 151 | 152 | return buf, nil 153 | } 154 | -------------------------------------------------------------------------------- /shm_generic.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build !linux 16 | 17 | package shm 18 | 19 | const ( 20 | PROT_READ = 0 21 | PROT_WRITE = 0 22 | PROT_RDWR = 0 23 | ) 24 | 25 | func (buf *sharedBuffer) Close() error { 26 | panic("!linux: not implemented") 27 | } 28 | 29 | func (buf *sharedBuffer) ReadOnly() { 30 | panic("!linux: not implemented") 31 | } 32 | 33 | func (buf *sharedBuffer) Advise(offset, length, advice int) error { 34 | panic("!linux: not implemented") 35 | } 36 | 37 | func (buf *sharedBuffer) Lock(offset, length int) error { 38 | panic("!linux: not implemented") 39 | } 40 | 41 | func (buf *sharedBuffer) Unlock(offset, length int) error { 42 | panic("!linux: not implemented") 43 | } 44 | 45 | func (buf *sharedBuffer) CloseFile() error { 46 | panic("!linux: not implemented") 47 | } 48 | 49 | func NewBufferHugepages(size, prot int) (Buffer, error) { 50 | panic("!linux: not implemented") 51 | } 52 | -------------------------------------------------------------------------------- /shm_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // +build linux 16 | 17 | package shm 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "os" 23 | "runtime" 24 | "syscall" 25 | ) 26 | 27 | const ( 28 | PROT_RDWR = syscall.PROT_READ | syscall.PROT_WRITE 29 | PROT_READ = syscall.PROT_READ 30 | ) 31 | 32 | const ( 33 | devHugepages = "/dev/hugepages" 34 | devShm = "/dev/shm" 35 | shmPrefix = "shm-" 36 | shmDirFormat = "%s/" + shmPrefix + "%d" 37 | ) 38 | 39 | func uniquePath(path string) string { 40 | return fmt.Sprintf(shmDirFormat, path, uint64(rng.Int63())) 41 | } 42 | 43 | var errInvalidFS = errors.New("shm: invalid file system") 44 | 45 | func createBuffer(dir string, size int) ([]*os.File, error) { 46 | var stat syscall.Statfs_t 47 | err := syscall.Statfs(dir, &stat) 48 | if err != nil { 49 | return nil, os.NewSyscallError("statfs", err) 50 | } 51 | const HUGETLBFS_MAGIC = 0x958458f6 52 | const TMPFS_MAGIC = 0x1021994 53 | if stat.Type != HUGETLBFS_MAGIC && stat.Type != TMPFS_MAGIC { 54 | return nil, errInvalidFS 55 | } 56 | 57 | // Quick check for an impending ENOMEM 58 | if stat.Bavail == 0 { 59 | return nil, syscall.ENOMEM 60 | } 61 | 62 | path := uniquePath(dir) 63 | // O_EXCL ensures that we never open a file twice 64 | const flags = os.O_RDWR | os.O_CREATE | os.O_EXCL | os.O_SYNC | syscall.O_NOATIME 65 | file, err := os.OpenFile(path, flags, 0660) 66 | for err != nil { 67 | if os.IsExist(err) { 68 | path = uniquePath(path) 69 | file, err = os.OpenFile(path, flags, 0660) 70 | continue 71 | } else { 72 | return nil, err 73 | } 74 | } 75 | 76 | localFile, err := os.Open(path) 77 | if err != nil { 78 | checkClose(file) 79 | if err := os.Remove(path); err != nil { 80 | panic(err) 81 | } 82 | return nil, err 83 | } 84 | 85 | if err := os.Remove(path); err != nil { 86 | checkClose(file) 87 | checkClose(localFile) 88 | return nil, err 89 | } 90 | 91 | if stat.Type == TMPFS_MAGIC { 92 | // reserve space. only needed for tmpfs. 93 | if err := file.Truncate(int64(size)); err != nil { 94 | checkClose(file) 95 | checkClose(localFile) 96 | return nil, err 97 | } 98 | } 99 | 100 | return []*os.File{file, localFile}, nil 101 | } 102 | 103 | // NewBufferMemoryFs maps a mounted file system into shared memory and returns a handle to the shared memory buffer 104 | func NewBufferMemoryFs(dir string, size, prot int) (Buffer, error) { 105 | files, err := createBuffer(dir, size) 106 | if err != nil { 107 | return nil, err 108 | } 109 | file, localFile := files[0], files[1] 110 | 111 | // MAP_NORESERVE is specifically not used because it allows mmap 112 | // to succeed even when there are no huge pages reserved. 113 | const flags = syscall.MAP_SHARED | syscall.MAP_POPULATE 114 | b, err := syscall.Mmap(int(file.Fd()), 0, size, prot, flags) 115 | if err != nil { 116 | checkClose(file) 117 | checkClose(localFile) 118 | return nil, os.NewSyscallError("NewBufferMemoryFs: mmap", err) 119 | } 120 | 121 | buf := &sharedBuffer{file, localFile, b, stackToKeep()} 122 | 123 | // Lock everything to avoid SIGBUS when trying to use memory 124 | // mapped on a tmpfs that becomes full after the Statfs check in 125 | // createBuffer. 126 | if err := buf.LockAll(); err != nil { 127 | checkClose(buf) 128 | return nil, err 129 | } 130 | 131 | runtime.SetFinalizer(buf, (*sharedBuffer).finalize) 132 | 133 | return buf, nil 134 | } 135 | 136 | // NewBufferTmpfs maps /dev/shm into shared memory and returns a handle to the shared memory buffer 137 | func NewBufferTmpfs(size, prot int) (Buffer, error) { 138 | return NewBufferMemoryFs(devShm, size, prot) 139 | } 140 | 141 | // NewBufferTmpfs maps /dev/hugepages into shared memory and returns a handle to the shared memory buffer 142 | func NewBufferHugepages(size, prot int) (Buffer, error) { 143 | if prot == PROT_READ { 144 | // PROT_READ breaks mlock of hugepages. 145 | panic("NewBufferHugepages: PROT_READ wont't work here") 146 | } 147 | return NewBufferMemoryFs(devHugepages, size, prot) 148 | } 149 | 150 | // CloseFile closes both shared memory file descriptors 151 | func (buf *sharedBuffer) CloseFile() error { 152 | if buf.file != nil { 153 | if err := buf.file.Close(); err != nil { 154 | panic("shm: Close: " + err.Error()) 155 | } 156 | buf.file = nil 157 | } 158 | if buf.localFile != nil { 159 | if err := buf.localFile.Close(); err != nil { 160 | panic("shm: Close: " + err.Error()) 161 | } 162 | buf.localFile = nil 163 | } 164 | return nil 165 | } 166 | 167 | // Close Munmaps the shared memory and closes the file descriptors 168 | func (buf *sharedBuffer) Close() error { 169 | if buf.buf != nil { 170 | if err := syscall.Munmap(buf.buf); err != nil { 171 | panic("shm: Close: munmap: " + err.Error()) 172 | } 173 | buf.buf = nil 174 | } 175 | if err := buf.CloseFile(); err != nil { 176 | return err 177 | } 178 | runtime.SetFinalizer(buf, nil) 179 | // the errors above are fatal 180 | return nil 181 | } 182 | 183 | func (buf *sharedBuffer) Advise(offset, length, advice int) error { 184 | b := buf.Bytes() 185 | err := checkOffsetLength(b, offset, length) 186 | if err == nil { 187 | err = syscall.Madvise(b[offset:offset+length], advice) 188 | } 189 | return os.NewSyscallError("shm: madvise", err) 190 | } 191 | 192 | // Lock locks a region of the mapped shared memory 193 | func (buf *sharedBuffer) Lock(offset, length int) error { 194 | b := buf.Bytes() 195 | err := checkOffsetLength(b, offset, length) 196 | if err == nil { 197 | err = syscall.Mlock(b[offset : offset+length]) 198 | } 199 | return os.NewSyscallError("shm: mlock", err) 200 | } 201 | 202 | // Unlock unlocks a region of the mapped shared memory 203 | func (buf *sharedBuffer) Unlock(offset, length int) error { 204 | b := buf.Bytes() 205 | err := checkOffsetLength(b, offset, length) 206 | if err == nil { 207 | err = syscall.Munlock(b[offset : offset+length]) 208 | } 209 | return os.NewSyscallError("shm: munlock", err) 210 | } 211 | 212 | type closer interface { 213 | Close() error 214 | } 215 | 216 | func checkClose(c closer) { 217 | if err := c.Close(); err != nil { 218 | panic(err) 219 | } 220 | } 221 | 222 | func checkError(err error) { 223 | if err != nil { 224 | panic(err) 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /shm_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shm 16 | 17 | import ( 18 | "flag" 19 | "github.com/jsgilmore/mount" 20 | "math" 21 | "os" 22 | "runtime" 23 | "sync" 24 | "syscall" 25 | "testing" 26 | ) 27 | 28 | const size64MiB = 64 << 20 29 | const size1G = 1024 << 20 30 | 31 | func newBuffer(size, prot int) Buffer { 32 | buf, err := NewBufferTmpfs(size, prot) 33 | if err != nil { 34 | panic(err) 35 | } 36 | return buf 37 | } 38 | 39 | func testShm(newBuffer func(_, _ int) (Buffer, error)) { 40 | buf, err := newBuffer(size64MiB, PROT_RDWR) 41 | if err != nil { 42 | panic(err) 43 | } 44 | defer checkClose(buf) 45 | fd2, err := syscall.Dup(int(buf.File().Fd())) 46 | if err != nil { 47 | panic(err) 48 | } 49 | file2 := os.NewFile(uintptr(fd2), "") 50 | buf2, err := NewBufferFile(file2, buf.Len(), PROT_READ) 51 | if err != nil { 52 | panic(err) 53 | } 54 | defer checkClose(buf2) 55 | 56 | // NewBufferFile duped file2 57 | checkClose(file2) 58 | 59 | bufSlice := buf.Bytes() 60 | for i := 0; i < len(bufSlice); i++ { 61 | bufSlice[i] = byte(i) 62 | } 63 | buf2Slice := buf2.Bytes() 64 | if len(bufSlice) != len(buf2Slice) { 65 | panic("slices must have the same length") 66 | } 67 | for i := 0; i < len(bufSlice); i++ { 68 | if bufSlice[i] != buf2Slice[i] { 69 | panic("slices not equal") 70 | } 71 | } 72 | buf2.Lock(0, buf2.Len()) 73 | buf2.Unlock(0, buf2.Len()) 74 | } 75 | 76 | func stat(dir string) syscall.Statfs_t { 77 | var st syscall.Statfs_t 78 | err := syscall.Statfs(dir, &st) 79 | if err != nil { 80 | panic(err) 81 | } 82 | return st 83 | } 84 | 85 | func TestShmTmpfs(t *testing.T) { 86 | setup() 87 | st := stat(devShm) 88 | testShm(NewBufferTmpfs) 89 | if st != stat(devShm) { 90 | panic("stat mismatch") 91 | } 92 | } 93 | 94 | func TestShmDevHugepages(t *testing.T) { 95 | setup() 96 | st := stat(devHugepages) 97 | testShm(NewBufferHugepages) 98 | if st != stat(devHugepages) { 99 | panic("stat mismatch") 100 | } 101 | } 102 | 103 | func TestProtReadTmpfs(t *testing.T) { 104 | setup() 105 | st := stat(devShm) 106 | // this doesn't work with hugepages 107 | buf, err := NewBufferTmpfs(size1G, PROT_READ) 108 | if err != nil { 109 | panic(err) 110 | } 111 | if err := buf.LockAll(); err != nil { 112 | panic(err) 113 | } 114 | checkClose(buf) 115 | if st != stat(devShm) { 116 | panic("stat mismatch") 117 | } 118 | } 119 | 120 | func BenchmarkAllocate(b *testing.B) { 121 | setup() 122 | b.SetBytes(int64(size64MiB)) 123 | origBuf := make([]byte, size64MiB) 124 | b.ResetTimer() 125 | b.StartTimer() 126 | for n := b.N; n > 0; n-- { 127 | buf := newBuffer(size64MiB, PROT_RDWR) 128 | copy(buf.Bytes(), origBuf) 129 | buf.Close() 130 | } 131 | b.StopTimer() 132 | } 133 | 134 | func BenchmarkCopySliceToSlice(b *testing.B) { 135 | setup() 136 | b.SetBytes(int64(size64MiB)) 137 | dest := make([]byte, size64MiB) 138 | src := make([]byte, size64MiB) 139 | b.ResetTimer() 140 | b.StartTimer() 141 | for n := b.N; n > 0; n-- { 142 | copy(dest, src) 143 | } 144 | b.StopTimer() 145 | } 146 | 147 | func BenchmarkMemmoveSliceToSlice(b *testing.B) { 148 | setup() 149 | b.SetBytes(int64(size64MiB)) 150 | dest := make([]byte, size64MiB) 151 | src := make([]byte, size64MiB) 152 | b.ResetTimer() 153 | b.StartTimer() 154 | for n := b.N; n > 0; n-- { 155 | memmove(dest, src) 156 | } 157 | b.StopTimer() 158 | } 159 | 160 | func BenchmarkCopySliceToShm(b *testing.B) { 161 | setup() 162 | b.SetBytes(int64(size64MiB)) 163 | origBuf := make([]byte, size64MiB) 164 | buf := newBuffer(size64MiB, PROT_RDWR) 165 | b.ResetTimer() 166 | b.StartTimer() 167 | for n := b.N; n > 0; n-- { 168 | copy(buf.Bytes(), origBuf) 169 | } 170 | b.StopTimer() 171 | buf.Close() 172 | } 173 | 174 | func BenchmarkCopyShmToShm(b *testing.B) { 175 | setup() 176 | b.SetBytes(int64(size64MiB)) 177 | origBuf := make([]byte, size64MiB) 178 | buf1 := newBuffer(size64MiB, PROT_RDWR) 179 | copy(buf1.Bytes(), origBuf) 180 | buf2 := newBuffer(size64MiB, PROT_RDWR) 181 | b.ResetTimer() 182 | b.StartTimer() 183 | for n := b.N; n > 0; n-- { 184 | copy(buf2.Bytes(), buf1.Bytes()) 185 | } 186 | b.StopTimer() 187 | buf1.Close() 188 | buf2.Close() 189 | } 190 | 191 | func BenchmarkMemcpyShmToShm(b *testing.B) { 192 | setup() 193 | b.SetBytes(int64(size64MiB)) 194 | origBuf := make([]byte, size64MiB) 195 | buf1 := newBuffer(size64MiB, PROT_RDWR) 196 | copy(buf1.Bytes(), origBuf) 197 | buf2 := newBuffer(size64MiB, PROT_RDWR) 198 | b.ResetTimer() 199 | b.StartTimer() 200 | for n := b.N; n > 0; n-- { 201 | memcpy(buf2.Bytes(), buf1.Bytes()) 202 | } 203 | b.StopTimer() 204 | buf1.Close() 205 | buf2.Close() 206 | } 207 | 208 | func BenchmarkMemmoveShmToShm(b *testing.B) { 209 | setup() 210 | b.SetBytes(int64(size64MiB)) 211 | origBuf := make([]byte, size64MiB) 212 | buf1 := newBuffer(size64MiB, PROT_RDWR) 213 | copy(buf1.Bytes(), origBuf) 214 | buf2 := newBuffer(size64MiB, PROT_RDWR) 215 | b.ResetTimer() 216 | b.StartTimer() 217 | for n := b.N; n > 0; n-- { 218 | memmove(buf2.Bytes(), buf1.Bytes()) 219 | } 220 | b.StopTimer() 221 | buf1.Close() 222 | buf2.Close() 223 | } 224 | 225 | func TestUnlinkError(t *testing.T) { 226 | err := syscall.Unlink("/this/does/not/exist") 227 | if !os.IsNotExist(err) { 228 | panic("expected an IsNotExist error") 229 | } 230 | } 231 | 232 | var tmpfsSize *int64 = flag.Int64("tmpfs.size", 4<<30, "tmpfs size") 233 | var hugeSize *int64 = flag.Int64("huge.size", 4<<30, "hugetlbfs size") 234 | var setupDone = false 235 | 236 | func setup() { 237 | if setupDone { 238 | return 239 | } 240 | mount.MountNamespace() 241 | if err := mount.MountTmpfs("/dev/shm", *tmpfsSize); err != nil { 242 | panic(err) 243 | } 244 | if err := mount.MountHugetlbfs("/dev/hugepages", 2<<20, *hugeSize); err != nil { 245 | panic(err) 246 | } 247 | setupDone = true 248 | } 249 | 250 | func checkNil(err error) { 251 | if err != nil { 252 | panic("expected nil error") 253 | } 254 | } 255 | 256 | func checkNotNil(err error) { 257 | if err == nil { 258 | panic("expected non-nil error") 259 | } 260 | } 261 | 262 | func testOffsetLength(lenb, offset, length int) error { 263 | // negative checks, zero/overflow check, bounds check 264 | if offset < 0 || length < 0 || offset+length <= offset || offset+length > lenb { 265 | return errOffsetLength 266 | } 267 | return nil 268 | } 269 | 270 | func TestCheckOffsetLength(t *testing.T) { 271 | checkNotNil(checkOffsetLength(make([]byte, 0), 0, 0)) 272 | checkNotNil(checkOffsetLength(make([]byte, 1), 0, 0)) 273 | checkNil(checkOffsetLength(make([]byte, 1), 0, 1)) 274 | checkNotNil(checkOffsetLength(make([]byte, 1), 0, 0)) 275 | checkNotNil(checkOffsetLength(make([]byte, 1), 0, -1)) 276 | checkNil(checkOffsetLength(make([]byte, 2), 0, 1)) 277 | checkNil(checkOffsetLength(make([]byte, 2), 0, 2)) 278 | checkNil(checkOffsetLength(make([]byte, 2), 1, 1)) 279 | checkNotNil(checkOffsetLength(make([]byte, 2), 1, 2)) 280 | checkNotNil(checkOffsetLength(make([]byte, 1), math.MaxInt32, 1)) 281 | checkNotNil(checkOffsetLength(make([]byte, 1), math.MinInt32, 1)) 282 | checkNotNil(checkOffsetLength(make([]byte, 1), 0, math.MinInt32)) 283 | checkNil(testOffsetLength(math.MaxInt32, math.MaxInt32-1, 1)) 284 | checkNotNil(testOffsetLength(math.MaxInt32, math.MaxInt32, 0)) 285 | checkNotNil(testOffsetLength(math.MaxInt32, math.MaxInt32, math.MinInt32)) 286 | checkNotNil(testOffsetLength(math.MaxInt32, math.MaxInt32-10, 11)) 287 | checkNil(testOffsetLength(math.MaxInt32, 0, math.MaxInt32)) 288 | checkNotNil(testOffsetLength(math.MaxInt32, 0, math.MinInt32)) 289 | checkNotNil(testOffsetLength(math.MaxInt32-1, 0, math.MaxInt32)) 290 | } 291 | 292 | func BenchmarkSyscalls(b *testing.B) { 293 | defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(8)) 294 | 295 | buf := newBuffer(size64MiB, PROT_RDWR) 296 | defer checkClose(buf) 297 | 298 | var wg sync.WaitGroup 299 | workChans := make([]chan struct{}, 0, 8) 300 | for i := 0; i < cap(workChans); i++ { 301 | wg.Add(1) 302 | workChan := make(chan struct{}) 303 | workChans = append(workChans, workChan) 304 | go func() { 305 | <-workChan 306 | for i := 0; i < b.N; i++ { 307 | fd2, err := syscall.Dup(int(buf.File().Fd())) 308 | if err != nil { 309 | panic(err) 310 | } 311 | file2 := os.NewFile(uintptr(fd2), "") 312 | buf2, err := NewBufferFile(file2, buf.Len(), PROT_READ) 313 | if err != nil { 314 | panic(err) 315 | } 316 | checkClose(buf2) 317 | checkClose(file2) 318 | } 319 | wg.Done() 320 | }() 321 | } 322 | 323 | b.StartTimer() 324 | for _, workChan := range workChans { 325 | close(workChan) 326 | } 327 | wg.Wait() 328 | b.StopTimer() 329 | } 330 | -------------------------------------------------------------------------------- /slice.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shm 16 | 17 | import ( 18 | "fmt" 19 | "math/rand" 20 | "os" 21 | "time" 22 | ) 23 | 24 | // NewBufferSlice returns a mock shared memory object that is backed by a byte slice, instead of mapped memory 25 | // Pay carefull attentions to which methods are implemented 26 | func NewBufferSlice(size int) Buffer { 27 | return &sliceBuf{make([]byte, size)} 28 | } 29 | 30 | type sliceBuf struct { 31 | buf []byte 32 | } 33 | 34 | func (s *sliceBuf) Bytes() []byte { 35 | return s.buf 36 | } 37 | 38 | func (s *sliceBuf) Len() int { 39 | return len(s.buf) 40 | } 41 | 42 | func (s *sliceBuf) Close() error { 43 | return nil 44 | } 45 | 46 | func (s *sliceBuf) CloseFile() error { 47 | return nil 48 | } 49 | 50 | func (s *sliceBuf) String() string { 51 | return fmt.Sprintf("sliceBuf@%p:%d", &s.buf[0], len(s.buf)) 52 | } 53 | 54 | // Calling File will cause a panic 55 | func (s *sliceBuf) File() *os.File { 56 | panic("not to be implemented") 57 | } 58 | 59 | // Calling LocalFile will cause a panic 60 | func (s *sliceBuf) LocalFile() *os.File { 61 | panic("not to be implemented") 62 | } 63 | 64 | func (s *sliceBuf) Remap(prot int) { 65 | // no-op for now 66 | } 67 | 68 | func (s *sliceBuf) Advise(offset, length, advice int) error { 69 | // no-op for now 70 | return nil 71 | } 72 | 73 | func (s *sliceBuf) Lock(offset, length int) error { 74 | // no-op for now 75 | time.Sleep(time.Duration(rand.Intn(200e6))) 76 | return nil 77 | } 78 | 79 | func (s *sliceBuf) Unlock(offset, length int) error { 80 | // no-op for now 81 | return nil 82 | } 83 | 84 | func (s *sliceBuf) LockAll() error { 85 | // no-op for now 86 | return nil 87 | } 88 | 89 | func (s *sliceBuf) UnlockAll() error { 90 | // no-op for now 91 | return nil 92 | } 93 | -------------------------------------------------------------------------------- /sudo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Vastech SA (PTY) LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package shm 16 | 17 | import ( 18 | "flag" 19 | ) 20 | 21 | var _ = flag.Bool("sudo", true, "sudo") 22 | --------------------------------------------------------------------------------