github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/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 "sync/atomic" 67 ) 68 69 // A TaskStop is a condition visible to the task control flow graph that 70 // prevents a task goroutine from running or exiting, i.e. an internal stop. 71 // 72 // NOTE(b/30793614): Most TaskStops don't contain any data; they're 73 // distinguished by their type. The obvious way to implement such a TaskStop 74 // is: 75 // 76 // type groupStop struct{} 77 // func (groupStop) Killable() bool { return true } 78 // ... 79 // t.beginInternalStop(groupStop{}) 80 // 81 // However, this doesn't work because the state package can't serialize values, 82 // only pointers. Furthermore, the correctness of save/restore depends on the 83 // ability to pass a TaskStop to endInternalStop that will compare equal to the 84 // TaskStop that was passed to beginInternalStop, even if a save/restore cycle 85 // occurred between the two. As a result, the current idiom is to always use a 86 // typecast nil for data-free TaskStops: 87 // 88 // type groupStop struct{} 89 // func (*groupStop) Killable() bool { return true } 90 // ... 91 // t.beginInternalStop((*groupStop)(nil)) 92 // 93 // This is pretty gross, but the alternatives seem grosser. 94 type TaskStop interface { 95 // Killable returns true if Task.Kill should end the stop prematurely. 96 // Killable is analogous to Linux's TASK_WAKEKILL. 97 Killable() bool 98 } 99 100 // beginInternalStop indicates the start of an internal stop that applies to t. 101 // 102 // Preconditions: 103 // * The caller must be running on the task goroutine. 104 // * The task must not already be in an internal stop (i.e. t.stop == nil). 105 func (t *Task) beginInternalStop(s TaskStop) { 106 t.tg.pidns.owner.mu.RLock() 107 defer t.tg.pidns.owner.mu.RUnlock() 108 t.tg.signalHandlers.mu.Lock() 109 defer t.tg.signalHandlers.mu.Unlock() 110 t.beginInternalStopLocked(s) 111 } 112 113 // Preconditions: Same as beginInternalStop, plus: 114 // * The signal mutex must be locked. 115 func (t *Task) beginInternalStopLocked(s TaskStop) { 116 if t.stop != nil { 117 panic(fmt.Sprintf("Attempting to enter internal stop %#v when already in internal stop %#v", s, t.stop)) 118 } 119 t.Debugf("Entering internal stop %#v", s) 120 t.stop = s 121 t.beginStopLocked() 122 } 123 124 // endInternalStopLocked indicates the end of an internal stop that applies to 125 // t. endInternalStopLocked does not wait for the task to resume. 126 // 127 // The caller is responsible for ensuring that the internal stop they expect 128 // actually applies to t; this requires holding the signal mutex which protects 129 // t.stop, which is why there is no endInternalStop that locks the signal mutex 130 // for you. 131 // 132 // Preconditions: 133 // * The signal mutex must be locked. 134 // * The task must be in an internal stop (i.e. t.stop != nil). 135 func (t *Task) endInternalStopLocked() { 136 if t.stop == nil { 137 panic("Attempting to leave non-existent internal stop") 138 } 139 t.Debugf("Leaving internal stop %#v", t.stop) 140 t.stop = nil 141 t.endStopLocked() 142 } 143 144 // BeginExternalStop indicates the start of an external stop that applies to t. 145 // BeginExternalStop does not wait for t's task goroutine to stop. 146 func (t *Task) BeginExternalStop() { 147 t.tg.pidns.owner.mu.RLock() 148 defer t.tg.pidns.owner.mu.RUnlock() 149 t.tg.signalHandlers.mu.Lock() 150 defer t.tg.signalHandlers.mu.Unlock() 151 t.beginStopLocked() 152 t.interrupt() 153 } 154 155 // EndExternalStop indicates the end of an external stop started by a previous 156 // call to Task.BeginExternalStop. EndExternalStop does not wait for t's task 157 // goroutine to resume. 158 func (t *Task) EndExternalStop() { 159 t.tg.pidns.owner.mu.RLock() 160 defer t.tg.pidns.owner.mu.RUnlock() 161 t.tg.signalHandlers.mu.Lock() 162 defer t.tg.signalHandlers.mu.Unlock() 163 t.endStopLocked() 164 } 165 166 // beginStopLocked increments t.stopCount to indicate that a new internal or 167 // external stop applies to t. 168 // 169 // Preconditions: The signal mutex must be locked. 170 func (t *Task) beginStopLocked() { 171 if newval := atomic.AddInt32(&t.stopCount, 1); newval <= 0 { 172 // Most likely overflow. 173 panic(fmt.Sprintf("Invalid stopCount: %d", newval)) 174 } 175 } 176 177 // endStopLocked decrements t.stopCount to indicate that an existing internal 178 // or external stop no longer applies to t. 179 // 180 // Preconditions: The signal mutex must be locked. 181 func (t *Task) endStopLocked() { 182 if newval := atomic.AddInt32(&t.stopCount, -1); newval < 0 { 183 panic(fmt.Sprintf("Invalid stopCount: %d", newval)) 184 } else if newval == 0 { 185 t.endStopCond.Signal() 186 } 187 } 188 189 // BeginExternalStop indicates the start of an external stop that applies to 190 // all current and future tasks in ts. BeginExternalStop does not wait for 191 // task goroutines to stop. 192 func (ts *TaskSet) BeginExternalStop() { 193 ts.mu.Lock() 194 defer ts.mu.Unlock() 195 ts.stopCount++ 196 if ts.stopCount <= 0 { 197 panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount)) 198 } 199 if ts.Root == nil { 200 return 201 } 202 for t := range ts.Root.tids { 203 t.tg.signalHandlers.mu.Lock() 204 t.beginStopLocked() 205 t.tg.signalHandlers.mu.Unlock() 206 t.interrupt() 207 } 208 } 209 210 // PullFullState receives full states for all tasks. 211 func (ts *TaskSet) PullFullState() { 212 ts.mu.Lock() 213 defer ts.mu.Unlock() 214 if ts.Root == nil { 215 return 216 } 217 for t := range ts.Root.tids { 218 t.Activate() 219 if mm := t.MemoryManager(); mm != nil { 220 t.p.PullFullState(t.MemoryManager().AddressSpace(), t.Arch()) 221 } 222 t.Deactivate() 223 } 224 } 225 226 // EndExternalStop indicates the end of an external stop started by a previous 227 // call to TaskSet.BeginExternalStop. EndExternalStop does not wait for task 228 // goroutines to resume. 229 func (ts *TaskSet) EndExternalStop() { 230 ts.mu.Lock() 231 defer ts.mu.Unlock() 232 ts.stopCount-- 233 if ts.stopCount < 0 { 234 panic(fmt.Sprintf("Invalid stopCount: %d", ts.stopCount)) 235 } 236 if ts.Root == nil { 237 return 238 } 239 for t := range ts.Root.tids { 240 t.tg.signalHandlers.mu.Lock() 241 t.endStopLocked() 242 t.tg.signalHandlers.mu.Unlock() 243 } 244 }