syscall: process_vm_* copies data by chunks

First, it avoids allocating a large buffer that can be costly.
Second, it allows to interrupt a system call in case of any signals.

PiperOrigin-RevId: 580721720
This commit is contained in:
Andrei Vagin
2023-11-08 17:59:08 -08:00
committed by gVisor bot
parent 9f6156f23a
commit 9bfd408753
2 changed files with 73 additions and 31 deletions
+47 -30
View File
@@ -141,6 +141,10 @@ type processVMOpArgs struct {
writeIovecCount int
}
// maxScratchBufferSize is the maximum size of a scratch buffer. It should be
// sufficiently large to minimizing the number of trips through MM.
const maxScratchBufferSize = 1 << 20
func doProcessVMOpMaybeLocked(t *kernel.Task, args processVMOpArgs) (int, error) {
// Copy IOVecs in to kernel.
readIovecs, err := t.CopyInIovecsAsSlice(args.readAddr, args.readIovecCount)
@@ -160,6 +164,9 @@ func doProcessVMOpMaybeLocked(t *kernel.Task, args processVMOpArgs) (int, error)
bufSize = int(readIovec.Length())
}
}
if bufSize > maxScratchBufferSize {
bufSize = maxScratchBufferSize
}
buf := t.CopyScratchBuffer(bufSize)
// Number of bytes written.
@@ -169,42 +176,52 @@ func doProcessVMOpMaybeLocked(t *kernel.Task, args processVMOpArgs) (int, error)
break
}
buf = buf[0:int(readIovec.Length())]
bytes, err := args.readCtx.CopyInBytes(readIovec.Start, buf)
if linuxerr.Equals(linuxerr.EFAULT, err) {
return n, nil
}
if err != nil {
return n, err
}
if bytes != int(readIovec.Length()) {
return n, nil
}
start := 0
for bytes > start && 0 < len(writeIovecs) {
writeLength := int(writeIovecs[0].Length())
if writeLength > (bytes - start) {
writeLength = bytes - start
for readIovec.Length() != 0 {
length := readIovec.Length()
if length > maxScratchBufferSize {
length = maxScratchBufferSize
}
out, err := args.writeCtx.CopyOutBytes(writeIovecs[0].Start, buf[start:writeLength+start])
n += out
start += out
buf = buf[0:int(length)]
bytes, err := args.readCtx.CopyInBytes(readIovec.Start, buf)
if linuxerr.Equals(linuxerr.EFAULT, err) {
return n, nil
}
if err != nil {
if bytes == 0 && err != nil {
return n, err
}
if out != writeLength {
return n, nil
}
writeIovecs[0].Start += hostarch.Addr(out)
if !writeIovecs[0].WellFormed() {
return n, err
}
if writeIovecs[0].Length() == 0 {
writeIovecs = writeIovecs[1:]
readIovec.Start += hostarch.Addr(bytes)
start := 0
for bytes > start && 0 < len(writeIovecs) {
if t.Interrupted() {
if n == 0 {
return 0, linuxerr.EINTR
}
return n, nil
}
writeLength := int(writeIovecs[0].Length())
if writeLength > (bytes - start) {
writeLength = bytes - start
}
out, err := args.writeCtx.CopyOutBytes(writeIovecs[0].Start, buf[start:writeLength+start])
n += out
start += out
if linuxerr.Equals(linuxerr.EFAULT, err) {
return n, nil
}
if err != nil {
return n, err
}
if out != writeLength {
return n, nil
}
writeIovecs[0].Start += hostarch.Addr(out)
if !writeIovecs[0].WellFormed() {
return n, err
}
if writeIovecs[0].Length() == 0 {
writeIovecs = writeIovecs[1:]
}
}
}
}
+26 -1
View File
@@ -113,6 +113,18 @@ struct ProcessVMTestCase {
using ProcessVMTest = ::testing::TestWithParam<ProcessVMTestCase>;
std::string getTestBuffer(std::string pattern, size_t size) {
std::string s;
auto pattern_length = pattern.length();
s.reserve(size);
while (s.length() + pattern_length < size) {
s += pattern;
}
s += pattern.substr(0, size - s.length());
return s;
}
INSTANTIATE_TEST_SUITE_P(
ProcessVMTests, ProcessVMTest,
::testing::ValuesIn<ProcessVMTestCase>(
@@ -132,7 +144,20 @@ INSTANTIATE_TEST_SUITE_P(
{"Obi-wan never told you what happened to your father.",
"He told me enough...he told me you killed him."},
{"No...I am your father.", "No. No.", "That's not true.",
"That's impossible!"}}}),
"That's impossible!"}},
{
"LargeBuffer",
{
getTestBuffer(
"Train yourself to let go of everything you fear to lose.",
32 << 20),
"Hello there!",
},
{
"Do. Or do not. There is no try.",
getTestBuffer("The greatest teacher, failure is.", 32 << 20),
},
}}),
[](const ::testing::TestParamInfo<ProcessVMTest::ParamType>& info) {
return info.param.test_name;
});