You are editing a Word document. Before saving, you try to move it to another folder. Windows stops you cold: “The file is being used by another program.” Most users have seen this message hundreds of times. Now switch to Linux, and the same scenario plays out differently — you can delete a file that is actively being used, rename it, or overwrite it, and the running process usually keeps humming along without so much as a warning. What is actually going on underneath?

The answer lies in a philosophical and architectural divergence between the two operating systems — one that dates back to their respective design origins and has profound implications for system administration, deployment practices, and software reliability.

Windows: Mandatory locking by default

When a Windows application opens a file, it typically calls the CreateFile() Win32 API and specifies a sharing mode. In many common cases — particularly for executables, libraries, and documents being written — applications request exclusive or restricted access. The kernel enforces this at the system level: other processes that attempt to open, move, rename, or delete a locked file are flatly refused.

This is called mandatory locking. The kernel itself prevents conflicting access, regardless of whether the competing process intends any harm. The system prioritises predictability and data integrity: if a file is open for writing, no one else touches it.

Why Windows restarts after updates

The mandatory locking model is precisely why Windows so frequently asks you to restart after software updates. When a running program holds a lock on a DLL or system file, the installer cannot replace it in place. The update is staged and only applied on the next boot, when no process holds a lock on those files.

The practical consequence is that system administrators on Windows must schedule maintenance windows and reboots for updates that would be handled seamlessly on Linux. It is a trade-off: simplicity and safety for the application developer at the cost of operational flexibility for the system operator.

Linux: Advisory locking and the inode model

Linux takes a fundamentally different approach. By default, file locks in Linux are advisory — they are cooperative agreements between processes, not kernel-enforced barriers. An application can request a lock via flock() or fcntl(), and well-behaved programs respect those locks. But the kernel will not physically prevent another process from opening or modifying the file if it does not bother to check for locks first.

More importantly, the Linux kernel’s internal accounting does not revolve around filenames. It revolves around inodes.

An inode is the kernel’s internal representation of a file object — it holds the file’s data block pointers, ownership, permissions, timestamps, and a reference count. A filename (a directory entry) is simply a pointer to an inode. When you open a file, the kernel gives your process a file descriptor that references the inode directly. The filename becomes irrelevant at that point.

“When Linux deletes a file, it removes the directory entry — the name. The underlying inode and its data survive as long as any process holds an open file descriptor to it.”

This is why rm is technically a directory operation, not a data operation. When you delete a file, the kernel decrements the inode’s link count. Only when both the link count and the open file descriptor count reach zero does the kernel actually free the data blocks. Until then, any process with an open descriptor continues to read and write as if nothing happened.

Practical demonstration: deleting a running binary

Consider a running nginx web server. Its binary lives at /usr/sbin/nginx. If you execute:

rm /usr/sbin/nginx

The filename is removed from the filesystem. But the nginx worker processes, which opened that binary when they started, still hold file descriptors referencing the inode. The kernel keeps the inode alive. The website continues serving requests without interruption.

You can observe this state with:

lsof | grep deleted

This will show any process holding a file descriptor to a file whose directory entry has been removed — labelled with (deleted) to indicate the name is gone but the data is not. This is a powerful diagnostic command when a filesystem is unexpectedly full: a process may be writing to a log file that was “deleted” but whose space has not been reclaimed because the descriptor is still open.

How Linux enables zero-downtime deployments

This inode model is not merely a quirk — it is a deliberate design feature that underpins modern deployment practices. When you deploy a new version of a service on Linux:

The package manager or deployment tool writes the new binary to a temporary path, then atomically renames it over the old one (rename() is atomic at the filesystem level). The running process still holds its descriptor to the old inode, so it keeps executing the old code. A controlled restart — or a hot-reload signal — causes the service to exec the new binary from the filesystem. The old inode is released when the last descriptor closes, and its blocks are freed.

This is why Linux services can be updated without downtime, and why systemd’s daemon-reload followed by systemctl restart is a clean, safe operation: the old and new versions coexist briefly at the inode level without conflict.

The hidden danger: disk and memory diverge silently

Real-world incident

This section documents a real failure pattern. The specifics illustrate a class of bug that is surprisingly easy to introduce in any long-running Linux service.

Incident log — Tailscale monitoring script, Debian 11
  • Apr 19 Developer refactors Python monitoring script. Uses str | None union syntax — a Python 3.10 feature. Server is running Python 3.9. Script is reloaded by systemd.
  • Apr 19–29 Script continues running normally. systemd did not restart the process; the new file was written to disk but the old process image, loaded into memory, kept executing. No errors. No alerts.
  • Apr 30 Server reboots unexpectedly. systemd starts a fresh Python 3.9 process and reads the new code from disk for the first time.
  • Apr 30 Process crashes immediately: TypeError: unsupported operand type(s) for |: 'type' and 'NoneType'. The incompatibility had been silently masked for eleven days.

This incident illustrates one of the most deceptive failure modes in Linux systems administration. The developer assumed that saving a new file would cause the running service to reflect the change. It did not — and could not, because the process was executing code already resident in memory, mapped from the old inode.

The incompatibility was real and severe. But the Linux inode model insulated the running process from it completely — right up until a reboot forced a cold start from disk.

Comparing the two models

Property Windows Linux
Default locking Mandatory — kernel enforced Advisory — application cooperative
Delete open file Blocked by OS Permitted; inode kept alive by open descriptors
Replace running binary Blocked; requires reboot Permitted; old process runs old inode until restarted
Update behaviour Often requires restart to apply Restart service to load new binary; OS reboot rarely needed
Disk/memory divergence Not possible while file is locked Possible and silent — a key operational hazard
Filesystem accounting unit Filename (path) Inode (reference-counted file object)
Mandatory locking support Full, integral to design Removed entirely in kernel 5.15 (2021)
Note on Linux mandatory locking

Linux did once have an optional mandatory locking feature, governed by CONFIG_MANDATORY_FILE_LOCKING. It was never reliable — kernel developers documented race conditions that made it unsound in practice. It was removed entirely in Linux 5.15, released in October 2021. Modern Linux is exclusively advisory-locking at the kernel level.

What the demand-paging model adds

One additional nuance is worth clarifying. It is a simplification to say a program’s code is “loaded into memory” at startup. Modern operating systems use demand paging: the binary is memory-mapped, and pages are loaded from disk only as they are needed. The file descriptor to the binary is held open throughout the process’s lifetime to support this.

On Linux, this means the kernel genuinely continues to read from the old inode’s data blocks on demand, even after the filename has been unlinked. The inode stays warm in the page cache. The running process never touches the new binary on disk unless it explicitly execs it.

Practical lessons for Linux operators

The freedom Linux grants — replacing files under running processes — comes with a corresponding responsibility. A few practices follow from this model:

After deploying new code to a long-running service, always issue an explicit restart or reload signal. Do not assume that writing a new file causes the running process to use it. Use lsof | grep deleted periodically to identify processes holding descriptors to deleted files — a common cause of surprising disk space exhaustion. And when a service behaves inconsistently after a deployment, check whether it was actually restarted: the process may have been running the prior version all along.

The Linux kernel does not protect you from your own changes. It gives you maximum flexibility and trusts you with the consequences. Windows makes the opposite trade-off: maximum protection, at the cost of operational agility.

Neither model is universally superior. For desktop environments where ordinary users work with documents and applications, mandatory locking prevents accidents. For servers and infrastructure, advisory locking with the inode model enables zero-downtime deployments that would require scheduled maintenance windows on Windows. Understanding which model you are working with — and what it silently permits — is foundational knowledge for anyone operating software in production.