# mmap — Virtual Memory Mapping, Shared Memory & MMIO

It can be used for various types of memory mapping into the virtual address space of a process

> `mmap(<0 or NULL or specific addr>, <length>, <Read/Write Permissions>, <Flag deciding mapping type>, <fileDescriptor>, <offset>);`

* * *

### 1st arg

*   0/NULL -> tells kernel "I don't care where you map in virtual addr space"
    
    *   Kernel chooses a suitable free virtual address
        
    *   This is the normal/recommended usage
        
*   Specific Value `0x7fff0000`
    
    *   Without `MAP_FIXED` -> this is only a hint to the kernel
        
    *   Kernel may map at a different virtual address if required
        
    *   With `MAP_FIXED` -> kernel tries to map at exactly this virtual address
        
        *   Existing mapping at that address may be replaced
            
        *   Dangerous because it can destroy an existing mapping
            
    *   `MAP_FIXED_NOREPLACE` -> requires the address to be unused; fails instead of replacing an existing mapping
        

* * *

### 2nd arg

*   Length of memory to map into the process virtual address space
    
*   It does NOT have to be page-size aligned
    
*   Kernel internally manages mappings at page granularity
    
*   We should provide the actual size required by our data
    
    *   Hardware peripheral
        
        *   Mapping length should cover the required registers
            
        *   `offset` passed to mmap must satisfy the alignment requirements of the mapping
            
        *   If the required physical address is not page aligned, map from the containing page boundary and add the intra-page offset
            
            Eg : `0x3F200032` (not page aligned)
            
            *   `MAP_SIZE` is 4096 (`0x1000`)
                
            *   `MAP_MASK` is `MAP_SIZE - 1`, which is 4095 (`0x0FFF`)
                
            *   `~MAP_MASK` gives `0xFFFFF000`
                
            *   `PERIPHERAL_PHY_BASE & ~MAP_MASK`
                
                *   removes the lower 12 bits
                    
                *   gives the beginning of the page containing the peripheral address
                    
    *   Anonymous memory in RAM or shared memory
        
        *   We can request any size
            
        *   Kernel manages the mapping using page-sized units internally
            
        *   We should provide the exact size of the data structure/buffer we need
            
        *   Example:
            
            *   Structure -> `sizeof(structure)`
                
            *   Array -> `number_of_elements * sizeof(element)`
                
    *   Scenario A: shared memory
        
        ```c
        typedef struct {
            int process_id;
            char status[32];
            float telemetry_data;
        } SharedData;
        
        SharedData *data = (SharedData *)mmap(
            NULL,
            sizeof(SharedData),
            PROT_READ | PROT_WRITE,
            MAP_SHARED | MAP_ANONYMOUS,
            -1,
            0
        );
        ```
        
    *   Scenario B : Allocating a Dynamic Buffer (e.g., Arrays)
        
        ```c
        size_t num_elements = 500;
        size_t allocation_size = num_elements * sizeof(int);
        
        int *my_array = (int *)mmap(
            NULL,
            allocation_size,
            PROT_READ | PROT_WRITE,
            MAP_PRIVATE | MAP_ANONYMOUS,
            -1,
            0
        );
        ```
        

* * *

### 3rd arg (permissions)

*   `PROT_READ`, `PROT_WRITE`, `PROT_EXEC`, `PROT_NONE`
    
*   Defines what operations the process is allowed to perform on the mapped memory
    
*   Example:
    
    *   `PROT_READ` -> read allowed
        
    *   `PROT_WRITE` -> write allowed
        
    *   `PROT_READ | PROT_WRITE` -> read + write
        

* * *

### 4th arg (flag)

*   `MAP_PRIVATE`
    
    *   Changes are private to the process
        
    *   For file-backed mappings, writes use copy-on-write
        
*   `MAP_SHARED`
    
    *   Changes are visible through other mappings of the same backing object
        
    *   Commonly used for shared memory and device mappings
        
*   `MAP_ANONYMOUS`
    
    *   Mapping has no file backing
        
    *   `fd` is normally `-1`
        
    *   Can be combined with `MAP_PRIVATE` or `MAP_SHARED`
        
*   `MAP_FIXED`
    
    *   Forces mapping at the exact virtual address supplied in 1st argument
        
    *   May replace an existing mapping
        
*   `MAP_FIXED_NOREPLACE`
    
    *   Requires the requested virtual address range to be unused
        
    *   Fails instead of replacing an existing mapping
        

* * *

### 5th arg (file descriptor)

*   `fd` identifies the object backing the mapping
    
*   It is NOT necessarily a physical address
    
*   Depending on the mapping type:
    
    *   Hardware/device mapping
        
        *   `fd` can be a device file descriptor
            
        *   Example: `/dev/...`
            
    *   Regular file mapping
        
        *   `fd` is the file descriptor returned by `open()`
            
    *   POSIX shared memory
        
        *   `fd` is returned by `shm_open()`
            
    *   Anonymous memory
        
        *   `fd = -1`
            
        *   No file backing
            

* * *

### 6th arg

*   `offset` (type `off_t`)
    
*   Tells the kernel which portion of the backing object should be mapped
    
*   For file-backed mappings, it is normally page aligned
    
*   Anonymous memory
    
    *   `fd = -1`
        
    *   `offset = 0`
        
*   POSIX shared memory
    
    *   Usually `offset = 0`
        
    *   Can use a different page-aligned offset for a large shared-memory object
        
*   Hardware peripheral
    
    *   Device-driver-specific mmap semantics
        
    *   If mapping physical memory directly, the offset is normally the page-aligned physical address
        
        Let's assume physical address is `0x13E8`
        
        *   Page size = 4096 = `0x1000`
            
        *   Page boundaries:
            
            *   `0x0000`
                
            *   `0x1000`
                
            *   `0x2000`
                
            *   ...
                
        *   `0x13E8` is inside the page starting at `0x1000`
            
        
        ```c
        phy_base_addr = 0x13E8 & 0xFFFFF000;
        offset        = 0x13E8 & 0x00000FFF;
        ```
        
        Results:
        
        ```text
        phy_base_addr = 0x1000
        offset        = 0x03E8
        ```
        
        Then:
        
        ```c
        void *mapped_base = mmap(
            NULL,
            MAP_SIZE,
            PROT_READ | PROT_WRITE,
            MAP_SHARED,
            fd,
            phy_base_addr
        );
        
        volatile uint32_t *my_register =
            (volatile uint32_t *)((char *)mapped_base + offset);
        ```
        
        This resolves conceptually into:
        
        ```text
        physical page base = 0x1000
        in-page offset     = 0x03E8
        
        mapped_base + 0x03E8
        ```
        
        *   `mapped_base` is a virtual address
            
        *   `0x03E8` is an offset inside that mapped page
            
        *   Do NOT treat `mapped_base + offset` as a physical address
            
        
        Pointer arithmetic:
        
        ```c
        (char *)mapped_base + offset
        ```
        
        *   `char *` increments in 1-byte units
            
        *   Then cast the resulting address to the required register pointer type
            

* * *

* * *

## Hardware peripheral :

* * *

```c
#define PERIPHERAL_PHY_BASE 0x3F200000
#define MAP_SIZE            4096UL
#define MAP_MASK            (MAP_SIZE - 1)

int fd = open("/dev/memoryCreated", O_RDWR | O_SYNC);

/* Align physical address to page boundary */
void *mapped_base = mmap(
    NULL,
    MAP_SIZE,
    PROT_READ | PROT_WRITE,
    MAP_SHARED,
    fd,
    PERIPHERAL_PHY_BASE & ~MAP_MASK
);

/* Offset to the required register within the page */
volatile uint32_t *my_register =
    (volatile uint32_t *)((char *)mapped_base +
                          (PERIPHERAL_PHY_BASE & MAP_MASK));

/* Read and write hardware register */
uint32_t val = *my_register;

*my_register = val | 0x01;

/* Cleanup */
munmap(mapped_base, MAP_SIZE);
```

*   `mapped_base` is a virtual address returned by `mmap()`
    
*   `PERIPHERAL_PHY_BASE & ~MAP_MASK`
    
    *   gives page-aligned physical/device offset
        
*   `PERIPHERAL_PHY_BASE & MAP_MASK`
    
    *   gives offset inside that page
        
*   `volatile`
    
    *   tells compiler each register access is observable
        
    *   useful for MMIO registers
        
    *   does NOT provide locking or atomicity
        
*   In production Linux, hardware MMIO should normally be exposed through a proper device driver rather than directly using `/dev/mem`
    

* * *

# Coding Snippets for each of the case:

## Anonymous memory in RAM:

* * *

```c
#include <sys/mman.h>
#include <stdint.h>

#define BUFFER_SIZE 1000

int main()
{
    uint32_t *ptr = mmap(
        NULL,
        BUFFER_SIZE,
        PROT_READ | PROT_WRITE,
        MAP_PRIVATE | MAP_ANONYMOUS,
        -1,
        0
    );

    if (ptr == MAP_FAILED)
        return -1;

    uint32_t res = 1;

    for (int i = 1; i < 100; i++) {
        res = res * i;
        ptr[0] = res;
    }

    munmap(ptr, BUFFER_SIZE);

    return 0;
}
```

*   `MAP_ANONYMOUS` -> no file backing
    
*   `fd = -1`
    
*   `offset = 0`
    
*   `BUFFER_SIZE` means 1000 bytes
    
*   `sizeof(BUFFER_SIZE)` would NOT mean 1000 bytes
    
    *   because `BUFFER_SIZE` is a macro replaced by `1000`
        
    *   `sizeof(1000)` is normally `sizeof(int)`
        
*   `volatile` is not required for normal RAM
    
*   For inter-process shared anonymous memory:
    
    *   use `MAP_SHARED | MAP_ANONYMOUS`
        
    *   typically between related processes after `fork()`
        

* * *

* * *

## RAW shared memory in RAM:

* * *

### Reader

```c
#define SHARED_MEMORY "/shm1"
#define SEMAPHOR_NAME "/sem1"
#define BUFFER_SIZE 64

typedef struct {
    int sensor_id;
    float sensor_value;
    char manufacturer[BUFFER_SIZE];
} sensor;

int main()
{
    int shm_fd = shm_open(SHARED_MEMORY, O_RDWR);

    sensor *mapped_virt_addr = mmap(
        NULL,
        sizeof(sensor),
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        shm_fd,
        0
    );

    sem_t *sem = sem_open(SEMAPHOR_NAME, 0);

    while (1) {
        sem_wait(sem);

        printf("sensor_id:%d, sensor_value:%f, manufacturer:%s\n",
               mapped_virt_addr->sensor_id,
               mapped_virt_addr->sensor_value,
               mapped_virt_addr->manufacturer);

        sem_post(sem);
    }

    munmap(mapped_virt_addr, sizeof(sensor));
    close(shm_fd);
}
```

*   `mapped_virt_addr` points to the shared mapped memory
    
*   Do NOT create a separate local `sensor_data` and read from it
    
*   Both processes must access the data through the `mmap()` returned address
    
*   `sem_wait()` / `sem_post()` provide synchronization
    
*   Shared memory itself does NOT provide synchronization
    

* * *

### Writer

```c
#define SHARED_MEMORY "/shm1"
#define SEMAPHOR_NAME "/sem1"
#define BUFFER_SIZE 64

typedef struct {
    int sensor_id;
    float sensor_value;
    char manufacturer[BUFFER_SIZE];
} sensor;

int main()
{
    int shm_fd = shm_open(
        SHARED_MEMORY,
        O_CREAT | O_RDWR,
        0666
    );

    ftruncate(shm_fd, sizeof(sensor));

    sensor *mapped_virt_addr = mmap(
        NULL,
        sizeof(sensor),
        PROT_READ | PROT_WRITE,
        MAP_SHARED,
        shm_fd,
        0
    );

    sem_t *sem = sem_open(
        SEMAPHOR_NAME,
        O_CREAT,
        0666,
        1
    );

    while (1) {
        sem_wait(sem);

        mapped_virt_addr->sensor_id = 10;
        mapped_virt_addr->sensor_value = 14.566;

        snprintf(
            mapped_virt_addr->manufacturer,
            BUFFER_SIZE,
            "%s",
            "BOSCH-ADC-TEMPERATURE"
        );

        sem_post(sem);
    }

    munmap(mapped_virt_addr, sizeof(sensor));
    close(shm_fd);
}
```

*   `O_CREAT` -> correct flag
    
*   `ftruncate()` -> sets the size of the POSIX shared-memory object
    
*   `mmap()` -> maps that object into process virtual address space
    
*   Writer writes directly through the mapped pointer
    
*   Reader maps the same shared-memory object
    
*   Both processes can have DIFFERENT virtual addresses
    
*   They still access the same underlying shared-memory pages
    
*   `shm_unlink()` is used when the shared-memory object should be removed
    
*   `sem_close()` / `sem_unlink()` are used for semaphore cleanup
    

* * *

## Correct mmap Combinations

| Goal / Target Memory | Flag 1 | Flag 2 | Flag 3 | fd Argument | offset Argument | Behavior & Use Case |
| --- | --- | --- | --- | --- | --- | --- |
| Anonymous RAM (Private) | `MAP_PRIVATE` | `MAP_ANONYMOUS` | None | `-1` | `0` | Private anonymous memory |
| Anonymous RAM (Shared) | `MAP_SHARED` | `MAP_ANONYMOUS` | None | `-1` | `0` | Shared anonymous memory, commonly between parent/child after `fork()` |
| POSIX Shared Memory | `MAP_SHARED` | None | None | `shm_fd` | Usually `0` | Shared memory between unrelated processes |
| Hardware Peripheral | `MAP_SHARED` | None | None | Device fd | Driver/device-defined; commonly page-aligned | MMIO/device mapping |
| Regular File | `MAP_SHARED` or `MAP_PRIVATE` | None | None | `file_fd` | Page-aligned file offset | File-backed mapping |
| Specific Virtual Address | Any valid combination | Any valid combination | `MAP_FIXED` | As required | As required | Forces requested virtual address; dangerous because existing mappings may be replaced |

* * *

* * *

> `mmap()` creates a virtual memory mapping.
> 
> It does NOT simply mean "allocate physical RAM and return its address."
> 
> The backing memory can be:
> 
> *   Anonymous RAM
>     
> *   Shared memory
>     
> *   A regular file
>     
> *   Device/MMIO memory
>     
> 
> The process receives a virtual address.

* * *

### Understanding Process Memory Expansion & Page Allocation

To clear up the doubt, separate:

*   **Virtual Address Space**
    
*   **Physical RAM**
    
*   **Device/Physical Memory**
    

## 1\. How the Virtual Memory Space Changes

Assume a process already has several virtual-memory mappings.

When `mmap()` is called:

*   Kernel finds a suitable unused virtual-address range
    
*   Creates a new mapping/VMA
    
*   Returns the starting virtual address
    
*   Existing mappings are not automatically moved or extended
    

Example:

```text
PROCESS VIRTUAL ADDRESS SPACE

[ Existing mapping ]
[ Existing mapping ]
[ Existing mapping ]

        ↓ mmap()

[ Existing mapping ]
[ Existing mapping ]
[ Existing mapping ]
[ Anonymous mapping ]
```

The new mapping does not necessarily appear immediately after the old mapping.

It can be somewhere else in the process virtual address space.

* * *

## 2\. What happens in Physical RAM?

Virtual pages do NOT have to correspond to physically adjacent pages.

Example:

```text
PROCESS VIRTUAL ADDRESS SPACE

Virtual Page 1  ───────────────> Physical Page X
Virtual Page 2  ───────────────> Physical Page A
Virtual Page 3  ───────────────> Physical Page Z
Virtual Page 4  ───────────────> Physical Page B

Anonymous mapping

Virtual Page 10 ──────────────> Physical Page M
Virtual Page 11 ──────────────> Physical Page Q
```

*   Virtual addresses appear contiguous
    
*   Physical pages can be anywhere in RAM
    
*   MMU/page tables maintain the translation
    

* * *

## 3\. Anonymous Memory

When:

```c
mmap(NULL, size,
     PROT_READ | PROT_WRITE,
     MAP_PRIVATE | MAP_ANONYMOUS,
     -1, 0);
```

*   Kernel creates a virtual mapping
    
*   Physical pages may NOT be allocated immediately
    
*   Pages can be allocated on demand when accessed
    
*   This is called demand paging
    
*   Physical RAM is managed independently from virtual address placement
    

* * *

## 4\. Shared Memory

When two processes map the same shared-memory object:

```markdown
PROCESS A                         PROCESS B

Virtual Address A                Virtual Address B
       |                                |
       v                                v
   [ Page X ]                        [ Page Y ]
       |                                |
       +------------+  +---------------+
                    |  |
                    v  v
              Same backing pages
              / shared object
```

Important:

*   Virtual addresses do NOT need to be the same
    
*   Both mappings refer to the same underlying shared-memory object/pages
    
*   Changes made through one mapping can be observed through the other
    
*   Synchronization is still required
    

* * *

## 5\. Where does the I2C Peripheral fit?

The I2C controller is a hardware device, not normal RAM.

Conceptually:

```text
Process Virtual Address
        |
        v
      MMU
        |
        v
 Page table / kernel mapping
        |
        v
Device physical/bus address
        |
        v
I2C Controller Registers
        |
        v
I2C Bus
```

*   The process receives a virtual address
    
*   CPU accesses that virtual address
    
*   MMU translates it according to the mapping
    
*   The access reaches the device register space
    
*   It does NOT mean the register is stored in normal physical RAM
    
*   On Linux, a device driver normally controls this mapping and hardware access
    

* * *

## Summary of What Happens

When you call `mmap()`:

```plaintext
1. Kernel creates a virtual-memory mapping for the requested range.
2. Kernel returns the starting virtual address.
3. The mapping can refer to anonymous memory, shared memory, a file, or device memory.
4. Anonymous memory may obtain physical pages lazily when accessed.
5. Shared memory allows multiple processes to map the same backing object.
6. Different processes can use different virtual addresses for the same shared memory.
7. Device mappings refer to device/MMIO address space rather than normal RAM.
8. `mmap()` itself is a mapping operation; it is not simply a physical-memory allocation API.
```

* * *

## Quick Mental Model

```text
                    mmap()
                       |
                       v
              PROCESS VIRTUAL ADDRESS
                       |
          +------------+-------------+
          |            |             |
          v            v             v
     Anonymous      File-backed     Device/MMIO
       Memory         Memory          Memory
          |            |             |
          v            v             v
     Physical RAM    File data      Device registers
```

* * *

## Most Important Interview Points

```text
VA != PA

mmap() != malloc()

MAP_ANONYMOUS != "cannot be shared"

MAP_SHARED != synchronization

Different processes
        ↓
different virtual addresses
        ↓
same shared backing pages
```

## mmap() Flow

```text
User Process
     |
     | mmap()
     v
Kernel
     |
     +---- Choose Virtual Address
     |
     +---- Create Virtual Memory Mapping
     |
     +---- Identify Backing Object
     |       |
     |       +-- Anonymous
     |       +-- File
     |       +-- Shared Memory
     |       +-- Device
     |
     v
Return Virtual Address
     |
     v
Process accesses memory
     |
     v
MMU / Page Tables
     |
     +----> Physical RAM
     |
     +----> File-backed pages
     |
     +----> Device/MMIO
```

## Page Alignment Mental Model

```text
Physical / Device Address
        |
        v
   0x13E8
        |
        +--------------------+
        |                    |
        v                    v
Page Base                 Offset
0x1000                    0x3E8

        |
        v

mmap(..., offset = 0x1000)

        |
        v

mapped_base
     +
   0x3E8
     |
     v
Target register
```
