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: yesExpected 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;
}