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
36mos::HashMap<pid_t, Process *> ProcessTable;
37
38const 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
51const 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
61static pid_t new_process_id(void)
62{
63 static std::atomic<pid_t> next = 1;
64 return next++;
65}
66
67Process::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
101Process::~Process()
102{
103 pr_emerg("process %p destroyed", (void *) this);
104}
105
106void 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
137Process *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
161std::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
172fd_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
193io_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
201bool 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
216pid_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: &current_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: &current_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: &current_process->signal_info.sigchild_waitlist);
248 return -ERESTARTSYS;
249 }
250
251 goto find_dead_child;
252 }
253
254child_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: &current_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
284void 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(&current_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
368void 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
383bool 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
399static 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
413static 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
420static 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
450static 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
456SYSFS_AUTOREGISTER(current, process_sysfs_items);
457