1 | // SPDX-License-Identifier: GPL-3.0-or-later |
2 | |
3 | #include "mos/tasks/process.hpp" |
4 | |
5 | #include "mos/filesystem/sysfs/sysfs.hpp" |
6 | #include "mos/filesystem/sysfs/sysfs_autoinit.hpp" |
7 | #include "mos/io/io.hpp" |
8 | #include "mos/mm/mm.hpp" |
9 | #include "mos/tasks/signal.hpp" |
10 | |
11 | #include <abi-bits/wait.h> |
12 | #include <errno.h> |
13 | #include <limits.h> |
14 | #include <mos/filesystem/dentry.hpp> |
15 | #include <mos/filesystem/fs_types.h> |
16 | #include <mos/filesystem/vfs.hpp> |
17 | #include <mos/hashmap.hpp> |
18 | #include <mos/lib/structures/hashmap.hpp> |
19 | #include <mos/lib/structures/hashmap_common.hpp> |
20 | #include <mos/lib/structures/list.hpp> |
21 | #include <mos/lib/sync/spinlock.hpp> |
22 | #include <mos/misc/panic.hpp> |
23 | #include <mos/mm/cow.hpp> |
24 | #include <mos/mm/paging/paging.hpp> |
25 | #include <mos/platform/platform.hpp> |
26 | #include <mos/syslog/printk.hpp> |
27 | #include <mos/tasks/schedule.hpp> |
28 | #include <mos/tasks/task_types.hpp> |
29 | #include <mos/tasks/thread.hpp> |
30 | #include <mos/tasks/wait.hpp> |
31 | #include <mos/type_utils.hpp> |
32 | #include <mos_stdio.hpp> |
33 | #include <mos_stdlib.hpp> |
34 | #include <mos_string.hpp> |
35 | |
36 | mos::HashMap<pid_t, Process *> ProcessTable; |
37 | |
38 | const char *get_vmap_content_str(vmap_content_t content) |
39 | { |
40 | switch (content) |
41 | { |
42 | case VMAP_UNKNOWN: return "unknown" ; |
43 | case VMAP_STACK: return "stack" ; |
44 | case VMAP_FILE: return "file" ; |
45 | case VMAP_MMAP: return "mmap" ; |
46 | case VMAP_DMA: return "DMA" ; |
47 | default: return "unknown" ; |
48 | }; |
49 | } |
50 | |
51 | const char *get_vmap_type_str(vmap_type_t type) |
52 | { |
53 | switch (type) |
54 | { |
55 | case VMAP_TYPE_PRIVATE: return "private" ; |
56 | case VMAP_TYPE_SHARED: return "shared" ; |
57 | default: return "unknown" ; |
58 | }; |
59 | } |
60 | |
61 | static pid_t new_process_id(void) |
62 | { |
63 | static std::atomic<pid_t> next = 1; |
64 | return next++; |
65 | } |
66 | |
67 | Process::Process(Private, Process *parent_, mos::string_view name_) : magic(PROCESS_MAGIC_PROC), pid(new_process_id()), parent(parent_) |
68 | { |
69 | linked_list_init(head_node: &children); |
70 | |
71 | waitlist_init(list: &signal_info.sigchild_waitlist); |
72 | |
73 | this->name = name_.empty() ? "<unknown>" : name_; |
74 | |
75 | if (unlikely(pid == 1) || unlikely(pid == 2)) |
76 | { |
77 | this->parent = nullptr; |
78 | pr_demph(process, "special process %pp created" , (void *) this); |
79 | } |
80 | else if (parent == nullptr) |
81 | { |
82 | mos_panic("process %pp has no parent" , (void *) this); |
83 | } |
84 | else |
85 | { |
86 | list_node_append(head: &parent->children, list_node(this)); |
87 | } |
88 | |
89 | if (unlikely(pid == 2)) |
90 | { |
91 | mm = platform_info->kernel_mm; // ! Special case: PID 2 (kthreadd) uses the kernel page table |
92 | } |
93 | else |
94 | { |
95 | mm = mm_create_context(); |
96 | } |
97 | |
98 | MOS_ASSERT_X(mm != NULL, "failed to create page table for process" ); |
99 | } |
100 | |
101 | Process::~Process() |
102 | { |
103 | pr_emerg("process %p destroyed" , (void *) this); |
104 | } |
105 | |
106 | void process_destroy(Process *process) |
107 | { |
108 | if (!process_is_valid(process)) |
109 | return; |
110 | |
111 | ProcessTable.remove(key: process->pid); |
112 | |
113 | MOS_ASSERT(process != current_process); |
114 | pr_dinfo2(process, "destroying process %pp" , process); |
115 | |
116 | MOS_ASSERT_X(process->main_thread != nullptr, "main thread must be dead before destroying process" ); |
117 | |
118 | spinlock_acquire(&process->main_thread->state_lock); |
119 | thread_destroy(thread: std::move(t&: process->main_thread)); |
120 | |
121 | if (process->mm != NULL) |
122 | { |
123 | spinlock_acquire(&process->mm->mm_lock); |
124 | list_foreach(vmap_t, vmap, process->mm->mmaps) |
125 | { |
126 | spinlock_acquire(&vmap->lock); |
127 | vmap_destroy(vmap); |
128 | } |
129 | |
130 | // free page table |
131 | MOS_ASSERT(process->mm != current_mm); |
132 | mm_destroy_context(table: process->mm); |
133 | process->mm = nullptr; |
134 | } |
135 | } |
136 | |
137 | Process *process_new(Process *parent, mos::string_view name, const stdio_t *ios) |
138 | { |
139 | const auto proc = Process::New(parent, name); |
140 | if (unlikely(!proc)) |
141 | return NULL; |
142 | pr_dinfo2(process, "creating process %pp" , proc); |
143 | |
144 | process_attach_ref_fd(process: proc, file: ios && ios->in ? ios->in : io_null, flags: FD_FLAGS_NONE); |
145 | process_attach_ref_fd(process: proc, file: ios && ios->out ? ios->out : io_null, flags: FD_FLAGS_NONE); |
146 | process_attach_ref_fd(process: proc, file: ios && ios->err ? ios->err : io_null, flags: FD_FLAGS_NONE); |
147 | |
148 | const auto thread = thread_new(owner: proc, mode: THREAD_MODE_USER, name: proc->name, stack_size: 0, NULL); |
149 | if (thread.isErr()) |
150 | { |
151 | process_destroy(process: proc); |
152 | return NULL; // TODO |
153 | } |
154 | proc->main_thread = thread.get(); |
155 | proc->working_directory = dentry_ref_up_to(dentry: parent ? parent->working_directory : root_dentry, root: root_dentry); |
156 | |
157 | ProcessTable.insert(key: proc->pid, value: proc); |
158 | return proc; |
159 | } |
160 | |
161 | std::optional<Process *> process_get(pid_t pid) |
162 | { |
163 | if (auto pproc = ProcessTable.get(key: pid)) |
164 | { |
165 | if (process_is_valid(process: *pproc)) |
166 | return pproc; |
167 | } |
168 | |
169 | return std::nullopt; |
170 | } |
171 | |
172 | fd_t process_attach_ref_fd(Process *process, io_t *file, fd_flags_t flags) |
173 | { |
174 | MOS_ASSERT(process_is_valid(process)); |
175 | |
176 | // find a free fd |
177 | fd_t fd = 0; |
178 | while (process->files[fd].io) |
179 | { |
180 | fd++; |
181 | if (fd >= MOS_PROCESS_MAX_OPEN_FILES) |
182 | { |
183 | mos_warn("process %pp has too many open files" , process); |
184 | return -EMFILE; |
185 | } |
186 | } |
187 | |
188 | process->files[fd].io = io_ref(io: file); |
189 | process->files[fd].flags = flags; |
190 | return fd; |
191 | } |
192 | |
193 | io_t *process_get_fd(Process *process, fd_t fd) |
194 | { |
195 | MOS_ASSERT(process_is_valid(process)); |
196 | if (fd < 0 || fd >= MOS_PROCESS_MAX_OPEN_FILES) |
197 | return NULL; |
198 | return process->files[fd].io; |
199 | } |
200 | |
201 | bool process_detach_fd(Process *process, fd_t fd) |
202 | { |
203 | MOS_ASSERT(process_is_valid(process)); |
204 | if (fd < 0 || fd >= MOS_PROCESS_MAX_OPEN_FILES) |
205 | return false; |
206 | io_t *io = process->files[fd].io; |
207 | |
208 | if (unlikely(!io_valid(io))) |
209 | return false; |
210 | |
211 | io_unref(io: process->files[fd].io); |
212 | process->files[fd] = nullfd; |
213 | return true; |
214 | } |
215 | |
216 | pid_t process_wait_for_pid(pid_t pid, u32 *exit_code, u32 flags) |
217 | { |
218 | if (pid == -1) |
219 | { |
220 | if (list_is_empty(head: ¤t_process->children)) |
221 | return -ECHILD; // no children to wait for at all |
222 | |
223 | find_dead_child:; |
224 | // first find if there are any dead children |
225 | list_foreach(Process, child, current_process->children) |
226 | { |
227 | if (child->exited) |
228 | { |
229 | pid = child->pid; |
230 | goto child_dead; |
231 | } |
232 | } |
233 | |
234 | if (flags & WNOHANG) |
235 | return 0; // no dead children, and we don't want to wait |
236 | |
237 | // we have to wait for a child to die |
238 | MOS_ASSERT_X(!current_process->signal_info.sigchild_waitlist.closed, "waitlist is in use" ); |
239 | |
240 | const bool ok = reschedule_for_waitlist(waitlist: ¤t_process->signal_info.sigchild_waitlist); |
241 | MOS_ASSERT(ok); // we just created the waitlist, it should be empty |
242 | |
243 | // we are woken up by a signal, or a child dying |
244 | if (signal_has_pending()) |
245 | { |
246 | pr_dinfo2(process, "woken up by signal" ); |
247 | waitlist_remove_me(waitlist: ¤t_process->signal_info.sigchild_waitlist); |
248 | return -ERESTARTSYS; |
249 | } |
250 | |
251 | goto find_dead_child; |
252 | } |
253 | |
254 | child_dead:; |
255 | auto target_proc_opt = process_get(pid); |
256 | if (!target_proc_opt) |
257 | { |
258 | pr_warn("process %d does not exist" , pid); |
259 | return -ECHILD; |
260 | } |
261 | |
262 | const auto target_proc = *target_proc_opt; |
263 | |
264 | while (true) |
265 | { |
266 | // child is already dead |
267 | if (target_proc->exited) |
268 | break; |
269 | |
270 | // wait for the child to die |
271 | bool ok = reschedule_for_waitlist(waitlist: ¤t_process->signal_info.sigchild_waitlist); |
272 | MOS_ASSERT(ok); |
273 | } |
274 | |
275 | list_remove(target_proc); // remove from parent's children list |
276 | pid = target_proc->pid; |
277 | if (exit_code) |
278 | *exit_code = target_proc->exit_status; |
279 | process_destroy(process: target_proc); |
280 | |
281 | return pid; |
282 | } |
283 | |
284 | void process_exit(Process *&&process, u8 exit_code, signal_t signal) |
285 | { |
286 | MOS_ASSERT(process_is_valid(process)); |
287 | pr_dinfo2(process, "process %pp exited with code %d, signal %d" , process, exit_code, signal); |
288 | |
289 | if (unlikely(process->pid == 1)) |
290 | mos_panic("init process terminated with code %d, signal %d" , exit_code, signal); |
291 | |
292 | for (const auto &thread : process->thread_list) |
293 | { |
294 | spinlock_acquire(&thread->state_lock); |
295 | if (thread->state == THREAD_STATE_DEAD) |
296 | { |
297 | pr_dinfo2(process, "cleanup thread %pt" , thread); |
298 | MOS_ASSERT(thread != current_thread); |
299 | thread_table.remove(key: thread->tid); |
300 | list_remove(thread); |
301 | thread_destroy(thread: std::move(t: thread)); |
302 | } |
303 | else |
304 | { |
305 | // send termination signal to all threads, except the current one |
306 | if (thread != current_thread) |
307 | { |
308 | pr_dinfo2(signal, "sending SIGKILL to thread %pt" , thread); |
309 | spinlock_release(&thread->state_lock); |
310 | signal_send_to_thread(target: thread, SIGKILL); |
311 | thread_wait_for_tid(tid: thread->tid); |
312 | spinlock_acquire(&thread->state_lock); |
313 | pr_dinfo2(process, "thread %pt terminated" , thread); |
314 | MOS_ASSERT_X(thread->state == THREAD_STATE_DEAD, "thread %pt is not dead" , thread); |
315 | thread_table.remove(key: thread->tid); |
316 | thread_destroy(thread: std::move(t: thread)); |
317 | } |
318 | else |
319 | { |
320 | spinlock_release(&thread->state_lock); |
321 | process->main_thread = thread; // make sure we properly destroy the main thread at the end |
322 | pr_dinfo2(process, "thread %pt is current thread, making it main thread" , thread); |
323 | } |
324 | } |
325 | } |
326 | |
327 | size_t files_total = 0; |
328 | size_t files_closed = 0; |
329 | for (int i = 0; i < MOS_PROCESS_MAX_OPEN_FILES; i++) |
330 | { |
331 | fd_type file = process->files[i]; |
332 | process->files[i] = nullfd; |
333 | |
334 | if (io_valid(io: file.io)) |
335 | { |
336 | files_total++; |
337 | if (io_unref(io: file.io) == NULL) |
338 | files_closed++; |
339 | } |
340 | } |
341 | |
342 | // re-parent all children to parent of this process |
343 | list_foreach(Process, child, process->children) |
344 | { |
345 | child->parent = process->parent; |
346 | linked_list_init(list_node(child)); |
347 | list_node_append(head: &process->parent->children, list_node(child)); |
348 | } |
349 | |
350 | dentry_unref(dentry: process->working_directory); |
351 | |
352 | pr_dinfo2(process, "closed %zu/%zu files owned by %pp" , files_closed, files_total, process); |
353 | process->exited = true; |
354 | process->exit_status = W_EXITCODE(exit_code, signal); |
355 | |
356 | // let the parent wait for our exit |
357 | spinlock_acquire(¤t_thread->state_lock); |
358 | |
359 | // wake up parent |
360 | pr_dinfo2(process, "waking up parent %pp" , process->parent); |
361 | signal_send_to_process(target: process->parent, SIGCHLD); |
362 | waitlist_wake(list: &process->parent->signal_info.sigchild_waitlist, INT_MAX); |
363 | |
364 | thread_exit_locked(t: std::move(current_thread)); |
365 | MOS_UNREACHABLE(); |
366 | } |
367 | |
368 | void process_dump_mmaps(const Process *process) |
369 | { |
370 | pr_info("process %pp:" , process); |
371 | size_t i = 0; |
372 | list_foreach(vmap_t, map, process->mm->mmaps) |
373 | { |
374 | i++; |
375 | const char *typestr = get_vmap_content_str(content: map->content); |
376 | const char *forkmode = get_vmap_type_str(type: map->type); |
377 | pr_info2(" %3zd: %pvm, %s, %s" , i, (void *) map, typestr, forkmode); |
378 | } |
379 | |
380 | pr_info("total: %zd memory regions" , i); |
381 | } |
382 | |
383 | bool process_register_signal_handler(Process *process, signal_t sig, const sigaction_t *sigaction) |
384 | { |
385 | pr_dinfo2(signal, "registering signal handler for process %pp, signal %d" , process, sig); |
386 | if (!sigaction) |
387 | { |
388 | process->signal_info.handlers[sig] = (sigaction_t) { .handler = SIG_DFL }; |
389 | return true; |
390 | } |
391 | process->signal_info.handlers[sig] = *sigaction; |
392 | return true; |
393 | } |
394 | |
395 | // ! sysfs support |
396 | |
397 | #define do_print(fmt, name, item) sysfs_printf(f, "%-10s: " fmt "\n", name, item); |
398 | |
399 | static bool process_sysfs_process_stat(sysfs_file_t *f) |
400 | { |
401 | do_print("%d" , "pid" , current_process->pid); |
402 | do_print("%s" , "name" , current_process->name.c_str()); |
403 | |
404 | do_print("%d" , "parent" , current_process->parent->pid); |
405 | |
406 | for (const auto &thread : current_process->thread_list) |
407 | { |
408 | do_print("%pt" , "thread" , thread); |
409 | } |
410 | return true; |
411 | } |
412 | |
413 | static bool process_sysfs_thread_stat(sysfs_file_t *f) |
414 | { |
415 | do_print("%d" , "tid" , current_thread->tid); |
416 | do_print("%s" , "name" , current_thread->name.c_str()); |
417 | return true; |
418 | } |
419 | |
420 | static bool process_sysfs_vmap_stat(sysfs_file_t *f) |
421 | { |
422 | list_foreach(vmap_t, vmap, current_mm->mmaps) |
423 | { |
424 | #define stat_line(fmt) " %9s: " fmt "\n" |
425 | sysfs_printf(file: f, PTR_RANGE "\n" , vmap->vaddr, vmap->vaddr + vmap->npages * MOS_PAGE_SIZE); |
426 | sysfs_printf(file: f, stat_line("%pvf,%s%s" ), "Perms" , // |
427 | (void *) &vmap->vmflags, // |
428 | vmap->vmflags & VM_USER ? "user" : "kernel" , // |
429 | vmap->vmflags & VM_GLOBAL ? " global" : "" // |
430 | ); |
431 | sysfs_printf(file: f, stat_line("%s" ), "Type" , get_vmap_type_str(type: vmap->type)); |
432 | sysfs_printf(file: f, stat_line("%s" ), "Content" , get_vmap_content_str(content: vmap->content)); |
433 | if (vmap->content == VMAP_FILE) |
434 | { |
435 | char filepath[MOS_PATH_MAX_LENGTH]; |
436 | io_get_name(io: vmap->io, buf: filepath, size: sizeof(filepath)); |
437 | sysfs_printf(file: f, stat_line("%s" ), " File" , filepath); |
438 | sysfs_printf(file: f, stat_line("%zu bytes" ), " Offset" , vmap->io_offset); |
439 | } |
440 | sysfs_printf(file: f, stat_line("%zu pages" ), "Total" , vmap->npages); |
441 | sysfs_printf(file: f, stat_line("%zu pages" ), "Regular" , vmap->stat.regular); |
442 | sysfs_printf(file: f, stat_line("%zu pages" ), "PageCache" , vmap->stat.pagecache); |
443 | sysfs_printf(file: f, stat_line("%zu pages" ), "CoW" , vmap->stat.cow); |
444 | #undef stat_line |
445 | sysfs_printf(file: f, fmt: "\n" ); |
446 | } |
447 | return true; |
448 | } |
449 | |
450 | static sysfs_item_t process_sysfs_items[] = { |
451 | SYSFS_RO_ITEM("process" , process_sysfs_process_stat), |
452 | SYSFS_RO_ITEM("thread" , process_sysfs_thread_stat), |
453 | SYSFS_RO_ITEM("vmaps" , process_sysfs_vmap_stat), |
454 | }; |
455 | |
456 | SYSFS_AUTOREGISTER(current, process_sysfs_items); |
457 | |