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 | |
34 | hashmap_t process_table = { 0 }; // pid_t -> process_t |
35 | |
36 | static 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 | |
44 | const char *vmap_type_str[] = { |
45 | [VMAP_TYPE_SHARED] = "shared" , |
46 | [VMAP_TYPE_PRIVATE] = "private" , |
47 | }; |
48 | |
49 | static pid_t new_process_id(void) |
50 | { |
51 | static pid_t next = 1; |
52 | return (pid_t){ next++ }; |
53 | } |
54 | |
55 | process_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 | |
107 | void 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 | |
143 | process_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 | |
162 | process_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 | |
171 | fd_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 | |
192 | io_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 | |
200 | bool 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 | |
215 | pid_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: ¤t_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: ¤t_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: ¤t_process->signal_info.sigchild_waitlist); |
247 | return -ERESTARTSYS; |
248 | } |
249 | |
250 | goto find_dead_child; |
251 | } |
252 | |
253 | child_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: ¤t_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 | |
281 | void 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(¤t_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 | |
365 | void 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 | |
380 | bool 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 | |
396 | static 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 | |
410 | static 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 | |
417 | static 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 | |
447 | static 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 | |
453 | SYSFS_AUTOREGISTER(current, process_sysfs_items); |
454 | |