I was wondering whether this is a direct consequence of the
copy-on-write principle or not
No, it's not. FWIW, you could have shared code segments without COW, and you could have COW without shared code segments. It's independent.
If shared program code were to be achieved as a consequence of COW, then only related processes could benefit from that.
For example, if process A
forks twice and creates processes B
and C
, and then B
and C
call one of the seven exec
functions on the same binary, then you could say that the code segment is shared because of COW - since the code segment is never written during execution, and is mapped read-only, then it must be automatically shared, right?
What if you start the same executable from another shell? (Or some other unrelated process forks and executes the same program? It doesn't have to be a shell...)
If code segment sharing was a consequence of COW, in this scenario we wouldn't benefit from sharing the code segment, because the processes are unrelated (so there are no COW-shared pages with the other instances to begin with).
Instead, the code segment is shared with memory mapped files. When loading a new executable in memory, mmap(2)
is called to map the binary file's contents into memory.
and if it is not, what is the process which ensures that no
unnecessary copies of the program's code reside in the memory?
The exact implementation details depend on the operating system, but it's not that complicated. Conceptually, mmap(2)
maps files into memory, so you just need to keep some state on the underlying file representation to keep track of which (if any) memory mappings are active for that file. Such information is usually kept in the file's inode.
Linux, for example, associates files with memory address spaces with the i_mapping
field of struct inode
. So, when mmap(2)
is called on a binary for the first time, physical memory pages are allocated to hold information and the i_mapping
field of that file's inode is set; later invocations will use the i_mapping
field and realize that there is an address space associated with this inode, and because it is read-only, no physical pages are allocated, so everything ends up being shared. Note that the virtual memory might be different in each process, although it refers the same physical page (which means that the kernel will at least allocate and update each process's page tables, but that's about it).
The inode
structure is defined in fs.h
- I can only guess that other UNIX variants do this in a similar way.
Of course, this all works as long as the same binary file is used. If you copy the binary file and execute both copies separately, for obvious reasons, the code segment will not be shared.