📁 Sangam Dhakal - Portfolio v2.0 - Netscape Navigator 4.0
_
□
×

📚 Interview question

What is the operating system kernel, and what is the difference between user mode and kernel mode?

The kernel's responsibilities, CPU privilege levels (rings on x64, exception levels on Arm64), why applications run in user mode, how and when the CPU switches to kernel mode, and what that means for .NET applications, drivers, containers and security.

Junior Theory Intermediate kernel kernel-mode operating-system security user-mode

🎯 Key points

  • The kernel is the core of the OS: it manages CPU scheduling, memory, devices (through drivers), file systems, networking, inter-process communication and security
  • The CPU enforces privilege levels: kernel mode (ring 0 on x64, EL1 on Arm64) can access all memory and hardware; user mode (ring 3 on x64, EL0 on Arm64) cannot touch hardware or other processes' memory
  • Every .NET app runs in user mode, including Windows services, IIS worker processes and daemons; only the kernel and drivers run in kernel mode
  • The CPU switches to kernel mode on system calls (syscall/svc), hardware interrupts and exceptions such as page faults, then returns to user mode
  • Stability: a crash in user mode kills one process (a NullReferenceException is a user-mode fault turned into a managed exception); a bug in kernel mode can crash the whole machine (blue screen, kernel panic)
  • Security: privilege escalation means tricking the kernel into acting for an attacker; kernels defend with KASLR, SMEP/SMAP, page-table isolation and patching, and least privilege limits what a compromised process can request
  • Containers share the host kernel (isolated with namespaces, cgroups, seccomp, capabilities); virtual machines run their own kernel
  • Mode switches cost time (hundreds of ns to µs), so chatty kernel interaction such as many tiny I/O calls adds measurable system CPU

⏱ 30-second answer

The kernel is the core of the operating system. It schedules threads on CPUs, manages memory, talks to devices through drivers, and implements file systems, networking and security. The CPU has privilege levels: in kernel mode (ring 0 on x64, EL1 on Arm64) code can access all memory and hardware; in user mode (ring 3, EL0) it can't touch hardware or other processes' memory directly. Every .NET app runs in user mode. When it needs the kernel (to read a file, send on a socket, allocate memory), it makes a system call, the CPU switches to kernel mode, the kernel does the work and returns. Interrupts and faults also switch modes. The split protects the system: a bug in your app crashes your process, not the machine.

🗣 Full answer (senior)

What the kernel does

Responsibility Examples
Process and thread management Create processes, schedule threads on cores, context switches
Memory management Virtual memory, page tables, page faults, swapping
Device I/O Drivers for disks, network cards, GPUs
File systems NTFS, ext4, XFS; permissions, caching
Networking TCP/IP stack, sockets, firewall hooks
Security Users, permissions, isolation between processes
Inter-process communication Pipes, shared memory, signals, events

Windows (NT kernel) and Linux are mostly monolithic kernels with loadable drivers (Windows splits some work into user-mode services). Microkernels move more into user mode for isolation, at some performance cost.

User mode versus kernel mode

User mode Kernel mode
CPU privilege Ring 3 (x64), EL0 (Arm64) Ring 0, EL1
Memory access Only its own process's mapped pages All memory
Hardware access None directly Full
Who runs here Applications, the .NET runtime, most services Kernel, drivers
Effect of a bug Process crashes System crash (blue screen, kernel panic) or security hole

How the CPU switches

  1. System call: the app executes a special instruction (syscall on x64, svc on Arm64); the CPU jumps to a kernel entry point in kernel mode.
  2. Interrupt: a device (network card, timer) signals the CPU; the kernel's handler runs.
  3. Exception or fault: a page fault, divide by zero or invalid instruction transfers control to the kernel, which fixes it (loads a page) or signals the process (which becomes an exception or a crash).

The kernel then returns to user mode at the right instruction. Each round trip costs from about a hundred nanoseconds to a few microseconds, more with security mitigations.

Why it matters for .NET developers

  • Performance: thousands of tiny reads or writes mean thousands of mode switches. Buffering and batching reduce them.
  • Reliability: a NullReferenceException is a user-mode fault turned into a managed exception; it can't take down the host.
  • Containers: all containers on a node share the host's kernel. Kernel features (cgroups, namespaces, seccomp) enforce limits and isolation. A container escape is usually a kernel vulnerability, which is why patching nodes matters.
  • Security: running as a non-root user in containers and with least privilege on Windows reduces what a compromised process can ask the kernel to do. The ACSC Essential Eight's "restrict administrative privileges" and "patch operating systems" are directly about this boundary.
  • Tools: strace (Linux) and Process Monitor (Windows) show the system calls your app makes.

Production example

A logging library wrote every log line directly to a file with an unbuffered FileStream and Flush() after each line. Under load the service spent about 20% of CPU in kernel mode. strace -c showed hundreds of thousands of write and fsync calls per minute. Switching to buffered, asynchronous logging (writing batches) cut kernel time to a few percent.

🔍 What the interviewer is checking

Checks foundational OS knowledge that underpins performance and security discussions.

Green flags: clear description of kernel duties, privilege levels and mode switches via system calls, interrupts and faults, connects it to .NET exceptions, I/O performance, containers and least privilege.

Red flags: vague "the kernel is the OS"; can't explain why user code can't access hardware; thinks containers have their own kernel.

📚 Learn in depth

1. Rings, drawn

          +---------------------------------------------+
          |  user mode (ring 3 / EL0)                    |
          |   MyApi (dotnet) | nginx | sqlservr | bash   |
          +--------------------- syscall / svc ---------+
          |  kernel mode (ring 0 / EL1)                  |
          |   scheduler | memory manager | file systems  |
          |   TCP/IP stack | drivers | security          |
          +---------------------------------------------+
          |  hardware: CPU, RAM, disks, NICs             |
          +---------------------------------------------+

x64 defines rings 0–3 but mainstream OSes use only 0 and 3. Hypervisors run at a further privileged level (often described as "ring −1", or EL2 on Arm64).

2. What a fault looks like end to end

C#:   obj.Name   where obj is null
CPU:  load from address 0x0000000000000008  -> page fault (no mapping)
OS:   delivers an access violation (Windows) / SIGSEGV (Linux) to the process
CLR:  recognises the faulting instruction in managed code -> throws NullReferenceException
App:  catch block or unhandled exception handler runs

The same mechanism in native code without a runtime just kills the process.

3. Measuring user versus kernel time

Tool What it shows
top / htop (Linux) us (user) versus sy (system) CPU percentages
pidstat -u -p <pid> 1 Per-process %usr and %system
Task Manager → Performance → "Show kernel times" (Windows) Kernel time shaded in the CPU graph
Process.UserProcessorTime, PrivilegedProcessorTime (.NET) Split for the current process
strace -c -f -p <pid> Count and time of each system call

High system time usually means lots of small I/O, page faults, context switches or contention in kernel locks.

4. Containers versus virtual machines

Container Virtual machine
Kernel Shared with the host Its own
Isolation Namespaces, cgroups, seccomp, capabilities Hardware virtualisation
Start-up Milliseconds to seconds Seconds to minutes
Risk of kernel exploits Affects all containers on the node Contained to the VM

Sandboxed runtimes (for example Kata Containers or gVisor) add a VM or user-space kernel boundary when multi-tenant isolation matters.

5. Security practices tied to the boundary

  • Run containers as non-root (USER app in the official .NET images since .NET 8), drop Linux capabilities, use read-only root file systems.
  • Patch the host OS and kernel promptly; in AKS use node image upgrades.
  • Avoid kernel-mode code unless you're writing drivers; user-mode alternatives (eBPF on Linux for observability) are safer.

6. Try it yourself

  1. On Linux, run strace -c -f dotnet MyApp.dll for a simple console app that reads a file, and look at the system call summary.
  2. Write a loop that writes one byte at a time with an unbuffered FileStream (bufferSize: 0 or 1), then with the default buffer; compare kernel time with pidstat or Process.PrivilegedProcessorTime.
  3. In a container, run id and check whether you're root; then switch to the app user in your Dockerfile.

💻 Code

namespace UserKernel;

using System.Diagnostics;

public static class Program
{
    public static void Main()
    {
        string path = Path.Combine(Path.GetTempPath(), "modes.bin");

        Measure("Unbuffered, 1 byte per write", () =>
        {
            using var stream = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 0);
            for (var i = 0; i < 200_000; i++)
            {
                stream.WriteByte(1);          // each call becomes a system call
            }
        });

        Measure("Buffered (default 4 KB buffer)", () =>
        {
            using var stream = new FileStream(path, FileMode.Create, FileAccess.Write);
            for (var i = 0; i < 200_000; i++)
            {
                stream.WriteByte(1);          // batched into far fewer system calls
            }
        });
    }

    private static void Measure(string label, Action action)
    {
        using var process = Process.GetCurrentProcess();
        var user = process.UserProcessorTime;
        var kernel = process.PrivilegedProcessorTime;
        var sw = Stopwatch.StartNew();

        action();

        process.Refresh();
        Console.WriteLine($"{label}: wall {sw.ElapsedMilliseconds} ms, " +
                          $"user {(process.UserProcessorTime - user).TotalMilliseconds:F0} ms, " +
                          $"kernel {(process.PrivilegedProcessorTime - kernel).TotalMilliseconds:F0} ms");
    }
}
# Linux: count system calls made by the app
strace -c -f dotnet UserKernel.dll

⚠ Common mistakes

  • Mistake: thinking .NET code runs "in the kernel" or has special privileges. The runtime and your code are ordinary user-mode processes; they reach the kernel only through system calls. Instead: explain the boundary and that every file, socket and memory operation crosses it via the OS.

  • Mistake: ignoring kernel time when profiling. A managed profiler may show little user CPU while the process spends much of its time in system calls or page faults. Instead: check user versus system CPU (top, pidstat, Task Manager kernel times) and use strace or Process Monitor when system time is high.

  • Mistake: assuming containers are as isolated as VMs. Containers share the host kernel, so a kernel vulnerability can affect every container on the node. Instead: patch nodes, run as non-root with minimal capabilities, and use VM-level isolation for untrusted multi-tenant workloads.

  • Mistake: running services as root or Administrator "to avoid permission problems". A compromised process can then ask the kernel to do anything, which turns a bug into a full system compromise. Instead: run with least privilege and grant only the specific permissions needed.

  • Mistake: making many tiny I/O calls. Each crosses into kernel mode and back, so per-byte or per-line writes waste CPU. Instead: buffer and batch I/O, and use async APIs that let the kernel do the waiting.

🏛 Senior insight: trade-offs

Seniors use the user/kernel boundary as both a performance and a security lens.

Lens What to watch What to do
Performance High system (sy) CPU, many syscalls in strace -c Buffer and batch I/O, async I/O, fewer context switches
Stability Process crashes versus host crashes Keep code in user mode; avoid third-party kernel drivers on servers
Security Running as root/Administrator, unpatched kernels Non-root containers, dropped capabilities, read-only root FS, prompt node patching (ACSC Essential Eight: restrict admin privileges, patch OS)
Isolation Multi-tenant or untrusted code on shared nodes VM-level isolation (separate node pools, Kata/gVisor-style sandboxes)
Observability Need kernel-level insight eBPF tools on Linux, ETW on Windows, rather than custom kernel code

They also know containers are a kernel feature, which explains both their efficiency (no guest OS) and their isolation limits (one kernel bug affects every container on the node).

🔗 Follow-ups the interviewer may ask

  1. When: Candidate says user code must ask the kernel to do privileged work Why: Checks they know the mechanism (system calls) and its cost
    How to answer

    A system call is how a user-mode program asks the kernel to do something privileged: read a file, send data, allocate memory, create a thread. The program puts arguments in registers and executes a special instruction (syscall on x64); the CPU switches to kernel mode, the kernel validates the request, does the work and returns. In .NET, File.ReadAllBytesAsync goes through FileStream/RandomAccess and a SafeFileHandle to ReadFile on Windows or pread on Linux; Socket.SendAsync uses I/O completion ports on Windows and epoll on Linux. Each call costs hundreds of nanoseconds to microseconds, more with security mitigations, so thousands of tiny calls add up. Buffering, batching and async I/O keep the count down.

  2. When: Candidate mentions the kernel manages processes Why: Moves to processes and threads, the units the kernel schedules
    How to answer

    A process is a running program with its own virtual address space, handles (open files, sockets) and security context; processes are isolated from each other. A thread is a path of execution inside a process with its own stack, registers and thread-local storage, while sharing the process's memory with other threads. Sharing makes communication cheap but requires synchronisation. In .NET, a System.Diagnostics.Process represents an OS process, a Thread is one-to-one with an OS thread, the thread pool keeps a reusable set of threads, and a Task is a unit of work scheduled onto them. An await on I/O usually uses no thread at all while it waits.

↩ Usually asked after

More in Operating System

Last updated 23 Oct 2026.

🌐 Sangam's Webring

⏪ Previous 🏠 Main Next ⏩
💬 Chat with me