-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathprocess.rs
More file actions
417 lines (361 loc) · 13.3 KB
/
process.rs
File metadata and controls
417 lines (361 loc) · 13.3 KB
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
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
use alloc::{
collections::btree_set::BTreeSet,
sync::{Arc, Weak},
vec::Vec,
};
use core::{
fmt,
sync::atomic::{AtomicU8, Ordering},
};
use bitflags::bitflags;
use kspin::SpinNoIrq;
use lazyinit::LazyInit;
use weak_map::StrongMap;
use crate::{Pid, ProcessGroup, Session};
#[derive(Default)]
pub(crate) struct ThreadGroup {
pub(crate) threads: BTreeSet<Pid>,
pub(crate) exit_code: i32,
pub(crate) group_exited: bool,
}
bitflags! {
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct ProcessState: u8 {
const RUNNING = 1 << 0;
const STOPPED = 1 << 1;
const ZOMBIE = 1 << 2;
}
}
/// A process.
pub struct Process {
pid: Pid,
state: AtomicU8,
pub(crate) tg: SpinNoIrq<ThreadGroup>,
// TODO: child subreaper9
children: SpinNoIrq<StrongMap<Pid, Arc<Process>>>,
parent: SpinNoIrq<Weak<Process>>,
group: SpinNoIrq<Arc<ProcessGroup>>,
}
impl Process {
/// The [`Process`] ID.
pub fn pid(&self) -> Pid {
self.pid
}
/// Returns `true` if the [`Process`] is the init process.
///
/// This is a convenience method for checking if the [`Process`]
/// [`Arc::ptr_eq`]s with the init process, which is cheaper than
/// calling [`init_proc`] or testing if [`Process::parent`] is `None`.
pub fn is_init(self: &Arc<Self>) -> bool {
Arc::ptr_eq(self, INIT_PROC.get().unwrap())
}
}
/// Parent & children
impl Process {
/// The parent [`Process`].
pub fn parent(&self) -> Option<Arc<Process>> {
self.parent.lock().upgrade()
}
/// The child [`Process`]es.
pub fn children(&self) -> Vec<Arc<Process>> {
self.children.lock().values().cloned().collect()
}
}
/// [`ProcessGroup`] & [`Session`]
impl Process {
/// The [`ProcessGroup`] that the [`Process`] belongs to.
pub fn group(&self) -> Arc<ProcessGroup> {
self.group.lock().clone()
}
fn set_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) {
let mut self_group = self.group.lock();
self_group.processes.lock().remove(&self.pid);
group.processes.lock().insert(self.pid, self);
*self_group = group.clone();
}
/// Creates a new [`Session`] and new [`ProcessGroup`] and moves the
/// [`Process`] to it.
///
/// If the [`Process`] is already a session leader, this method does
/// nothing and returns `None`.
///
/// Otherwise, it returns the new [`Session`] and [`ProcessGroup`].
///
/// The caller has to ensure that the new [`ProcessGroup`] does not conflict
/// with any existing [`ProcessGroup`]. Thus, the [`Process`] must not
/// be a [`ProcessGroup`] leader.
///
/// Checking [`Session`] conflicts is unnecessary.
pub fn create_session(self: &Arc<Self>) -> Option<(Arc<Session>, Arc<ProcessGroup>)> {
if self.group.lock().session.sid() == self.pid {
return None;
}
let new_session = Session::new(self.pid);
let new_group = ProcessGroup::new(self.pid, &new_session);
self.set_group(&new_group);
Some((new_session, new_group))
}
/// Creates a new [`ProcessGroup`] and moves the [`Process`] to it.
///
/// If the [`Process`] is already a group leader, this method does nothing
/// and returns `None`.
///
/// Otherwise, it returns the new [`ProcessGroup`].
///
/// The caller has to ensure that the new [`ProcessGroup`] does not conflict
/// with any existing [`ProcessGroup`].
pub fn create_group(self: &Arc<Self>) -> Option<Arc<ProcessGroup>> {
if self.group.lock().pgid() == self.pid {
return None;
}
let new_group = ProcessGroup::new(self.pid, &self.group.lock().session);
self.set_group(&new_group);
Some(new_group)
}
/// Moves the [`Process`] to a specified [`ProcessGroup`].
///
/// Returns `true` if the move succeeded. The move failed if the
/// [`ProcessGroup`] is not in the same [`Session`] as the [`Process`].
///
/// If the [`Process`] is already in the specified [`ProcessGroup`], this
/// method does nothing and returns `true`.
pub fn move_to_group(self: &Arc<Self>, group: &Arc<ProcessGroup>) -> bool {
if Arc::ptr_eq(&self.group.lock(), group) {
return true;
}
if !Arc::ptr_eq(&self.group.lock().session, &group.session) {
return false;
}
self.set_group(group);
true
}
}
/// Threads
impl Process {
/// Adds a thread to this [`Process`] with the given thread ID.
pub fn add_thread(self: &Arc<Self>, tid: Pid) {
self.tg.lock().threads.insert(tid);
}
/// Removes a thread from this [`Process`] and sets the exit code if the
/// group has not exited.
///
/// Returns `true` if this was the last thread in the process.
pub fn exit_thread(self: &Arc<Self>, tid: Pid, exit_code: i32) -> bool {
let mut tg = self.tg.lock();
if !tg.group_exited {
tg.exit_code = exit_code;
}
tg.threads.remove(&tid);
tg.threads.is_empty()
}
/// Get all threads in this [`Process`].
pub fn threads(&self) -> Vec<Pid> {
self.tg.lock().threads.iter().cloned().collect()
}
/// Returns `true` if the [`Process`] is group exited.
pub fn is_group_exited(&self) -> bool {
self.tg.lock().group_exited
}
/// Marks the [`Process`] as group exited.
pub fn group_exit(&self) {
self.tg.lock().group_exited = true;
}
/// The exit code of the [`Process`].
pub fn exit_code(&self) -> i32 {
self.tg.lock().exit_code
}
}
/// Status & exit
impl Process {
/// Returns `true` if the [`Process`] is a zombie process.
pub fn is_zombie(&self) -> bool {
let bits = self.state.load(Ordering::Acquire);
ProcessState::from_bits_truncate(bits).contains(ProcessState::ZOMBIE)
}
/// Returns `true` if the [`Process`] is running.
pub fn is_running(&self) -> bool {
let bits = self.state.load(Ordering::Acquire);
ProcessState::from_bits_truncate(bits).contains(ProcessState::RUNNING)
}
/// Returns `true` if the [`Process`] is stopped.
pub fn is_stopped(&self) -> bool {
let bits = self.state.load(Ordering::Acquire);
ProcessState::from_bits_truncate(bits).contains(ProcessState::STOPPED)
}
/// Change the [`Process`] from Running to `Stopped`.
///
/// This method atomically transitions the process state to STOPPED using
/// CAS, ensuring the state is either successfully changed or already in
/// ZOMBIE state (in which case no change occurs).
///
/// # Memory Ordering
///
/// Uses `Release` ordering on success to synchronize with `Acquire` loads
/// in `is_stopped()`. This ensures that any writes before this
/// transition, such as setting `stop_signal` in the
/// `ProcessSignalManager`, are visible to threads that observe the
/// `STOPPED` state transition.
pub fn transition_to_stopped(&self) {
let _ = self.state.fetch_update(
Ordering::Release, // Success: synchronize with is_stopped()
Ordering::Relaxed, // Failure: no synchronization needed
|curr| {
let mut flags = ProcessState::from_bits_truncate(curr);
if flags.contains(ProcessState::ZOMBIE) || !flags.contains(ProcessState::RUNNING) {
None // Already zombie or not running, don't transition
} else {
flags.remove(ProcessState::RUNNING);
flags.insert(ProcessState::STOPPED);
Some(flags.bits())
}
},
);
}
/// Change the [`Process`] from `Stopped` to `Running`.
///
/// This method atomically transitions the process state to RUNNING using
/// CAS. The transition succeeds if and only if the current state is
/// `STOPPED` and not `ZOMBIE`.
///
/// # Memory Ordering
///
/// Uses `Release` ordering on success to synchronize with `Acquire` loads
/// in `is_running()`. This ensures that any writes before this
/// transition, for example, setting `cont_signal` in the
/// `ProcessSignalManager`, are visible to threads that observe the
/// `RUNNING` state transition.
pub fn transition_to_running(&self) {
let _ = self.state.fetch_update(
Ordering::Release, // Success: synchronize with is_running()
Ordering::Relaxed, // Failure: no synchronization needed
|curr| {
let mut flags = ProcessState::from_bits_truncate(curr);
if !flags.contains(ProcessState::STOPPED) || flags.contains(ProcessState::ZOMBIE) {
None // Not stopped or already zombie, don't transition
} else {
flags.remove(ProcessState::STOPPED);
flags.insert(ProcessState::RUNNING);
Some(flags.bits())
}
},
);
}
/// Change the [`Process`] from `Stopped` or `Running` to `Zombie`.
///
/// This is a terminal state transition - once a process becomes a zombie,
/// it cannot transition to any other state (it can only be freed via
/// `free()`).
///
/// # Memory Ordering
///
/// Uses `Release` ordering to synchronize with `Acquire` loads in
/// `is_zombie()`, ensuring that when a parent process's `wait()` observes
/// the ZOMBIE state, it also observes all writes that happened before
/// the transition, particularly the exit_code set by `exit_thread()`.
pub fn transition_to_zombie(&self) {
let _ = self
.state
.fetch_update(Ordering::Release, Ordering::Relaxed, |curr| {
let mut flags = ProcessState::from_bits_truncate(curr);
if flags.contains(ProcessState::ZOMBIE) {
None // Already zombie
} else {
flags.remove(ProcessState::RUNNING | ProcessState::STOPPED);
flags.insert(ProcessState::ZOMBIE);
Some(flags.bits())
}
});
}
/// Terminates the [`Process`].
///
/// Child processes are inherited by the init process or by the nearest
/// subreaper process.
///
/// This method panics if the [`Process`] is the init process.
pub fn exit(self: &Arc<Self>) {
// TODO: child subreaper
let reaper = INIT_PROC.get().unwrap();
if Arc::ptr_eq(self, reaper) {
return;
}
let mut children = self.children.lock(); // Acquire the lock first
let mut reaper_children = reaper.children.lock();
let reaper = Arc::downgrade(reaper);
for (pid, child) in core::mem::take(&mut *children) {
*child.parent.lock() = reaper.clone();
reaper_children.insert(pid, child);
}
}
/// Frees a zombie [`Process`]. Removes it from the parent.
///
/// This method panics if the [`Process`] is not a zombie.
pub fn free(&self) {
assert!(self.is_zombie(), "only zombie process can be freed");
if let Some(parent) = self.parent() {
parent.children.lock().remove(&self.pid);
}
}
}
impl fmt::Debug for Process {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut builder = f.debug_struct("Process");
builder.field("pid", &self.pid);
let tg = self.tg.lock();
if tg.group_exited {
builder.field("group_exited", &tg.group_exited);
}
if self.is_zombie() {
builder.field("exit_code", &tg.exit_code);
}
if let Some(parent) = self.parent() {
builder.field("parent", &parent.pid());
}
builder.field("group", &self.group());
builder.finish()
}
}
/// Builder
impl Process {
fn new(pid: Pid, parent: Option<Arc<Process>>) -> Arc<Process> {
let group = parent.as_ref().map_or_else(
|| {
let session = Session::new(pid);
ProcessGroup::new(pid, &session)
},
|p| p.group(),
);
let process = Arc::new(Process {
pid,
state: AtomicU8::new(ProcessState::RUNNING.bits()),
tg: SpinNoIrq::new(ThreadGroup::default()),
children: SpinNoIrq::new(StrongMap::new()),
parent: SpinNoIrq::new(parent.as_ref().map(Arc::downgrade).unwrap_or_default()),
group: SpinNoIrq::new(group.clone()),
});
group.processes.lock().insert(pid, &process);
if let Some(parent) = parent {
parent.children.lock().insert(pid, process.clone());
} else {
INIT_PROC.init_once(process.clone());
}
process
}
/// Creates a init [`Process`].
///
/// This function can be called multiple times, but
/// [`ProcessBuilder::build`] on the the result must be called only once.
pub fn new_init(pid: Pid) -> Arc<Process> {
Self::new(pid, None)
}
/// Creates a child [`Process`].
pub fn fork(self: &Arc<Process>, pid: Pid) -> Arc<Process> {
Self::new(pid, Some(self.clone()))
}
}
static INIT_PROC: LazyInit<Arc<Process>> = LazyInit::new();
/// Gets the init process.
///
/// This function panics if the init process has not been initialized yet.
pub fn init_proc() -> Arc<Process> {
INIT_PROC.get().unwrap().clone()
}