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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173
|
// Copyright 2022 The gVisor Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iouringfs
import (
"fmt"
"gvisor.dev/gvisor/pkg/safemem"
)
// sharedBuffer represents a memory buffer shared between the sentry and
// userspace. In many cases, this is simply an internal mmap on the underlying
// memory (aka fast mode). However in some cases the mapped region may lie
// across multiple blocks and we need to copy the region into a contiguous
// buffer (aka slow mode). The goal in either case is to present a contiguous
// slice for easy access.
//
// sharedBuffer must be initialized with init before first use.
//
// Example
// =======
/*
var sb sharedBuffer
bs := MapInternal(...)
sb.init(bs)
fetch := true
for !done {
var err error
// (Re-)Fetch the view.
var view []byte
if fetch {
view, err = sb.view(128)
}
// Use the view slice to access the region, both for read or write.
someState := dosomething(view[10])
view[20] = someState & mask
// Write back the changes.
fetch, err = sb.writeback(128)
}
*/
// In the above example, in fast mode view returns a slice that points directly
// to the underlying memory and requires no copying. Writeback is a no-op, and
// the view can be reused on subsequent loop iterations (writeback will return
// refetch == false).
//
// In slow mode, view will copy disjoint parts of the region from different
// blocks to a single contiguous slice. Writeback will also required a copy, and
// a new view will have to be fetched on every loop iteration (writeback will
// return refetch == true).
//
// sharedBuffer is *not* thread safe.
type sharedBuffer struct {
bs safemem.BlockSeq
// copy is allocated once and reused on subsequent calls to view. We don't
// use the Task's copy scratch buffer because these buffers may be accessed
// from a background context.
copy []byte
// needsWriteback indicates whether we need to copy out back data from the
// slice returned by the last view() call.
needsWriteback bool
}
// init initializes the sharedBuffer, and must be called before first use.
func (b *sharedBuffer) init(bs safemem.BlockSeq) {
b.bs = bs
}
func (b *sharedBuffer) valid() bool {
return !b.bs.IsEmpty()
}
// view returns a slice representing the shared buffer. When done, view must be
// released with either writeback{,Window} or drop.
func (b *sharedBuffer) view(n int) ([]byte, error) {
if uint64(n) > b.bs.NumBytes() {
// Mapping too short? This is a bug.
panic(fmt.Sprintf("iouringfs: mapping too short for requested len: mapping length %v, requested %d", b.bs.NumBytes(), n))
}
// Fast path: use mapping directly, no copies required.
h := b.bs.Head()
if h.Len() <= n && !h.NeedSafecopy() {
b.needsWriteback = false
return h.ToSlice()[:n], nil
}
// Buffer mapped across multiple blocks, or requires safe copy.
if len(b.copy) < n {
b.copy = make([]byte, n)
}
dst := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.copy[:n]))
copyN, err := safemem.CopySeq(dst, b.bs)
if err != nil {
return nil, err
}
if copyN != uint64(n) {
// Short copy risks exposing stale data from view buffer. This should never happen.
panic(fmt.Sprintf("iouringfs: short copy for shared buffer view: want %d, got %d", n, copyN))
}
b.needsWriteback = true
return b.copy, nil
}
// writeback writes back the changes to the slice returned by the previous view
// call. On return, writeback indicates if the previous view may be reused, or
// needs to be refetched with a new call to view.
//
// Precondition: Must follow a call to view. n must match the value pased to
// view.
//
// Postcondition: Previous view is invalidated whether writeback is successful
// or not. To attempt another modification, a new view may need to be obtained,
// according to refetch.
func (b *sharedBuffer) writeback(n int) (refetch bool, err error) {
return b.writebackWindow(0, n)
}
// writebackWindow is like writeback, but only writes back a subregion. Useful
// if the caller knows only a small region has been updated, as it reduces how
// much data need to be copied. writebackWindow still potentially invalidates
// the entire view, caller must check refetch to determine if the view needs to
// be refreshed.
func (b *sharedBuffer) writebackWindow(off, len int) (refetch bool, err error) {
if uint64(off+len) > b.bs.NumBytes() {
panic(fmt.Sprintf("iouringfs: requested writeback to shared buffer from offset %d for %d bytes would overflow underlying region of size %d", off, len, b.bs.NumBytes()))
}
if !b.needsWriteback {
return false, nil
}
// Existing view invalid after this point.
b.needsWriteback = false
src := safemem.BlockSeqOf(safemem.BlockFromSafeSlice(b.copy[off : off+len]))
dst := b.bs.DropFirst(off)
copyN, err := safemem.CopySeq(dst, src)
if err != nil {
return true, err
}
if copyN != uint64(len) {
panic(fmt.Sprintf("iouringfs: short copy for shared buffer writeback: want %d, got %d", len, copyN))
}
return true, nil
}
// drop releases a view without writeback. Returns whether any existing views
// need to be refetched. Useful when caller is done with a view that doesn't
// need to be modified.
func (b *sharedBuffer) drop() bool {
wb := b.needsWriteback
b.needsWriteback = false
return wb
}
|