Virtualization and Containers

A hypervisor (virtual machine monitor) runs several operating systems on one machine, each in a virtual machine believing it owns the hardware.

Concept

A hypervisor (virtual machine monitor) runs several operating systems on one machine, each in a virtual machine believing it owns the hardware.

Type 1 (bare metal) Directly on hardware VMware ESXi, Xen, Hyper-V, KVM (Linux as hypervisor) Type 2 (hosted) VirtualBox, VMware Workstation, QEMU

Trap-and-emulate: guest privileged instructions trap to the hypervisor, which emulates them.

Hardware virtualization (Intel VT-x vmx, AMD-V svm, ARM EL2): a special CPU mode runs guest kernels natively; nested page tables (EPT/NPT) translate guest-physical to host-physical addresses in hardware.

Paravirtualization: the guest knows it is virtualized and calls the hypervisor directly (virtio drivers).

WSL2, used to verify this course, is itself a lightweight Hyper-V virtual machine running a real Linux kernel.

A container is a group of ordinary processes that the (shared) kernel isolates using:

Namespaces — separate views of PIDs, mounts, network, hostname (UTS), IPC, users, cgroups and time.

cgroups — limits and accounting for CPU, memory, I/O and number of processes.

Security — capabilities, seccomp system-call filters, LSMs (AppArmor, SELinux).

Layered file systems (overlayfs) for images.

Own guest kernel Shared host kernel Higher (memory for each OS) Strong (hardware-enforced) Weaker (kernel shared)

Different OSes, strong multi-tenant isolation Packaging and deploying applications (Docker, Kubernetes)

In automotive systems, hypervisors run safety-critical and infotainment software side by side on one SoC (mixed criticality).

Why It Matters

Cloud computing, CI pipelines, Docker/Kubernetes deployments, embedded hypervisors and development environments like WSL all rest on these mechanisms; understanding them explains performance overheads and isolation guarantees.

Syntax

ls -l /proc/self/ns The namespaces of a process cat /proc/self/cgroup unshare --pid --fork --mount-proc Run a command in new namespaces

Example

27.1 Detecting virtualization and building a mini container with namespaces

unshare --user --map-root-user creates a user namespace first, so no real root privileges are needed.

$ systemd-detect-virt
wsl
$ grep -m1 -o -w hypervisor /proc/cpuinfo
hypervisor
$ grep -m1 -o -w -E 'vmx|svm' /proc/cpuinfo || echo "no hardware virtualization flags visible inside this VM"
vmx
$ cat /proc/self/cgroup
0::/
$ ls -l /proc/self/ns | awk 'NR>1 {print $9, $11}' | head -8
cgroup cgroup:[4026531835]
ipc ipc:[4026532208]
mnt mnt:[4026532219]
net net:[4026531833]
pid pid:[4026532221]
pid_for_children pid:[4026532221]
time time:[4026531834]
time_for_children time:[4026531834]
$ echo "outside: my PID is $$, processes visible: $(ps -e --no-headers | wc -l)"
outside: my PID is 268, processes visible: 12
$ unshare --user --map-root-user --pid --fork --mount-proc sh -c 'echo "inside a new PID namespace: my PID is $$, uid $(id -u)"; ps -o pid,comm'
inside a new PID namespace: my PID is 1, uid 0
    PID COMMAND
      1 sh
      3 ps
$ unshare --user --map-root-user --uts sh -c 'hostname ecu-sandbox; echo "hostname inside: $(hostname)"'
hostname inside: ecu-sandbox
$ echo "hostname outside is unchanged: $([ "$(hostname)" != ecu-sandbox ] && echo yes)"
hostname outside is unchanged: yes

Expected Result

The system reports that it runs inside WSL (a Hyper-V VM), and the CPU advertises the hypervisor flag. The vmx flag is also visible because this VM allows nested virtualization.

Every process lives in a set of namespaces identified by inode numbers.

Inside the new PID namespace, the shell is PID 1 and ps sees only the two processes of the container; inside the user namespace it appears as uid 0 without real root privileges.

Changing the hostname in a new UTS namespace didn't affect the host.

That is the core of what Docker does, plus cgroups, a separate mount namespace with its own root file system, networking and security filters.

Common Mistakes

Treating containers as VMs: a kernel vulnerability affects all containers on the host.

Running containers as root and with unnecessary capabilities.

Forgetting that a container's CPU/memory limits (cgroups) may differ from what /proc/cpuinfo shows.

Best Practices

Use VMs for strong isolation between tenants or different OSes; containers for application packaging.

Run containers as non-root with minimal capabilities, read-only file systems and seccomp profiles.

Set explicit cgroup limits and make applications respect them.

Interview Relevance

High: "VM vs container?", "type 1 vs type 2 hypervisor?", "what are namespaces and cgroups?", "how does hardware virtualization work?", "what is paravirtualization?".

Practice Questions

Create a network namespace with unshare --user --map-root-user --net and run ip link.

Limit a process's memory with cgroups v2 on a system where you have root, and observe what happens when it exceeds the limit.

List which namespaces two processes share by comparing /proc/<pid>/ns links.

Explain why containers start faster than VMs.

Quick Quiz

Q1. (Multiple choice) Which kernel feature limits a container's memory? A) Namespaces B) cgroups C) seccomp D) chroot Answer: B.

Q2. (Predict the output) What PID does the first process in a new PID namespace see for itself?

Answer: 1 (verified).

Q3. (True/False) Containers on one host share the same kernel.

Answer: True. Q4. (Find the bug) "Our container can't see the host's processes, so a kernel exploit inside it can't affect the host."

Answer: Namespaces change what a process can see, but the kernel is shared; a kernel vulnerability can break isolation. Use VMs where that risk is unacceptable.

Coding Challenge

Write a program that prints the namespace identifiers of the calling process by reading the symbolic links in /proc/self/ns.

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    const char *kinds[] = {"pid", "mnt", "net", "uts", "ipc", "user", "cgroup"};
    for (int i = 0; i < 7; i++) {
        char path[64], target[128];
        snprintf(path, sizeof path, "/proc/self/ns/%s", kinds[i]);
        ssize_t n = readlink(path, target, sizeof target - 1);
        if (n < 0) continue;
        target[n] = '\0';
        printf("%-7s %s\n", kinds[i], target);
    }
    return 0;
}