-
Notifications
You must be signed in to change notification settings - Fork 69
Process Management
A guided, code-backed overview of process management in MentOS.
A process is the unit of execution in MentOS. It owns:
- A virtual address space (its
mm_struct_t), with a default user stack ofDEFAULT_STACK_SIZE= 1 MiB (kernel/inc/process/process.h) - A stack and registers (
thread_struct_t) - Open file descriptors — at most
MAX_OPEN_FD= 16 per task (kernel/inc/fs/vfs.h) - Identity and group information (PID/PPID/PGID/SID, UID/GID, real and effective)
- Scheduling metadata (
sched_entity) - Signal state (handlers, blocked mask, pending set) and timers
From user space, you interact with processes mainly through the POSIX-style APIs in unistd.h and sys/wait.h.
Three words that are easy to confuse, and where MentOS differs from Linux:
- A program is a file on disk — an ELF executable such as
/bin/ls. - A process is a running instance of a program, with its own address space and its own
task_struct. Every schedulable entity in MentOS is one of these. - A thread is, in Linux, a task that shares an address space with its siblings. MentOS
does not currently offer this.
__NR_clonehas a number inlib/inc/system/syscall_types.hand the dispatcher inkernel/src/system/syscall.chas a special case for it, butsyscall_init()never registers asys_clonehandler and no such handler exists in the tree. Treat threading as unimplemented, not as an available API. (TheFIXME: When threads will be implementedcomment in__load_executable()is a good marker of where the design anticipates it.)
Consequently, in MentOS "process" and "task" can be read as synonyms; in Linux they cannot.
The kernel creates the init process at boot and loads /bin/init — or /bin/runtests when the
Multiboot command line is runtests. It also wires standard I/O to /proc/video so user
programs can read and write to the screen and keyboard:
- The init process is created in
process_create_init(). - STDIN/STDOUT/STDERR are attached to
/proc/video:vfs_open("/proc/video", ...)is called three times, once read-only and twice write-only, producing descriptors 0, 1 and 2.
This is a MentOS-specific shortcut. In a conventional Unix system the first process inherits
descriptors backed by a terminal device (/dev/tty* or /dev/console), and the shell later
manages the controlling terminal, job control and terminal modes through it. MentOS instead
points all three at a procfs video node. Everything a program does with fds 0/1/2 still works
the way you expect, but if you go looking for a tty layer behind them you will not find one.
Relevant code paths:
- kernel/src/process/process.c
- kernel/inc/process/process.h
fork() creates a new process by cloning the current process state and address space. In MentOS:
- The child gets a cloned memory map (
mm_clone). - File descriptors are duplicated (
vfs_dup_task). - The child sees a return value of 0 —
sys_fork()setsproc->thread.regs.eax = 0in the child's saved frame, while the parent gets the child's PID as the ordinary syscall return. - Session, process group, and user/group ids are inherited from the parent.
"Duplicate" needs a qualifier. It is not true that fork() copies everything: the PID is
new, the parent PID is the caller, pending signals and accumulated CPU time start fresh, and
open files are shared objects reached through duplicated descriptors — the two processes share
one file offset per open file, they do not get private copies of the file.
Textbook fork() is fast because it maps the parent's pages read-only into the child and copies
a page only when one of them writes to it. MentOS has that machinery: vm_area_clone() takes
a cow flag, page table entries carry kernel_cow, and __page_handle_cow() in
kernel/src/mem/page_fault.c resolves COW faults.
But sys_fork() does not use it:
sys_fork() → mm_clone(current->mm) → vm_area_clone(mm, area, /* cow = */ 0, GFP_HIGHUSER)
With cow == 0, vm_area_clone() allocates fresh physical pages and eagerly copies the whole
area with vmem_memcpy(). So in the current tree fork() performs a full eager copy of the
address space. Keep the COW concept — it is the right thing to learn, and the code to read is
already there — but do not describe it as what fork() does today.
User-side API:
#include <unistd.h>
pid_t pid = fork();
if (pid == 0) {
// child
} else if (pid > 0) {
// parent
}execve() replaces the current process image with a new executable. In MentOS:
- The process identity survives: same PID, same parent, same open descriptors. Only the
program image — address space, entry point, argv/envp — is replaced. This is the Unix model,
and it is why
fork()+execve()is the standard way to start a program. - The old address space is destroyed and rebuilt (
mm_destroy,mm_create_blank). - ELF binaries are loaded by the kernel ELF loader, which accepts only 32-bit,
little-endian,
EM_386,ET_EXECimages (seeelf_check_file_header()). - Shebang (
#!) scripts are supported, one level deep:__load_executable()reads the first line, restarts with the interpreter path, and returns-ELOOPif that interpreter is itself a script. - A NULL
envpis accepted and replaced with a default environment (a MentOS convenience, not a POSIX behaviour — on Linux a NULLenvpis simply an empty environment):PATH=/bin:/usr/binHOME=/
Failure semantics differ from mature Unix kernels. POSIX guarantees that if
execve()fails, it returns-1and the calling process is left completely untouched. MentOS checks the cheap preconditions first (the file exists, is executable, and is either anET_EXECELF or a#!script) — but it then callsmm_destroy()and rebuilds a blank address space beforeelf_load_file()runs. If segment loading fails after that point, the old image is already gone and there is nothing sensible to return to. Do not present this as standard Unix behaviour; it is a known simplification in the current implementation.
User-side API:
#include <unistd.h>
char *argv[] = {"/bin/echo", "hello", NULL};
char *envp[] = {"PATH=/bin:/usr/bin", "HOME=/", NULL};
execve(argv[0], argv, envp);exit() marks a process as a zombie and notifies the parent with SIGCHLD. The parent should collect the exit status with waitpid().
User-side API:
#include <sys/wait.h>
#include <unistd.h>
pid_t pid = fork();
if (pid == 0) {
// child
_exit(42);
}
int status = 0;
pid_t done = waitpid(pid, &status, 0);
if (done > 0 && WIFEXITED(status)) {
int code = WEXITSTATUS(status);
}MentOS tracks the usual UNIX identifiers:
- PID: process ID
- PPID: parent process ID
- PGID: process group ID
- SID: session ID
- UID/GID: user and group IDs (real and effective)
User-side APIs (subset):
#include <unistd.h>
pid_t pid = getpid();
pid_t ppid = getppid();
pid_t pgid = getpgid(0);
pid_t sid = getsid(0);
setpgid(0, pgid);
setsid();Notes about waitpid() in MentOS:
-
pid == -1(any child) andpid > 0(specific child) are supported. -
pid == 0orpid < -1are not supported. - Only
WNOHANGandWUNTRACEDare accepted; other options return-EINVAL.
Processes are scheduled from a run queue (runqueue_t) and can be in these states:
TASK_RUNNINGTASK_INTERRUPTIBLETASK_UNINTERRUPTIBLETASK_STOPPEDTASK_TRACEDEXIT_ZOMBIEEXIT_DEAD
MentOS lets you select a scheduling policy at build time via the CMake SCHEDULER_TYPE
option, but only SCHEDULER_RR (the default) is fully implemented; the Priority, CFS, EDF, RM
and AEDF branches in kernel/src/process/scheduler_algorithm.c are student exercises that
currently fall back to Round-Robin. See Scheduling for a full overview.
Processes can receive POSIX-like signals. When a process exits, the kernel sends SIGCHLD to the parent.
User-side APIs are defined in <signal.h> and <sys/wait.h> (for exit status macros). See IPC and System Calls for details.
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if (pid == 0) {
printf("child pid=%d\n", getpid());
_exit(0);
}
int status = 0;
waitpid(pid, &status, 0);
printf("parent saw child exit\n");
return 0;
}#include <unistd.h>
int main(void)
{
pid_t pid = fork();
if (pid == 0) {
char *argv[] = {"/bin/ls", "/", NULL};
execve(argv[0], argv, NULL); // NULL envp is allowed in MentOS
_exit(127);
}
waitpid(pid, NULL, 0);
return 0;
}-
task_structdescribes a process (IDs, state, file descriptors, memory, signals) -
thread_structstores register state -
sched_entitystores scheduling metadata
- Process creation and exec: kernel/src/process/process.c
- Scheduler and syscalls like
getpid,setpgid,waitpid,exit: kernel/src/process/scheduler.c - Wait queues and sleep primitives: kernel/src/process/wait.c
-
fork()usesmm_clone()and duplicates the parent file table.mm_clone()copies the kernel half of the page directory from the main page directory and rebuilds the user half from the parent's VMA list, so parent and child never share page tables. -
execve()enforces executable permission, honours the setuid/setgid bits, and supports one level of shebang indirection. -
sys_exit()isdo_exit(exit_code << 8)— the 8-bit shift mirrors the UNIX wait-status encoding thatWEXITSTATUS()undoes. - Exiting processes become zombies until the parent calls
waitpid(). - The ELF loader maps every
PT_LOADsegment asMM_USER | MM_RW | MM_PRESENT; it does not derive per-segment permissions from the ELFPF_R/PF_W/PF_Xflags. In practice this means a program's.textis writable and there is no W^X separation. See Features.