File: buffer.go

package info (click to toggle)
golang-gvisor-gvisor 0.0~20221219.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bookworm-proposed-updates
  • size: 17,136 kB
  • sloc: asm: 2,860; cpp: 348; python: 89; sh: 40; makefile: 34; ansic: 21
file content (173 lines) | stat: -rw-r--r-- 5,756 bytes parent folder | download
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
}