logo

Process

PID

  • PID 0: swapper or sched, responsible for paging.
  • PID 1: systemd (if the distro uses systemd). The systemd daemon serves as the root of the user space's process tree. PID 1 replaces the parent of a process when the original parent terminates. Read more: systemd.

In k8s: the Pause container's process is almost always PID 1 inside a Kubernetes Pod. The Pause container's job is simply to exist and hold the shared namespaces (network and PID) for all the other application containers in the Pod.

User Mode vs Kernel Mode

  • User mode: the application does not have full access to hardware resources, e.g. a process can't issue I/O requests, that will raise an exception, then OS would kill the process.
  • Kernel mode: the OS has access to the full resources of the machine.

Interrupt

Software interrupt:

  • to enter kernel-space from user-space.
  • synchronous.
  • produced by software.
  • The defined software interrupt on x86 is interrupt number 128 (int $0x80).
  • trap is a kind of software interrupt.

Hardware interrupt:

  • asynchronous.
  • produced by hardware.
  • IRQ = Interrupt Request. Sent by a hardware device to interrupt the processor
  • e.g. timer (irq=0), keyboard (irq=1), etc.

Concepts

  • Process Control Block (PCB): a fancy way of talking about a C structure that contains information about each process.
  • Exception: Exceptions are produced by the processor while executing instructions either in response to a programming error (for example, divide by zero) or abnormal conditions that must be handled by the kernel (for example, a page fault).

Thread vs Process

Each thread has its own private set of registers it uses for computation; thus, if there are two threads that are running on a single processor, when switching from running one (T1) to running the other (T2), a context switch must take place.

  • Thread: separate registers; shared memory, opened files.
  • Process: separate address space, opened files.

Single-threaded vs Multi-threaded

  • Single-threaded Process: only one stack, one heap.
  • Multi-threaded Process: multiple stacks, one heap.

Thread Context Switch vs Process Context Switch

  • Same:
    • handing control over to the operating system kernel.
  • Different:
    • Context switch between processes: save memory (address space) state to process control block (PCB).
    • Context switch between threads: save register stats to thread control blocks (TCBs), the address space remains the same.

Green Thread vs Native Thread

  • Green Thread: scheduled by a runtime library or virtual machine (VM), emulating multithreaded environments without relying on any native OS capabilities; managed in user space instead of kernel space, enabling them to work in environments that do not have native thread support.
  • Native Thread: native thread supported by the underlying operating system.

System Calls

  • fork()
  • exec()
  • wait()
  • kill()

fork() and exec()

  • fork(): creates a new process from current one with new pid, memory space, registers
  • exec(): loads code (and static data) from that executable and overwrites its current code segment (and current static data) with it; the heap and stack and other parts of the memory space of the program are re-initialized. (no new process created)

Reason for the separation of fork() and exec(): it lets the shell run code after the call to fork() but before the call to exec(); this code can alter the environment of the about-to-be-run program, and thus enables a variety of interesting features to be readily built.

exec has 6 functions:

#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);

Note:

  • the 3 execv* functions use char *argv[] for arguments, execl* functions need to list out all args and use NULL at the end.
  • execle and execve use envp[] to replace default environment params, others use default
  • execlp and execvp do not need full path, they will try to find the file from PATH environment param, others need the full path.
  • only execve is the real sys call, others are built on top of it.

Cooperative vs Non-cooperative

If CPU is running some user program, it cannot run OS, thus OS cannot switch processes.

  • Cooperative: Processes that run for too long are assumed to periodically give up the CPU so that the OS can decide to run some other task (yield system call, or other system calls)
  • Non-cooperative: timer interrupt, "A timer device can be programmed to raise an interrupt every so many milliseconds; when the interrupt is raised, the currently running process is halted, and a pre-configured interrupt handler in the OS runs. At this point, the OS has regained control of the CPU, and thus can do what it pleases: stop the current process, and start a different one."

Commands

  • start a process
    • foreground
    • background
  • stop a process
  • kill a process
  • schedule a process

pstree

$ pstree
init─┬─acpid
     ├─apache2───2*[apache2───26*[{apache2}]]
     ├─atd
     ├─cron
     ...

Get PID

$ pgrep mongod

Multiple programs under the same name:

$ ps -eo pid,args | grep 'java -jar start.jar'| grep -v grep | cut -c1-6

Step by Step:

  • ps -e: show all the pids
  • ps -o pid,args: specify the fields to show:
$ ps -eo pid,args
12498 -bash
14132 java -jar start.jar
17489 grep java -jar start.jar
...
  • grep 'java -jar start.jar': only leave the specified commands:
$ ps -eo pid,args | grep 'java -jar start.jar'
14132 java -jar start.jar
17489 grep java -jar start.jar
  • grep -v grep: exclude the grep itself:
$ ps -eo pid,args | grep 'java -jar start.jar'| grep -v grep
14132 java -jar start.jar
  • cut -c1-6: cut the first 6 characters:
$ ps -eo pid,args | grep 'java -jar start.jar'| grep -v grep | cut -c1-6
14132

Other ways to get pid: echo $$ or echo $!.

kill

kill does not just kill, it's actually a signal sender.

Send SIGTERM signal to process. (-TERM is the default)

$ kill -TERM <pid>

Send multiple signals: (9=SIGKILL, 13=SIGPIPE)

$ kill -{13, 9} <pid>

More about available signals:

$ man 7 signal

Exit code

exit code ranges from 0 to 255.

128 + N: Fatal error signal N—Indicates that the command or program was terminated by a fatal error signal.

  • exit code 137: 137 (128 + 9) means your process exited due to SIGKILL.
  • exit code 143: if the container terminated gracefully with SIGTERM.

Kernel Threads vs User Threads

To understand the difference, you first have to understand that in Linux, both are technically "tasks" (managed by the same task_struct in the source code). The difference lies in where they are allowed to go and what memory they can see.

Normal Threads (User Threads)

These are the threads created by the programs you run, like Chrome, Discord, or a Python script.

  • Boundary: They spend most of their time in User Space (Ring 3). When they need the kernel, they "cross the border" via a system call, do their business, and come back.
  • Memory: They have their own User Address Space. This means they have their own stack, heap, and memory for variables that are isolated from other processes.
  • Context: They have a dedicated mm_struct (Memory Manager structure) that defines their private world.
  • Visibility: You can see them easily in top or ps.

Kernel Threads

Kernel threads are created by the Linux kernel itself to perform background maintenance tasks. They are like the "janitors" and "security guards" of the operating system.

  • Boundary: They live permanently in Kernel Space (Ring 0). They never "return" to user space because they don't have any user code to run.
  • Memory: They do not have a user address space. They don't have their own private heap or stack in the way an app does. Instead, they operate directly on the kernel's shared memory.
  • Context: Their mm pointer in the kernel is literally NULL. They "borrow" the memory mappings of whatever process was running before them to stay efficient.
  • Examples:
    • kswapd: Manages virtual memory and swapping.
    • kworker: Handles general background work.
    • ksoftirqd: Handles hardware interrupts.

Key Differences Table

Feature Normal (User) Thread Kernel Thread
Who created it? A User Application (via pthread_create / clone) The Kernel (via kthread_create)
Privilege Level Switches between Ring 3 and Ring 0 Always Ring 0 (Maximum privilege)
Address Space Has a private user-space memory map No user-space map (shares kernel memory)
Lifetime Ends when the application exits Usually lives until the system shuts down
System Calls Uses syscalls to talk to the kernel Doesn't use syscalls (it's already in the kernel)

How to spot them in your terminal

You can actually see the difference right now by running ps aux.

  1. Normal Threads: Look for any process with a standard name (e.g., bash, sshd, chrome).
  2. Kernel Threads: Look for names inside square brackets.
    • [ksoftirqd/0]
    • [kworker/u2:1]
    • [migration/0]

The square brackets are a convention used by Linux to tell you: "This task has no user-space memory; it is a kernel thread."

Why does the Kernel need its own threads?

If the kernel only reacted to what users did, certain things would never happen. The kernel needs its own threads to:

  1. Housekeeping: Clean up memory (garbage collection for the OS).
  2. Asynchronous Work: If you save a file, the "Normal Thread" might finish its work, but a "Kernel Thread" stays behind to actually write those bits to the physical disk in the background.
  3. Hardware Management: Waiting for a temperature sensor to report back or managing the power state of the CPU.

Why "Kernel Threads" instead of "Kernel Processes"?

In some operating systems (like Windows), there is a strict hierarchy: a Process is a "container," and a Thread is something that lives inside it.

In Linux, everything is a "task." Whether you call it a process, a thread, or a kernel thread, they are all managed by the exact same structure in the source code: struct task_struct. However

  • If the new task shares memory with its parent, we call it a Thread.
  • If the new task copies memory into a new private area, we call it a Process.

We don't call them "Kernel Processes" because of what the word Process implies in computer science.

  • A "Process" implies an Address Space: A process, by definition, has a private sandbox (a User Address Space). It has its own mm_struct that maps virtual memory to physical RAM. It is isolated.
  • A "Thread" implies Shared Execution: A thread doesn't have its own sandbox; it runs inside an existing address space.

Kernel Threads do not have their own private memory sandbox. They live and breathe inside the shared memory of the Kernel. Since they are simply a "flow of execution" within the already-existing kernel memory, the word "Thread" is more accurate than "Process."

  • Technically, the task_struct has two pointers: mm (active memory) and active_mm (borrowed memory).
  • For a Kernel Thread, mm is always NULL.
  • It just uses active_mm to stay alive until the scheduler switches to the next task.