summaryrefslogtreecommitdiffhomepage
path: root/pkg/sentry/kernel/task_stop.go
blob: a35948a5f26798a649ea115f78c582c5ad6ed8bb (plain)
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
// Copyright 2018 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

// This file implements task stops, which represent the equivalent of Linux's
// uninterruptible sleep states in a way that is compatible with save/restore.
// Task stops comprise both internal stops (which form part of the task's
// "normal" control flow) and external stops (which do not); see README.md for
// details.
//
// There are multiple interfaces for interacting with stops because there are
// multiple cases to consider:
//
// - A task goroutine can begin a stop on its associated task (e.g. a
// vfork() syscall stopping the calling task until the child task releases its
// MM). In this case, calling Task.interrupt is both unnecessary (the task
// goroutine obviously cannot be blocked in Task.block or executing application
// code) and undesirable (as it may spuriously interrupt a in-progress
// syscall).
//
// Beginning internal stops in this case is implemented by
// Task.beginInternalStop / Task.beginInternalStopLocked. As of this writing,
// there are no instances of this case that begin external stops, except for
// autosave; however, autosave terminates the sentry without ending the
// external stop, so the spurious interrupt is moot.
//
// - An arbitrary goroutine can begin a stop on an unrelated task (e.g. all
// tasks being stopped in preparation for state checkpointing). If the task
// goroutine may be in Task.block or executing application code, it must be
// interrupted by Task.interrupt for it to actually enter the stop; since,
// strictly speaking, we have no way of determining this, we call
// Task.interrupt unconditionally.
//
// Beginning external stops in this case is implemented by
// Task.BeginExternalStop. As of this writing, there are no instances of this
// case that begin internal stops.
//
// - An arbitrary goroutine can end a stop on an unrelated task (e.g. an
// exiting task resuming a sibling task that has been blocked in an execve()
// syscall waiting for other tasks to exit). In this case, Task.endStopCond
// must be notified to kick the task goroutine out of Task.doStop.
//
// Ending internal stops in this case is implemented by
// Task.endInternalStopLocked. Ending external stops in this case is
// implemented by Task.EndExternalStop.
//
// - Hypothetically, a task goroutine can end an internal stop on its
// associated task. As of this writing, there are no instances of this case.
// However, any instances of this case could still use the above functions,
// since notifying Task.endStopCond would be unnecessary but harmless.

import (
	"fmt"
	"sync/atomic"
)

// A TaskStop is a condition visible to the task control flow graph that
// prevents a task goroutine from running or exiting, i.e. an internal stop.
//
// NOTE(b/30793614): Most TaskStops don't contain any data; they're
// distinguished by their type. The obvious way to implement such a TaskStop
// is:
//
//     type groupStop struct{}
//     func (groupStop) Killable() bool { return true }
//     ...
//     t.beginInternalStop(groupStop{})
//
// However, this doesn't work because the state package can't serialize values,
// only pointers. Furthermore, the correctness of save/restore depends on the
// ability to pass a TaskStop to endInternalStop that will compare equal to the
// TaskStop that was passed to beginInternalStop, even if a save/restore cycle
// occurred between the two. As a result, the current idiom is to always use a
// typecast nil for data-free TaskStops:
//
//     type groupStop struct{}
//     func (*groupStop) Killable() bool { return true }
//     ...
//     t.beginInternalStop((*groupStop)(nil))
//
// This is pretty gross, but the alternatives seem grosser.
type TaskStop interface {
	// Killable returns true if Task.Kill should end the stop prematurely.
	// Killable is analogous to Linux's TASK_WAKEKILL.
	Killable() bool
}

// beginInternalStop indicates the start of an internal stop that applies to t.
//
// Preconditions:
// * The caller must be running on the task goroutine.
// * The task must not already be in an internal stop (i.e. t.stop == nil).
func (t *Task) beginInternalStop(s TaskStop) {
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	t.beginInternalStopLocked(s)
}

// Preconditions: Same as beginInternalStop, plus:
// * The signal mutex must be locked.
func (t *Task) beginInternalStopLocked(s TaskStop) {
	if t.stop != nil {
		panic(fmt.Sprintf("Attempting to enter internal stop %#v when already in internal stop %#v", s, t.stop))
	}
	t.Debugf("Entering internal stop %#v", s)
	t.stop = s
	t.beginStopLocked()
}

// endInternalStopLocked indicates the end of an internal stop that applies to
// t. endInternalStopLocked does not wait for the task to resume.
//
// The caller is responsible for ensuring that the internal stop they expect
// actually applies to t; this requires holding the signal mutex which protects
// t.stop, which is why there is no endInternalStop that locks the signal mutex
// for you.
//
// Preconditions:
// * The signal mutex must be locked.
// * The task must be in an internal stop (i.e. t.stop != nil).
func (t *Task) endInternalStopLocked() {
	if t.stop == nil {
		panic("Attempting to leave non-existent internal stop")
	}
	t.Debugf("Leaving internal stop %#v", t.stop)
	t.stop = nil
	t.endStopLocked()
}

// BeginExternalStop indicates the start of an external stop that applies to t.
// BeginExternalStop does not wait for t's task goroutine to stop.
func (t *Task) BeginExternalStop() {
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	t.beginStopLocked()
	t.interrupt()
}

// EndExternalStop indicates the end of an external stop started by a previous
// call to Task.BeginExternalStop. EndExternalStop does not wait for t's task
// goroutine to resume.
func (t *Task) EndExternalStop() {
	t.tg.pidns.owner.mu.RLock()
	defer t.tg.pidns.owner.mu.RUnlock()
	t.tg.signalHandlers.mu.Lock()
	defer t.tg.signalHandlers.mu.Unlock()
	t.endStopLocked()
}

// beginStopLocked increments t.stopCount to indicate that a new internal or
// external stop applies to t.
//
// Preconditions: The signal mutex must be locked.
func (t *Task) beginStopLocked() {
	if newval := atomic.AddInt32(&t.stopCount, 1); newval <= 0 {
		// Most likely overflow.
		panic(fmt.Sprintf("Invalid stopCount: %d", newval))
	}
}

// endStopLocked decrements t.stopCount to indicate that an existing internal
// or external stop no longer applies to t.
//
// Preconditions: The signal mutex must be locked.
func (t *Task) endStopLocked() {
	if newval := atomic.AddInt32(&t.stopCount, -1); newval < 0 {
		panic(fmt.Sprintf("Invalid stopCount: %d", newval))
	} else if newval == 0 {
		t.endStopCond.Signal()
	}
}

// BeginExternalStop indicates the start of an external stop that applies to
// all current and future tasks in ts. BeginExternalStop does not wait for
// task goroutines to stop.
func (ts *TaskSet) BeginExternalStop() {
	ts.mu.Lock()
	defer ts.mu.Unlock()
	ts.stopCount++
	if ts.stopCount <= 0 {
		panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount))
	}
	if ts.Root == nil {
		return
	}
	for t := range ts.Root.tids {
		t.tg.signalHandlers.mu.Lock()
		t.beginStopLocked()
		t.tg.signalHandlers.mu.Unlock()
		t.interrupt()
	}
}

// PullFullState receives full states for all tasks.
func (ts *TaskSet) PullFullState() {
	ts.mu.Lock()
	defer ts.mu.Unlock()
	if ts.Root == nil {
		return
	}
	for t := range ts.Root.tids {
		t.Activate()
		if mm := t.MemoryManager(); mm != nil {
			t.p.PullFullState(t.MemoryManager().AddressSpace(), t.Arch())
		}
		t.Deactivate()
	}
}

// EndExternalStop indicates the end of an external stop started by a previous
// call to TaskSet.BeginExternalStop. EndExternalStop does not wait for task
// goroutines to resume.
func (ts *TaskSet) EndExternalStop() {
	ts.mu.Lock()
	defer ts.mu.Unlock()
	ts.stopCount--
	if ts.stopCount < 0 {
		panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount))
	}
	if ts.Root == nil {
		return
	}
	for t := range ts.Root.tids {
		t.tg.signalHandlers.mu.Lock()
		t.endStopLocked()
		t.tg.signalHandlers.mu.Unlock()
	}
}