The operating system (with help from the CPU) keeps a page table, which is a mapping of each virtual page for to the physical page it is mapped to. The page table also includes a bit for whether a particular page is currently mapped. For every load and store instruction the hardware walks the page table (or at least a cached portion of it.) If the virtual page is currently mapped to a physical page, the hardware figures this out and returns the right data.
If the virtual page is currently unmapped then the operating system receives an interrupt. At this point it looks into the memory map for the process. This is a list of ranges of virtual memory, the permissions that should be applied to that range, and the file (if any) on disk that stores the data from that range when it is not in RAM. Typically for the main executable there will be a text segment, data segment and sometimes a read-only data segment (for constants), as well as a bss (zero initialized data) segment, a stack, and the heap managed by
malloc. There may be (usually are) additional text and data segments for each shared object (shared library) that the program needs to load.
You can use
mmap() to tell a Posix operating system to create a new region in the memory map, the permissions for that region, and which file to use to back that region. On Linux, for an existing process you can get a listing of its currently mapped regions using the