[Linux operating system] Shared storage mapping (mmap) in Linux system programming

In Linux system programming, communication between processes is an important task. Shared memory mapping (mmap) is an efficient process communication method that allows multiple processes to share the same memory area to achieve data sharing and communication. This article will introduce the concept, principle, usage and precautions of shared memory mapping to help readers better understand and apply shared memory mapping.

Article directory

    • 1. The concept of shared storage mapping
    • 2. Principle of shared storage mapping
    • 3. How to use shared storage mapping
      • 3.1 mmap
        • 3.1.1 Function prototype, parameters and return value
        • 3.1.2 Precautions
        • 3.1.3 Function examples
      • 3.2 munmap
        • 3.2.1 Function prototype, parameters and return value
        • 3.2.2 Precautions
        • 3.2.3 Function examples
    • 4. Parent-child process mmap communication
      • 4.1 The specific steps are as follows:
      • 4.2 Example
      • 4.3 Code explanation
    • 5. mmap communication between processes that are not related by blood
      • 5.1 The specific steps are as follows:
      • 5.2 Example
      • 5.3 Code explanation
    • 6. Conclusion

1. The concept of shared storage mapping

Shared memory mapping is a technique for mapping a file or device into the virtual address space of a process. Through shared memory mapping, multiple processes can access the same physical memory area, thereby realizing data sharing and communication.

Key benefits of shared storage mapping include:

  • Efficiency: Shared storage mapping avoids data duplication and transmission, and improves the efficiency of data access.
  • Flexibility: Shared memory mappings can be used for different types of data, including files, devices, and anonymous memory.
  • Simplicity: Shared memory mapping is easy to use, and only a few system calls are needed to complete the mapping and unmapping operations.

2. The principle of shared memory mapping

The principle of shared memory mapping is to map a file or device to a contiguous memory area in the process’s virtual address space. This memory area is called a shared memory segment. Multiple processes can share and communicate data by accessing shared memory segments.

The process of shared storage mapping includes the following steps:

  1. Create or open a shared memory segment.
  2. Map the shared memory segment into the process’s virtual address space.
  3. Read and write data in the shared memory segment.
  4. Unmap the shared memory segment.

3. How to use shared memory mapping

3.1 mmap

3.1.1 Function prototype, parameters and return value

The prototype of the mmap function is as follows:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

Parameter Description:

  • addr: Specifies the starting address of the mapping, usually set to NULL to allow the system to automatically select the appropriate address.
  • length: Specifies the length of the mapping, in bytes.
  • prot: Specifies the protection method of the mapping area, which can be a combination of the following values:
    • PROT_READ: Readable
    • PROT_WRITE: writable
    • PROT_EXEC: executable
    • PROT_NONE: not accessible
  • flags: Flags specifying the mapping area, which can be a combination of the following values:
    • MAP_SHARED: Shared mapping, multiple processes can access the same mapping area
    • MAP_PRIVATE: Private mapping, each process has its own independent mapping area
    • MAP_FIXED: The mapping area must start from the specified address. If the specified address is already occupied, the mapping fails.
    • MAP_ANONYMOUS: Creates an anonymous mapping area that is not associated with a file
  • fd: Specifies the file descriptor to be mapped. If the MAP_ANONYMOUS flag is used, this parameter is -1.
  • offset: Specifies the offset of the file, usually set to 0.

Return value:

  • On success, returns the starting address of the mapped region.
  • On failure, return MAP_FAILED.

3.1.2 Precautions

  1. Correct setting of parameters: The parameters of mmap function include starting address, mapping length, access permission, mapping method, etc. These parameters need to be set correctly according to your specific needs to ensure that the mapping behaves as expected.

  2. Error handling: The return value of the mmap function is the starting address of the mapping area. If the mapping fails, the return value is MAP_FAILED. After calling the mmap function, you need to check whether the return value is MAP_FAILED, determine the specific error cause based on the value of errno, and handle the error accordingly.

  3. Memory alignment: The starting address of the mapping area returned by the mmap function is usually aligned according to the system’s memory page size ( 4096 ). When using mapped areas, you need to ensure that you access the correct offset and length to avoid accessing out-of-bounds or unmapped memory.

  4. Unmap: When the mapped area is no longer needed, the munmap function should be used to unmap the mapped area. After unmapping, the previously mapped memory area is no longer accessible and access to the area will cause a segfault. Therefore, it is necessary to ensure that the timing and scope of unmapping are correct to avoid memory leaks or access to illegal memory.

  5. Synchronization of shared memory: If you use the mmap function to create a shared memory area (using the MAP_SHARED flag), you need to synchronize between multiple processes to avoid race conditions and data inconsistencies. Mechanisms such as semaphores and mutex locks can be used to achieve synchronization between processes.

  6. Synchronization of file mapping: If you use the mmap function to map a file into memory, you need to note that modifications to the data in memory may not be written to the file immediately. You can use the msync function to synchronize the modified data to the file, or use the munmap function to automatically synchronize when unmapping.

  7. Security: When using the mmap function, you need to pay attention to security issues, especially when mapping files. It is necessary to ensure that access to the mapped area is legal to avoid potential security holes, such as illegal memory access or code injection through the mapped area.

3.1.3 Function examples

Here is an example using the mmap function:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {<!-- -->
    int fd;
    char *shared_memory;

    // open a file
    fd = open("shared_memory", O_RDWR);
    if (fd == -1) {<!-- -->
        perror("open");
        return 1;
    }

    // Map the file into the virtual address space of the process
    shared_memory = (char *)mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shared_memory == MAP_FAILED) {<!-- -->
        perror("mmap");
        return 1;
    }

    // read the data in the shared memory segment
    printf("Data read from shared memory: %s\
", shared_memory);

    // Unmap the file
    if (munmap(shared_memory, 1024) == -1) {<!-- -->
        perror("munmap");
        return 1;
    }

    // close the file
    close(fd);

    return 0;
}

In this example, we open a file named “shared_memory” and map it into the process’s virtual address space. We then read the data in the shared memory segment and unmapped the file.

Communication between two processes:

First, process A creates a shared memory segment and maps it to its own virtual address space:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main() {<!-- -->
    int fd;
    char *shared_memory;
    const char *message = "Hello from Process A!";

    //Create or open a file
    fd = open("shared_memory", O_CREAT | O_RDWR, 0666);
    if (fd == -1) {<!-- -->
        perror("open");
        return 1;
    }

    //Adjust file size
    if (ftruncate(fd, 1024) == -1) {<!-- -->
        perror("ftruncate");
        return 1;
    }

    //Map the file into the virtual address space of the process
    shared_memory = (char *)mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shared_memory == MAP_FAILED) {<!-- -->
        perror("mmap");
        return 1;
    }

    // write data to the shared memory segment
    strcpy(shared_memory, message);

    // Unmap the file
    if (munmap(shared_memory, 1024) == -1) {<!-- -->
        perror("munmap");
        return 1;
    }

    // close file
    close(fd);

    return 0;
}

Then, process B opens the same shared memory segment and maps it into its own virtual address space:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main() {<!-- -->
    int fd;
    char *shared_memory;

    // open a file
    fd = open("shared_memory", O_RDWR);
    if (fd == -1) {<!-- -->
        perror("open");
        return 1;
    }

    //Map the file into the virtual address space of the process
    shared_memory = (char *)mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shared_memory == MAP_FAILED) {<!-- -->
        perror("mmap");
        return 1;
    }

    //Read the data in the shared memory segment
    printf("Data read from shared memory: %s\
", shared_memory);

    // Unmap the file
    if (munmap(shared_memory, 1024) == -1) {<!-- -->
        perror("munmap");
        return 1;
    }

    // close file
    close(fd);

    return 0;
}

In the above example, process A creates a shared memory segment and writes the data “Hello from Process A!”. Process B opens the same shared memory segment and reads the data written by process A.

3.2 munmap

3.2.1 Function prototype, parameters and return value

The munmap function is used to unmap the mapped area.

The function prototype is as follows:

int munmap(void *addr, size_t length);

Parameters:

  1. addr parameters:

    • Points to the starting address to be unmapped.
    • Usually a pointer to the mapped area returned by the mmap function.
  2. length parameters:

    • The length to unmap.
    • Usually the length of the mapping area passed in through the mmap function.

Return value:

The return value of the munmap function is 0 for success, -1 for failure, and errno is set to indicate the cause of the error.

3.2.2 Notes

  • The addr parameter must be the starting address of a valid mapping area, otherwise unmapping will fail.
  • The length parameter must be consistent with the length used in the original mapping, otherwise unmapping will fail.
  • After unmapping, the previously mapped memory area is no longer accessible and access to the area will cause a segfault.
  • After unmapping, if the MAP_SHARED flag was used previously and other processes are still accessing the mapped area, unexpected results may occur.
  • After unmapping, if the MAP_ANONYMOUS flag was used before and the pointer to the mapped area was not saved, the mapped area will not be accessed again.

When using the munmap function, you need to ensure that the timing and scope of unmapping are correct to avoid memory leaks or illegal memory access.

3.2.3 Function Example

Here is an example of unmapping using the munmap function:

#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>

int main() {<!-- -->
    int *data;
    size_t length = sizeof(int) * 10;

    // Use the mmap function to create a mapping area
    data = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
    if (data == MAP_FAILED) {<!-- -->
        perror("mmap");
        exit(1);
    }

    // write data in the mapped area
    for (int i = 0; i < 10; i ++ ) {<!-- -->
        data[i] = i;
    }

    // Unmap
    if (munmap(data, length) == -1) {<!-- -->
        perror("munmap");
        exit(1);
    }

    // try to access the unmapped memory region
    printf("%d\
", data[0]); // This line of code will cause a segfault

    return 0;
}

In this example, a mapping area of 10 ints is first created using the mmap function and stored in the data pointer. Then, numbers from 0 to 9 are written to the mapped area through a loop. Next, use the munmap function to unmap the mapped area. Finally, an attempt is made to access an unmapped memory region, which causes a segfault.

4. Parent-child process mmap communication

4.1 The specific steps are as follows:

  1. The parent process creates a shared memory area. You can use the shm_open function to create a named shared memory object, or you can use the mmap function to map a file into memory.

  2. The parent process uses the fork function to create a child process.

  3. The child process can directly access the shared memory by inheriting the shared memory descriptor of the parent process or the address of the file mapping.

  4. Parent and child processes can communicate through shared memory. A data structure can be defined in the shared memory. The parent process and the child process communicate by reading and writing the data structure.

  5. Parent-child processes need to use synchronization mechanisms (such as semaphores, mutexes, etc.) to ensure that access to shared memory is safe and avoid race conditions and data inconsistencies.

  6. When the communication is completed, the parent process and the child process need to use the munmap function to unmap the shared memory to release resources.

4.2 Example

Parent and child processes communicate through shared memory:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>

typedef struct {<!-- -->
    int value;
} SharedData;

int main() {<!-- -->
    int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
    ftruncate(fd, sizeof(SharedData));

    SharedData* sharedData = mmap(NULL, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    pid_t pid = fork();
    if (pid == 0) {<!-- -->
        // child process
        printf("Child process: value = %d\
", sharedData->value);
        sharedData->value = 100;
        printf("Child process: value = %d\
", sharedData->value);
        exit(0);
    } else if (pid > 0) {<!-- -->
        // parent process
        sharedData->value = 50;
        printf("Parent process: value = %d\
", sharedData->value);
        wait(NULL);
        printf("Parent process: value = %d\
", sharedData->value);

        munmap(sharedData, sizeof(SharedData));
        shm_unlink("/myshm");
    } else {<!-- -->
        perror("fork");
        exit(1);
    }

    return 0;
}

4.3 Code explanation

In this example, a named shared memory object is first created using the shm_open function and its size is set to the size of the SharedData structure. Then, use the mmap function to map the shared memory into the memory, and obtain the address of the shared memory. Next, use the fork function to create a child process.

In the child process, first print the value in the shared memory, then modify it to 100, and print it again. In the parent process, first set the value in the shared memory to 50, and then wait for the child process to exit. Finally, print the value in the shared memory, then use the munmap function to unmap the shared memory, and use the shm_unlink function to delete the shared memory object.

5. mmap communication between non-blood related processes

5.1 The specific steps are as follows:

  1. To create a shared memory area, you can use the shm_open function to create a named shared memory object, or you can use the mmap function to map a file into memory.

  2. All processes that need to communicate use the shm_open or mmap function to open or map the same shared memory object.

  3. Processes communicate through shared memory, and a data structure can be defined in shared memory, and processes communicate by reading and writing the data structure.

  4. Processes need to use synchronization mechanisms (such as semaphores, mutexes, etc.) to ensure that access to shared memory is safe and avoid race conditions and data inconsistencies.

  5. When the communication is completed, the process needs to use the munmap function to unmap the shared memory, and the shm_unlink function to delete the shared memory object.

5.2 Example

Unrelated processes communicate through shared memory:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <sys/wait.h>

typedef struct {<!-- -->
    int value;
} SharedData;

int main() {<!-- -->
    int fd = shm_open("/myshm", O_CREAT | O_RDWR, 0666);
    ftruncate(fd, sizeof(SharedData));

    SharedData* sharedData = mmap(NULL, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);

    pid_t pid = fork();
    if (pid == 0) {<!-- -->
        // child process
        printf("Child process: value = %d\
", sharedData->value);
        sharedData->value = 100;
        printf("Child process: value = %d\
", sharedData->value);
        exit(0);
    } else if (pid > 0) {<!-- -->
        // parent process
        sharedData->value = 50;
        printf("Parent process: value = %d\
", sharedData->value);
        wait(NULL);
        printf("Parent process: value = %d\
", sharedData->value);

        munmap(sharedData, sizeof(SharedData));
        shm_unlink("/myshm");
    } else {<!-- -->
        perror("fork");
        exit(1);
    }

    return 0;
}

5.3 Code explanation

This example is similar to the previous example of mmap communication between parent and child processes, but here we use named shared memory objects. Unrelated processes realize shared memory communication by opening or mapping the same named shared memory object. The other steps are the same as in the previous example.

It should be noted that when communicating between unrelated processes, it is necessary to ensure that the order in which shared memory objects are created and opened is consistent, and that the size of shared memory and the definition of data structures are consistent. In addition, appropriate synchronization mechanisms need to be used to ensure that access to shared memory is safe.

6. Conclusion

Shared memory is a mechanism for inter-process communication that allows multiple processes to access the same memory area for efficient data sharing. When using shared memory for inter-process communication, you need to pay attention to the following key points:

  1. Create shared memory: You can use the shm_open function to create a named shared memory object, or you can use the mmap function to map a file into memory. The size needs to be specified when creating shared memory.

  2. Access shared memory: The process maps the shared memory into its own address space through the mmap function, so that it can directly read and write data in the shared memory. Care should be taken to ensure that the order in which shared memory objects are created and opened is consistent.

  3. Synchronization mechanism: Since multiple processes accessing shared memory at the same time may cause race conditions and data inconsistencies, an appropriate synchronization mechanism needs to be used to ensure that access to shared memory is safe. Commonly used synchronization mechanisms include semaphores, mutexes, and condition variables.

  4. Unmapping and deleting shared memory: When communication is completed, the process needs to use the munmap function to unmap the shared memory and the shm_unlink function to delete the shared memory object.

It should be noted that the use of shared memory for inter-process communication needs to consider the issues of synchronization and data consistency. In actual use, appropriate modifications and extensions are also required according to specific needs to ensure the correctness and reliability of communication.

Commonly used functions and system calls include:

  • shm_open: Create or open a named shared memory object.
  • ftruncate: Set the size of shared memory.
  • mmap: Map shared memory into the address space of the process.
  • munmap: Unmap the shared memory.
  • shm_unlink: delete the named shared memory object.

In addition, you can also use other synchronization mechanism functions, such as sem_init, sem_wait, sem_post, etc., to achieve synchronous access to shared memory.