github.com/metacubex/gvisor@v0.0.0-20240320004321-933faba989ec/pkg/sentry/kernel/task_stop.go (about) 1 // Copyright 2018 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package kernel 16 17 // This file implements task stops, which represent the equivalent of Linux's 18 // uninterruptible sleep states in a way that is compatible with save/restore. 19 // Task stops comprise both internal stops (which form part of the task's 20 // "normal" control flow) and external stops (which do not); see README.md for 21 // details. 22 // 23 // There are multiple interfaces for interacting with stops because there are 24 // multiple cases to consider: 25 // 26 // - A task goroutine can begin a stop on its associated task (e.g. a 27 // vfork() syscall stopping the calling task until the child task releases its 28 // MM). In this case, calling Task.interrupt is both unnecessary (the task 29 // goroutine obviously cannot be blocked in Task.block or executing application 30 // code) and undesirable (as it may spuriously interrupt a in-progress 31 // syscall). 32 // 33 // Beginning internal stops in this case is implemented by 34 // Task.beginInternalStop / Task.beginInternalStopLocked. As of this writing, 35 // there are no instances of this case that begin external stops, except for 36 // autosave; however, autosave terminates the sentry without ending the 37 // external stop, so the spurious interrupt is moot. 38 // 39 // - An arbitrary goroutine can begin a stop on an unrelated task (e.g. all 40 // tasks being stopped in preparation for state checkpointing). If the task 41 // goroutine may be in Task.block or executing application code, it must be 42 // interrupted by Task.interrupt for it to actually enter the stop; since, 43 // strictly speaking, we have no way of determining this, we call 44 // Task.interrupt unconditionally. 45 // 46 // Beginning external stops in this case is implemented by 47 // Task.BeginExternalStop. As of this writing, there are no instances of this 48 // case that begin internal stops. 49 // 50 // - An arbitrary goroutine can end a stop on an unrelated task (e.g. an 51 // exiting task resuming a sibling task that has been blocked in an execve() 52 // syscall waiting for other tasks to exit). In this case, Task.endStopCond 53 // must be notified to kick the task goroutine out of Task.doStop. 54 // 55 // Ending internal stops in this case is implemented by 56 // Task.endInternalStopLocked. Ending external stops in this case is 57 // implemented by Task.EndExternalStop. 58 // 59 // - Hypothetically, a task goroutine can end an internal stop on its 60 // associated task. As of this writing, there are no instances of this case. 61 // However, any instances of this case could still use the above functions, 62 // since notifying Task.endStopCond would be unnecessary but harmless. 63 64 import ( 65 "fmt" 66 ) 67 68 // A TaskStop is a condition visible to the task control flow graph that 69 // prevents a task goroutine from running or exiting, i.e. an internal stop. 70 // 71 // NOTE(b/30793614): Most TaskStops don't contain any data; they're 72 // distinguished by their type. The obvious way to implement such a TaskStop 73 // is: 74 // 75 // type groupStop struct{} 76 // func (groupStop) Killable() bool { return true } 77 // ... 78 // t.beginInternalStop(groupStop{}) 79 // 80 // However, this doesn't work because the state package can't serialize values, 81 // only pointers. Furthermore, the correctness of save/restore depends on the 82 // ability to pass a TaskStop to endInternalStop that will compare equal to the 83 // TaskStop that was passed to beginInternalStop, even if a save/restore cycle 84 // occurred between the two. As a result, the current idiom is to always use a 85 // typecast nil for data-free TaskStops: 86 // 87 // type groupStop struct{} 88 // func (*groupStop) Killable() bool { return true } 89 // ... 90 // t.beginInternalStop((*groupStop)(nil)) 91 // 92 // This is pretty gross, but the alternatives seem grosser. 93 type TaskStop interface { 94 // Killable returns true if Task.Kill should end the stop prematurely. 95 // Killable is analogous to Linux's TASK_WAKEKILL. 96 Killable() bool 97 } 98 99 // beginInternalStop indicates the start of an internal stop that applies to t. 100 // 101 // Preconditions: 102 // - The caller must be running on the task goroutine. 103 // - The task must not already be in an internal stop (i.e. t.stop == nil). 104 func (t *Task) beginInternalStop(s TaskStop) { 105 t.tg.pidns.owner.mu.RLock() 106 defer t.tg.pidns.owner.mu.RUnlock() 107 t.tg.signalHandlers.mu.Lock() 108 defer t.tg.signalHandlers.mu.Unlock() 109 t.beginInternalStopLocked(s) 110 } 111 112 // Preconditions: Same as beginInternalStop, plus: 113 // - The signal mutex must be locked. 114 func (t *Task) beginInternalStopLocked(s TaskStop) { 115 if t.stop != nil { 116 panic(fmt.Sprintf("Attempting to enter internal stop %#v when already in internal stop %#v", s, t.stop)) 117 } 118 t.Debugf("Entering internal stop %#v", s) 119 t.stop = s 120 t.beginStopLocked() 121 } 122 123 // endInternalStopLocked indicates the end of an internal stop that applies to 124 // t. endInternalStopLocked does not wait for the task to resume. 125 // 126 // The caller is responsible for ensuring that the internal stop they expect 127 // actually applies to t; this requires holding the signal mutex which protects 128 // t.stop, which is why there is no endInternalStop that locks the signal mutex 129 // for you. 130 // 131 // Preconditions: 132 // - The signal mutex must be locked. 133 // - The task must be in an internal stop (i.e. t.stop != nil). 134 func (t *Task) endInternalStopLocked() { 135 if t.stop == nil { 136 panic("Attempting to leave non-existent internal stop") 137 } 138 t.Debugf("Leaving internal stop %#v", t.stop) 139 t.stop = nil 140 t.endStopLocked() 141 } 142 143 // BeginExternalStop indicates the start of an external stop that applies to t. 144 // BeginExternalStop does not wait for t's task goroutine to stop. 145 func (t *Task) BeginExternalStop() { 146 t.tg.pidns.owner.mu.RLock() 147 defer t.tg.pidns.owner.mu.RUnlock() 148 t.tg.signalHandlers.mu.Lock() 149 defer t.tg.signalHandlers.mu.Unlock() 150 t.beginStopLocked() 151 t.interrupt() 152 } 153 154 // EndExternalStop indicates the end of an external stop started by a previous 155 // call to Task.BeginExternalStop. EndExternalStop does not wait for t's task 156 // goroutine to resume. 157 func (t *Task) EndExternalStop() { 158 t.tg.pidns.owner.mu.RLock() 159 defer t.tg.pidns.owner.mu.RUnlock() 160 t.tg.signalHandlers.mu.Lock() 161 defer t.tg.signalHandlers.mu.Unlock() 162 t.endStopLocked() 163 } 164 165 // beginStopLocked increments t.stopCount to indicate that a new internal or 166 // external stop applies to t. 167 // 168 // Preconditions: The signal mutex must be locked. 169 func (t *Task) beginStopLocked() { 170 if newval := t.stopCount.Add(1); newval <= 0 { 171 // Most likely overflow. 172 panic(fmt.Sprintf("Invalid stopCount: %d", newval)) 173 } 174 } 175 176 // endStopLocked decrements t.stopCount to indicate that an existing internal 177 // or external stop no longer applies to t. 178 // 179 // Preconditions: The signal mutex must be locked. 180 func (t *Task) endStopLocked() { 181 if newval := t.stopCount.Add(-1); newval < 0 { 182 panic(fmt.Sprintf("Invalid stopCount: %d", newval)) 183 } else if newval == 0 { 184 t.endStopCond.Signal() 185 } 186 } 187 188 // BeginExternalStop indicates the start of an external stop that applies to 189 // all current and future tasks in ts. BeginExternalStop does not wait for 190 // task goroutines to stop. 191 func (ts *TaskSet) BeginExternalStop() { 192 ts.mu.Lock() 193 defer ts.mu.Unlock() 194 ts.stopCount++ 195 if ts.stopCount <= 0 { 196 panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount)) 197 } 198 if ts.Root == nil { 199 return 200 } 201 for t := range ts.Root.tids { 202 t.tg.signalHandlers.mu.Lock() 203 t.beginStopLocked() 204 t.tg.signalHandlers.mu.Unlock() 205 t.interrupt() 206 } 207 } 208 209 // PullFullState receives full states for all tasks. 210 func (ts *TaskSet) PullFullState() { 211 ts.mu.Lock() 212 defer ts.mu.Unlock() 213 if ts.Root == nil { 214 return 215 } 216 for t := range ts.Root.tids { 217 t.Activate() 218 if mm := t.MemoryManager(); mm != nil { 219 t.p.PullFullState(t.MemoryManager().AddressSpace(), t.Arch()) 220 } 221 t.Deactivate() 222 } 223 } 224 225 // EndExternalStop indicates the end of an external stop started by a previous 226 // call to TaskSet.BeginExternalStop. EndExternalStop does not wait for task 227 // goroutines to resume. 228 func (ts *TaskSet) EndExternalStop() { 229 ts.mu.Lock() 230 defer ts.mu.Unlock() 231 ts.stopCount-- 232 if ts.stopCount < 0 { 233 panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount)) 234 } 235 if ts.Root == nil { 236 return 237 } 238 for t := range ts.Root.tids { 239 t.tg.signalHandlers.mu.Lock() 240 t.endStopLocked() 241 t.tg.signalHandlers.mu.Unlock() 242 } 243 }