EthicsComputerCourses
Join training +91 90532 10052

Virtualisation & Containers

Operating Systems · Lesson 29 of 32 Advanced 8 min read Updated

By the end of this lesson you will be able to explain how a hypervisor runs several operating systems on one machine, compare virtual machines with containers, and describe the two Linux kernel features that make containers possible.

Cloud servers, college computer labs that reset every night, Android emulators and Docker all rely on the same big idea: give a program the illusion of its own machine or its own OS, while really sharing the hardware. Everything you learnt about processes, memory and protection comes together here.

Virtual machines and hypervisors

A virtual machine (VM) is a software copy of a whole computer: virtual CPUs, virtual RAM, a virtual disk and a virtual network card. A complete guest OS runs inside it, believing it owns the hardware. The software that creates and runs VMs is the hypervisor (also called a virtual machine monitor, VMM). The real machine is the host.

Think of a large house in Kochi divided into separate rented flats. Each family has its own kitchen, lock and address, and lives as if in an independent house. The owner (hypervisor) controls the water, electricity and the main door, and shares them out.

Type 1 and type 2 hypervisors

Type 1 (bare metal)Type 2 (hosted)
Runs onDirectly on the hardwareAs an app on a normal OS
ExamplesVMware ESXi, Microsoft Hyper-V, Xen; KVM inside the Linux kernelOracle VirtualBox, VMware Workstation
Typical useData centres and cloudsLearning, testing on a laptop
SpeedFaster, less overheadA little slower

KVM is part of the Linux kernel itself, turning Linux into a hypervisor; it is usually counted as type 1, though the line is blurry because the host is also a full OS.

How can a guest kernel run safely?

A guest OS expects to run in kernel mode and use privileged instructions, but it must not control the real hardware. Three techniques solve this:

  1. Trap and emulate: run the guest in a less privileged mode. When it tries a privileged instruction, the CPU traps to the hypervisor, which performs the action on the virtual hardware and returns.
  2. Hardware-assisted virtualisation: modern CPUs have special support (Intel VT-x, AMD-V) with an extra privilege level for the hypervisor, plus nested paging (Intel EPT, AMD NPT): a second level of page tables that maps guest "physical" addresses to real physical addresses, so guests manage their own page tables at full speed.
  3. Paravirtualisation: the guest knows it is virtual and uses efficient special drivers. The virtio drivers for disks and network cards are the common example on Linux.

Why use VMs?

  • Consolidation: many lightly used servers can share one powerful machine.
  • Isolation: a crash or attack in one VM does not reach the others.
  • Snapshots and migration: save a VM's whole state, roll it back, or move it to another host while it runs (live migration).
  • Different OSes: run Linux and Windows side by side.

Here is a consolidation sum. Seven VMs with different RAM needs must be placed on servers with 64 GB each. A simple first fit decreasing rule (largest VM first, into the first server with room) packs them:

PythonRun it yourself
host_ram = 64                                        # GB per physical server
vms = {"db": 24, "web1": 8, "web2": 8, "cache": 12, "ci": 16, "mail": 6, "dev": 10}

hosts = []                                           # first fit decreasing bin packing
for name, ram in sorted(vms.items(), key=lambda kv: -kv[1]):
    for h in hosts:
        if h["free"] >= ram:
            h["vms"].append(name); h["free"] -= ram; break
    else:
        hosts.append({"vms": [name], "free": host_ram - ram})

for i, h in enumerate(hosts, 1):
    print(f"server {i}: {h['vms']} ({host_ram - h['free']} GB used)")
Output
server 1: ['db', 'ci', 'cache', 'dev'] (62 GB used)
server 2: ['web1', 'web2', 'mail'] (22 GB used)

Seven separate machines become two. Real cloud schedulers solve this same packing problem, with CPU, RAM and network limits together.

Containers: sharing one kernel

A container isolates a group of processes without a separate guest kernel. All containers on a host share the host's kernel, but each sees its own files, its own process list, its own network and its own resource limits.

Back to the house: VMs are separate flats with their own kitchens. Containers are rooms in a well-run PG hostel. Everyone shares the one kitchen (the kernel), but each room has its own lock, and the warden decides how much electricity each room may use.

On Linux, two kernel features build containers:

  • Namespaces decide what a process can see. There are namespaces for process IDs, mount points (the file tree), network interfaces, hostname (UTS), inter-process communication, users, cgroups and time. A process inside a new PID namespace sees itself as PID 1.
  • Control groups (cgroups, now cgroup v2) decide how much a process can use: CPU time, memory, disk I/O and number of processes.

Step 1: run this model of PID namespaces. The host sees real PIDs; each container sees only its own processes, numbered from 1.

PythonRun it yourself
# Host view: every process has a host PID. Containers get their own PID namespace.
host_processes = [
    (1, "systemd", None), (842, "sshd", None),
    (5120, "nginx", "shop-web"), (5133, "nginx-worker", "shop-web"),
    (6001, "python", "billing"), (6010, "worker", "billing"),
]

def view(namespace):
    if namespace is None:                      # the host sees everything
        return [(pid, name) for pid, name, ns in host_processes]
    inside = [(pid, name) for pid, name, ns in host_processes if ns == namespace]
    return [(i, name) for i, (pid, name) in enumerate(inside, start=1)]   # PIDs restart at 1

print("host sees:   ", view(None))
print("shop-web sees:", view("shop-web"))
print("billing sees: ", view("billing"))
Output
host sees:    [(1, 'systemd'), (842, 'sshd'), (5120, 'nginx'), (5133, 'nginx-worker'), (6001, 'python'), (6010, 'worker')]
shop-web sees: [(1, 'nginx'), (2, 'nginx-worker')]
billing sees:  [(1, 'python'), (2, 'worker')]

Step 2: notice that the nginx container cannot even see the billing processes, so it cannot signal or inspect them. They are still ordinary processes to the host kernel, scheduled by the same scheduler as everything else.

Step 3: run this model of cgroup v2 limits. The cpu.max file holds a quota and a period in microseconds; memory.max holds a memory limit.

PythonRun it yourself
# cgroup v2 CPU limit: "quota period" in microseconds, as written to cpu.max
def cpu_limit(cpu_max):
    quota, period = cpu_max.split()
    if quota == "max":
        return "no limit"
    return f"{int(quota) / int(period):.2f} CPU cores"

for setting in ["max 100000", "50000 100000", "150000 100000", "25000 50000"]:
    print(f"cpu.max = {setting:15} -> {cpu_limit(setting)}")

# memory.max: what happens when a container goes over its limit
limit_mb, usage = 512, 0
for request in [200, 150, 120, 80]:
    usage += request
    status = "ok" if usage <= limit_mb else "over limit: kernel reclaims memory, then OOM-kills a process"
    print(f"container asks for {request} MB, total {usage} MB -> {status}")
Output
cpu.max = max 100000      -> no limit
cpu.max = 50000 100000    -> 0.50 CPU cores
cpu.max = 150000 100000   -> 1.50 CPU cores
cpu.max = 25000 50000     -> 0.50 CPU cores
container asks for 200 MB, total 200 MB -> ok
container asks for 150 MB, total 350 MB -> ok
container asks for 120 MB, total 470 MB -> ok
container asks for 80 MB, total 550 MB -> over limit: kernel reclaims memory, then OOM-kills a process

When you run docker run --cpus 1.5 --memory 512m nginx, Docker writes values like these into the container's cgroup files for you.

Images and layers

A container starts from an image: a read-only bundle of files (the app plus the libraries it needs, but no kernel). Images are built in layers, and many containers can share the same lower layers. Each running container gets a thin writable layer on top, usually with the overlay file system. That is why starting a container takes about a second, while booting a VM takes much longer. Tools such as Docker and Podman build and run containers; Kubernetes runs many containers across many machines.

VMs or containers?

Virtual machineContainer
Isolation unitWhole computer with its own kernelGroup of processes sharing the host kernel
Start-up timeTens of secondsAbout a second or less
SizeGigabytes (full OS)Megabytes to hundreds of MB
Isolation strengthStrong (hardware-enforced boundary)Good, but a kernel bug affects all containers
Can run a different OS kernel?Yes (Windows guest on Linux host)No, uses the host kernel

They are often combined: cloud providers run your containers inside VMs, getting both the speed of containers and the strong boundary of VMs. You can learn more in Containers vs Virtual Machines.

On Windows

  • Hyper-V is Microsoft's type 1 hypervisor. When it is enabled, even the normal Windows installation runs in a special partition on top of it.
  • WSL 2 (Windows Subsystem for Linux) runs a real Linux kernel inside a lightweight utility VM managed by Hyper-V technology, which is why Linux tools behave exactly as on Linux.
  • Windows containers come in two forms: process isolation, which shares the Windows kernel like Linux containers, and Hyper-V isolation, which wraps each container in a small VM for a stronger boundary.

Common mistakes

  • Saying a container contains an OS kernel. It contains user-space files only; the kernel is the host's.
  • Assuming containers are as isolated as VMs. Containers share one kernel, so kernel bugs and misconfiguration matter more.
  • Mixing up namespaces and cgroups. Namespaces limit what you can see; cgroups limit how much you can use.

Key points

  • A hypervisor runs virtual machines, each with its own guest OS; type 1 runs on hardware, type 2 on a host OS.
  • Trap-and-emulate, hardware support (VT-x, AMD-V, nested paging) and paravirtual drivers make VMs fast and safe.
  • Containers isolate processes while sharing the host kernel.
  • Linux namespaces control what a container sees; cgroups control how much CPU, memory and I/O it uses.
  • Images are layered and shared, so containers start quickly and use little space.
  • Windows offers Hyper-V, WSL 2 and two kinds of Windows containers.

Exercise

Add two more VMs to the consolidation example, "analytics": 30 and "backup": 4, and run it again. How many 64 GB servers are needed now? Then write the cpu.max setting that limits a container to a quarter of one CPU with a 100,000 microsecond period, and check it with cpu_limit.

Show solution
Python
vms.update({"analytics": 30, "backup": 4})
hosts = []
for name, ram in sorted(vms.items(), key=lambda kv: -kv[1]):
    for h in hosts:
        if h["free"] >= ram:
            h["vms"].append(name); h["free"] -= ram; break
    else:
        hosts.append({"vms": [name], "free": host_ram - ram})
print(len(hosts), "servers:", [h["vms"] for h in hosts])
print(cpu_limit("25000 100000"))
Output
2 servers: [['analytics', 'db', 'dev'], ['ci', 'cache', 'web1', 'web2', 'mail', 'backup']]
0.25 CPU cores

Run it after the lesson's examples. Two servers are still enough: the first is exactly full at 64 GB and the second uses 54 GB. A quota of 25,000 in a 100,000 period gives a quarter of a CPU.

Quick Quiz

1. What runs directly on the hardware in a type 1 hypervisor setup?

2. What do all containers on one Linux host share?

3. Which Linux feature limits how much CPU and memory a container can use?

4. What does WSL 2 use to run Linux on Windows?

Learn Operating Systems with a mentor

Free lessons get you started. Our Computer Science Fundamentals training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call