| 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 | |
| 37 | mos::HashMap<pid_t, Process *> ProcessTable; |
| 38 | |
| 39 | const 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 | |
| 52 | const 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 | |
| 62 | static pid_t new_process_id(void) |
| 63 | { |
| 64 | static std::atomic<pid_t> next = 1; |
| 65 | return next++; |
| 66 | } |
| 67 | |
| 68 | Process::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 | |
| 102 | Process::~Process() |
| 103 | { |
| 104 | // mEmerg << "process " << *this << " destroyed"; |
| 105 | } |
| 106 | |
| 107 | void 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 | |
| 141 | Process *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 | |
| 166 | std::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 | |
| 177 | fd_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 | |
| 198 | IO *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 | |
| 206 | bool 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 | |
| 221 | pid_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: ¤t_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: ¤t_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: ¤t_process->signal_info.sigchild_waitlist); |
| 253 | return -ERESTARTSYS; |
| 254 | } |
| 255 | |
| 256 | goto find_dead_child; |
| 257 | } |
| 258 | |
| 259 | child_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: ¤t_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 | |
| 289 | void 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(¤t_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 | |
| 373 | void 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 | |
| 388 | bool 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 | |
| 404 | static 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 | |
| 418 | static 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 | |
| 425 | static 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 | |
| 454 | static 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 | |
| 460 | SYSFS_AUTOREGISTER(current, process_sysfs_items); |
| 461 | |