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