Is it in Swap or RAM

· vm

In the last piece I talked about how, upon a page fault, the kernel first asks two questions: does this address belong to you, and is this kind of access allowed? If both answers are yes, it grabs the page from wherever it lives and lets the program continue. (The Fault That Isn't ?)

This time I want to get specific about that wherever.

zest :
When you run an executable from your SSD, the OS creates a process and its virtual address space, reads the ELF metadata, and learns which file ranges should correspond to which virtual-memory ranges. However it does not immediately copy every mapped section into RAM; pages are generally brought into physical frames as they are actually being accessed.

Then, while the program runs, it creates data that may not exist anywhere in the original files: heap data, stack data, computed values, or privately modified file-backed pages. If RAM gets tight and the kernel wants to evict one of those pages, it cannot simply throw it away because there may be no ordinary file from which those exact bytes can be recreated. So it can write the page’s contents to swap, which itself lives on storage


Say I have a 100 GB game sitting on my SSD and I run:

./game

Obviously 100 GB does not suddenly get copied into RAM. Ahhh! remember , Before I ran it, the game was just a collection of files sitting on storage: executable code, textures, models, sounds, libraries, configuration, etc . Once I execute it, the kernel creates a process and gives that process a virtual address space. Some ranges of that virtual address space are now mapped.

something like this ,

some VA range → executable code
some VA range → initialized data
some VA range → heap
some VA range → shared libraries
some VA range → stack

and further more a mapped range does not mean every page in that range already has a physical frame in RAM. It important to understand , out the huge Virtual address space any few are actually being used in the program's code by the programmer , and also the executable bin, and from those few , only a few are there in the your RAM in an instance.

So, upon seeing a VA that is new, the kernel needs two answers: does this virtual address belong to some valid region, and does this virtual page currently have a physical frame in RAM? The first can be yes while the second is no. And that is the kernel's cue to go fetch or create the page's contents, place them into a physical frame, update the page table, and resume the process. Oh, and by the way, this whole idea of bringing a page into RAM only when it is actually demanded is called demand paging.

so by Now we have established the fact that every mapped region from the VM (or a frame ) has some source or rule for where its contents come from. Broadly, there are two important kinds: file-backed and anonymous.

File-backed

A file-backed region has some file capable of supplying its contents.

The obvious example is your executable's code.

When the kernel loads an executable, it reads enough of the executable's metadata to learn things like , this part of the file contains executable code or this part contains initialized data or this file range should appear at this virtual address etc.

let's talk about how this happens , A file sitting on SSD does not have a process virtual address. It does, however, have file offsets. A file is ultimately a sequence of bytes.
So the kernel can remember something conceptually like ,

virtual page 0x400000

comes from

./game
file offset 0x2000

Then later, if the CPU accesses that virtual page and it isn't in the Memory, the kernel knows where the original bytes live. It can read the corresponding part of the file into a physical frame, update the page table, and let execution continue. Code is not the only example. Initialized global data is also initially present inside the executable:

int score = 42;

That 42 physically exists somewhere in the executable file. Shared libraries work the same way. Code from libc.so, some .so, or a .dylib is backed by those library files.
And then there are files that a program deliberately maps into memory using mmap.


a tangent about the mmap()

Aside: read() versus mmap()

A normal read() and mmap() connect a process to a file in fundamentally different ways. It is one of the ways a normal file can become the backing source of a virtual-memory region. Usually, if I want data from numbers.bin, I call read(). The kernel finds those bytes and copies them into some buffer that already belongs to my process. mmap() changes that relationship. Instead of copying the file into a buffer I chose, I ask the kernel to associate some range of the file with some range of my virtual address space , something like

numbers.bin bytes 0–4095
           ↕
VA 0x700000–0x700fff

The file still stays where it is. What changes is that the kernel now knows that accesses to those virtual addresses are supposed to correspond to bytes from that file. If I later touch one of those addresses and its page is not in RAM, the resulting page fault gives the kernel enough information to fetch the appropriate page from numberys.bin.

And mmap() is not limited to files. It can also create an anonymous mapping: a virtual-memory region with no backing file at all. In that case, the pages start as fresh anonymous memory rather than bytes from disk.


Anonymous

Now the other kind. Anonymous memory is memory whose current contents are not backed by an ordinary file. The usual examples are the heap, the stack, and .bss.

I used to think it as “data the program generated,” and that works often enough to be misleading. The heap fits that description. The stack mostly fits too. But .bss breaks it. Take:

static int hidden[1000000];

Because hidden is an uninitialized global, it lives in .bss, and C guarantees that it starts out filled with zeros. But those four million bytes of zeros are not sitting inside the executable on disk. The executable only records that this much zero-initialized memory needs to exist when the process runs. So when one of those pages is touched for the first time, the kernel does not go looking for its contents in a file. It simply provides a zero-filled page. That is called demand-zero paging: the virtual region already exists, but the actual page is only materialized when it is touched, and its initial contents are known to be zero.

a better simple def can be " anonymous memory is memory for which there is no ordinary file that can reproduce the page’s current contents."

Aside: why fresh memory has to be zeroed

Fresh anonymous memory is exposed to a process as zeroed memory. .bss is only one example; the same applies to fresh stack pages, heap pages obtained from the kernel, anonymous mmap regions ( not file mapping to VAs ) , and other anonymous mappings.

The reason is process isolat. A physical frame may have belonged to another process moments earlier, so the kernel cannot expose its leftover contents to the next one. File-backed pages are different: their initial contents come from the backing file, not from zero-fill.

This makes malloc() an interesting case, because in everyday C programming we are taught that memory returned by malloc() contains garbage. That sounds contradictory. If fresh anonymous memory from the kernel starts at zero, where does the garbage come from? malloc() is not asking the kernel for a brand-new page on every call. The allocator usually asks the kernel for larger regions of anonymous memory and then manages those regions itself. When you free() a chunk, malloc() may later hand that same chunk back to you without clearing it first. So the garbage you see in a malloc() allocation is generally stale memory from your own process or allocator bookkeeping, not secret leftovers from some unrelated process that previously owned the physical frame. And that is why malloc() does not promise zeros, while calloc() does.

Why any of this matters

It gets imp when RAM gets tight and the kernel wants a frame back.

Suppose the victim is a clean file-backed page, say a page of ./game's code. The copy in RAM is identical to the bytes already sitting in the executable, so there is nothing to save here. The kernel can discard the frame and, if the page is needed again, fault it back in from the original file. However Anonymous memory has no such escape hatch. Suppose a heap page now contains a value your program computed. Those exact bytes may exist nowhere except RAM. Throwing the page away would destroy part of the process's state. So before reclaiming that frame, the kernel needs somewhere to preserve the page. and that is swap file.

This is a useful way to think about swap: not extra RAM, and not as a stop between a program on disk and RAM ( this is what i used to think btw ;-) ) . So Pages reach swap as they are being pushed out of RAM so that we can use them later if needed.

But what if I change a file-backed page?

So far the distinction has been neat. A file-backed page can be thrown away because the file can recreate it. An anonymous page cannot, so if we need to evict it, swap gives those bytes a parking space. But now suppose the CPU writes to a file-backed page. The file contains X, the page arrives in RAM containing X, and at this point the page is clean: RAM and its backing file agree. Then the program changes it to Y. Now RAM contains Y, while the file still contains X. The page is dirty. And kernel cannot just throw that page away anymore. If it did, Y would disappear and rereading the file would only bring back X.

So where should Y go? This is where another property of the mapping starts to matter: is it shared or private?

Shared does not mean shared virtual address

This confused me for a while. Every process still has its own virtual address space. Two processes do not need to share the same VA for a mapping to be called shared. Process A might have a page at 0x700000, while process B has it at 0x900000, yet both virtual addresses can refer to the same underlying file-backed page. What is shared is not the virtual address. What is shared is the backing state.

Suppose both processes map the same part of save.dat using a writable MAP_SHARED mapping. The kernel records, when those mappings are created, that these virtual ranges correspond to this file and these file offsets, that writes are allowed, and that those writes belong to the shared file-backed state. Now suppose the file contains a score of 10. The corresponding page is faulted into RAM. A process changes that score to 11. The CPU has no concern about save.dat, SSDs, filesystems, or persistence. It simply stores a new value into memory. But that write makes the page dirty. The kernel already knows from the mapping metadata that this dirty page belongs to a shared mapping of save.dat. So unlike anonymous memory, this page already has somewhere appropriate for the new bytes to go: back to save.dat.

The kernel does not need to write it back immediately. It can keep the dirty page in RAM for a while, batch writes, and eventually flush it through the filesystem and storage stack. Once the file contains the same new bytes again, the page is clean. So a shared file-backed page can move through a little cycle: clean file-backed page → CPU writes → dirty file-backed page → writeback → clean again. And once it is clean again, the kernel can discard it whenever it needs the frame, because the file once again contains everything needed to recreate it.

What if two processes write the shared mapping?

Then we have a concurrency problem. If process A writes one value and process B later writes another value to the same bytes, the later write is what remains in the shared memory. If both race, the result depends on timing, overlap, atomicity, and whatever synchronization the program uses. MAP_SHARED means the state is shared. It does not mean the kernel understands the intention behind each writer and somehow merges them. The filesystem's job comes later: take the dirty file-backed state and eventually get those bytes onto persistent storage. This also exposes another useful boundary. The CPU deals in virtual addresses, page-table permissions, physical memory, faults, and memory accesses. It has no concept of “this is save.dat at offset 8192.” That relationship lives in kernel VM and filesystem metadata. The CPU writes RAM. The kernel knows what that RAM represents.

Shared/private and read/write are different questions

Another thing I kept mixing together was private and read-only. They are not the same thing. There are really two independent questions.

Can this mapping be written to?
And if it is written to, are those changes supposed to modify the backing file?

So a mapping can be shared and read-only, shared and writable, private and read-only, or private and writable. The read/write permissions answer whether the CPU is allowed to perform the access. Shared/private answers what a successful write is supposed to mean.

Private mappings

Suppose the file again contains X, and I map it privately. The file still supplies the initial bytes. I can read X exactly as before. But if the mapping is writable and I change X to Y, that Y belongs only to my process. The original file must remain X. This is where copy-on-write enters. As long as I only read, there is no reason to create another copy. I can keep using the original file-backed page. The moment I write, the kernel gives my process private modified state instead. The file remains untouched.

Now we have an interesting situation.

The page originally came from the file, but the file no longer contains the version my process needs. My process has Y. The file still has X. If memory pressure arrives, the kernel cannot discard my Y and reread the file later, because that would resurrect X. It also cannot write Y back to the file, because the mapping was private. So that modified page has effectively crossed into the anonymous side of the story: its current contents are now private process state with no ordinary file capable of recreating them. If it has to leave RAM, swap can preserve it. And this is why saying “dirty page = swap” is wrong. A dirty shared file-backed page can eventually go back to its file. A dirty private file-backed page cannot, so its modified contents may need swap if evicted. Dirty tells us that RAM and the backing file have diverged. Shared/private tells us what the kernel is allowed to do about that divergence.

.data is the perfect example

Take: int global_count = 42; Unlike .bss, that 42 actually exists inside the executable, in .data. So initially the page is file-backed. The executable can provide the original bytes. Then the process runs: global_count++; Now the running process needs 43. But obviously the kernel should not open the executable on disk and replace the original 42 with 43. Otherwise simply running a program would slowly rewrite its binary. So writable .data is mapped privately. The executable supplies the starting state, but modifications belong to the process. That means the page begins life file-backed, but after the private write, the executable can no longer reconstruct the process's current version. The original file still knows 42. The process now knows 43. If that modified page is later evicted, 43 may have to survive in swap. This is the weird middle ground that makes the original file-backed/anonymous split less rigid than it first looked. A page can begin with a file as its source and later acquire private state that the file no longer represents.

And that is copy-on-write again

This is the same idea we saw with fork(). Two processes can initially share one physical page because both see the same bytes. As long as nobody writes, there is no reason to duplicate anything. Then one process writes. That write faults under copy-on-write rules, and the writer gets its own private version while the other process keeps the original.

Same principle:

share the original while everybody agrees on the bytes; create private state only when somebody tries to diverge. The file-backed private case is doing essentially the same thing. The file provides the original state, and the first private write creates a process-specific version.

“shared library” is not MAP_SHARED

A .so or .dylib is called a shared library because many processes can use the same library code. That does not mean one process can casually edit the library for everyone else. The executable code pages of a shared library are normally mapped read-only and executable. Many processes can therefore reuse the same physical pages safely because nobody is allowed to modify them. Writable library data is generally private to each process. So “shared library” means its code can be shared across processes. It does not mean every mapping of that library is a writable MAP_SHARED mapping back to the .so or .dylib.

Back to the 100 GB game

So now the original ./game story has a fuller shape.

The game begins as files on storage. The kernel reads the executable metadata, creates a process and its virtual address space, and establishes which ranges correspond to which files or to anonymous memory. Pages are brought into RAM as demanded. Some pages remain clean and file-backed. If RAM gets tight, those are easy: discard them and fetch them again later. Some pages are anonymous from the beginning: stack, heap, .bss, anonymous mappings. Once they contain state the program needs, there is no ordinary file to recreate them, so swap can preserve them when their frames are reclaimed. And some pages start file-backed but become privately modified. The file supplied their original contents, but no longer represents their current ones. Those pages end up needing the same kind of preservation as anonymous memory. Which leaves me with a much better question than “is this code, heap, stack, or data?” If this page disappeared from RAM right now, where could the kernel get these exact bytes again? If the answer is “the executable at this offset,” discard it. If the answer is “this mapped file, once the dirty changes are written back,” flush it there. If the answer is “nowhere, these bytes belong only to this running process,” then the kernel has to preserve them somewhere else. And that somewhere is often swap.