Using a file system API to write and read files is not the only way to access files in Linux. There is another way called the memory-mapped IO. Knowing an alternate way to access files can be intriguing if someone is not already aware of it. This alternate file access method is also a common technical phone screening question. In this blog, we will learn how memory-mapped IO works to access files.
See Educative’s interview preparation guide. This guide provides a step-by-step plan that spans over 12 weeks.
At a high level, memory-mapped IO (MMIO) is simple: a part of a file is mapped in the virtual memory using an mmap system call, and after that, we can access the memory as usual, and any mutations will be disseminated to the underlying file.
The following is the prototype of an mmap system call:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr: This argument indicates the calling process’s virtual address where the mapping will be located. If we pass NULL, the kernel will choose a suitable, free location for us.
length: This argument specifies the length (in bytes) of the mapping. It determines how much data from the file will be mapped into memory.
prot: This argument sets the memory protection for the mapped region and can be a combination of the following flags:
PROT_READ: Pages in the mapping can be read.
PROT_WRITE: Pages in the mapping can be written.
PROT_EXEC: Pages in the mapping can be executed as code.
PROT_NONE: Pages in the mapping are inaccessible.
flags: This argument specifies additional options for the mapping:
MAP_SHARED: Multiple processes can share this mapping. Changes made by one process are visible to others.
MAP_PRIVATE: The mapping is private to the calling process. Changes to the mapped region are not visible to other processes and vice versa. A private copy of the page is created if a process modifies the memory. In the case of file mapping, the changes will not be written to the underlying file. There are many use cases for mapping a file as private.
fd: This argument is the file descriptor of the file we want to map into memory.
offset: This argument is the offset within the file (specified by fd) where the mapping should start. For regular files, this is typically 0.
The mmap system call returns a pointer to the mapped memory on success or MAP_FAILED on failure.
The file access using MMIO often can simplify program logic compared to explicitly using read() and write() functions. An example is an application that dynamically gets clients’ requests to access different parts of a large file. We will need explicit seeks to move the file pointer before accessing the file region. Using MMIO, we can map portions of the file in the memory and access those portions as if they were arrays in memory.
MMIO can perform better than raw read() and write() calls regarding latency. A call to read() and write() involves two data transfers. One between the file and a buffer in the kernel, and the second between the kernel buffer to the user-land buffer. Using MMIO, we can save the second copy (from kernel buffer to user-land buffer). Using MMIO also saves memory because the kernel puts the data in a mapped page that the user accesses.
If we are sequentially reading a file, MMIO might not give any benefits over read() because the IO cost of moving data from disk to memory will incur in both cases.
Small IO operations using MMIO are likely more costly than the simpler read() and write() calls because the cost of setting up memory pages in the memory management unit (MMU) hardware—setting the access right, etc.—have cost.
We now write and read files using MMIO. The following code uses two functions (mmio_read() and mmio_write()) for reading and writing files. We have annotated the following code to provide information in context. Let’s read the code carefully and then run it to see MMIO in action.
Now, it’s your turn to change the code above and see how it behaves. Some suggested exercises for you are as follows:
You can write a file via mmap and then read it via the plain old read() function call.
Or write a new file via the write() function call and read it via mmap.
In the Linux operating system, mmap is a powerful system call. In this blog, we used mmap to write and read to a file without using the usual write() and read() file system API calls.
If you want to learn or refresh operating systems concepts, see Educative’s course: Operating Systems: Virtualization, Concurrency & Persistence. Educative’s platform lets you write and run code inside the browser without the need to install any software.