Add task work mechanism.

Like task_work in Linux, this allows us to register callbacks to be executed
before returning to userspace. This is needed for kcov support, which requires
coverage information to be up-to-date whenever we are in user mode. We will
provide coverage data through the kcov interface to enable coverage-directed
fuzzing in syzkaller.

One difference from Linux is that task work cannot queue work before the
transition to userspace that it precedes; queued work will be picked up before
the next transition.

PiperOrigin-RevId: 322889984
This commit is contained in:
Dean Deng
2020-07-23 16:25:34 -07:00
committed by gVisor bot
parent 3a2fac0ab9
commit 8fed97794e
4 changed files with 70 additions and 1 deletions
+1
View File
@@ -132,6 +132,7 @@ go_library(
"task_stop.go",
"task_syscall.go",
"task_usermem.go",
"task_work.go",
"thread_group.go",
"threads.go",
"timekeeper.go",
+15
View File
@@ -68,6 +68,21 @@ type Task struct {
// runState is exclusive to the task goroutine.
runState taskRunState
// taskWorkCount represents the current size of the task work queue. It is
// used to avoid acquiring taskWorkMu when the queue is empty.
//
// Must accessed with atomic memory operations.
taskWorkCount int32
// taskWorkMu protects taskWork.
taskWorkMu sync.Mutex `state:"nosave"`
// taskWork is a queue of work to be executed before resuming user execution.
// It is similar to the task_work mechanism in Linux.
//
// taskWork is exclusive to the task goroutine.
taskWork []TaskWorker
// haveSyscallReturn is true if tc.Arch().Return() represents a value
// returned by a syscall (or set by ptrace after a syscall).
//
+16 -1
View File
@@ -167,7 +167,22 @@ func (app *runApp) execute(t *Task) taskRunState {
return (*runInterrupt)(nil)
}
// We're about to switch to the application again. If there's still a
// Execute any task work callbacks before returning to user space.
if atomic.LoadInt32(&t.taskWorkCount) > 0 {
t.taskWorkMu.Lock()
queue := t.taskWork
t.taskWork = nil
atomic.StoreInt32(&t.taskWorkCount, 0)
t.taskWorkMu.Unlock()
// Do not hold taskWorkMu while executing task work, which may register
// more work.
for _, work := range queue {
work.TaskWork(t)
}
}
// We're about to switch to the application again. If there's still an
// unhandled SyscallRestartErrno that wasn't translated to an EINTR,
// restart the syscall that was interrupted. If there's a saved signal
// mask, restore it. (Note that restoring the saved signal mask may unblock
+38
View File
@@ -0,0 +1,38 @@
// Copyright 2020 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 kernel
import "sync/atomic"
// TaskWorker is a deferred task.
//
// This must be savable.
type TaskWorker interface {
// TaskWork will be executed prior to returning to user space. Note that
// TaskWork may call RegisterWork again, but this will not be executed until
// the next return to user space, unlike in Linux. This effectively allows
// registration of indefinite user return hooks, but not by default.
TaskWork(t *Task)
}
// RegisterWork can be used to register additional task work that will be
// performed prior to returning to user space. See TaskWorker.TaskWork for
// semantics regarding registration.
func (t *Task) RegisterWork(work TaskWorker) {
t.taskWorkMu.Lock()
defer t.taskWorkMu.Unlock()
atomic.AddInt32(&t.taskWorkCount, 1)
t.taskWork = append(t.taskWork, work)
}