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