How to synchronize when sharing memory communication between Linux processes? (with source code)

Today we will talk about how to synchronize when using shared memory communication between processes to ensure the correctness of data?

In Linux, the shared memory communication between processes requires a synchronization mechanism to ensure the correctness and consistency of data. The commonly used synchronization mechanisms include 信号量, 互斥锁, and 条件变量so on.

Among them, using semaphores to synchronize shared memory access between processes is a common method. Each shared memory area can be associated with one or more semaphores to protect the read and write operations of the shared memory area. Before accessing the shared memory, the process needs to obtain the right to use the semaphore. After the read and write operations are completed, the right to use the semaphore is released so that other processes can access the shared memory area.

1. Semaphore synchronization

Here is a simple example program that shows how to use semaphores to synchronize read and write operations on shared memory regions:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h>

#define SHM_SIZE 1024
#define SEM_KEY 0x123456

// 定义联合体,用于信号量操作
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int main() {
    int shmid, semid;
    char *shmaddr;
    struct sembuf semops[2];
    union semun semarg;

    // 创建共享内存区域
    shmid = shmget(IPC_PRIVATE, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存区域附加到进程地址空间中
    shmaddr = shmat(shmid, NULL, 0);
    if (shmaddr == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // 创建信号量
    semid = semget(SEM_KEY, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        exit(1);
    }

    // 初始化信号量值为1
    semarg.val = 1;
    if (semctl(semid, 0, SETVAL, semarg) == -1) {
        perror("semctl");
        exit(1);
    }

    // 等待信号量
    semops[0].sem_num = 0;
    semops[0].sem_op = 0;
    semops[0].sem_flg = 0;
    if (semop(semid, semops, 1) == -1) {
        perror("semop");
        exit(1);
    }

    // 在共享内存中写入数据
    strncpy(shmaddr, "Hello, world!", SHM_SIZE);

    // 释放信号量
    semops[0].sem_num = 0;
    semops[0].sem_op = 1;
    semops[0].sem_flg = 0;
    if (semop(semid, semops, 1) == -1) {
        perror("semop");
        exit(1);
    }

    // 等待信号量
    semops[0].sem_num = 0;
    semops[0].sem_op = 0;
    semops[0].sem_flg = 0;
    if (semop(semid, semops, 1) == -1) {
        perror("semop");
        exit(1);
    }

    // 从共享内存中读取数据
    printf("Received message: %s\n", shmaddr);

    // 释放共享内存区域
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(1);
    }

    // 删除共享内存区域
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
        perror("shmctl");
        exit(1);
    }

    // 删除信号量
    if (semctl(semid, 0, IPC_RMID, semarg) == -1) {
        perror("semctl");
        exit(1);
    }

    return 0;

In this sample program, System V semaphores are used to synchronize read and write operations on shared memory. The program first creates a shared memory region and attaches it to the process address space. Then, use the semget() function to create a semaphore and initialize it to 1. Before writing shared memory data, the program waits for the semaphore using the semop() function. Once the right to use the semaphore is obtained, the program can write data in the shared memory area. After writing the data, the program uses the semop() function again to release the right to use the semaphore. When reading shared memory data, the program also needs to wait for the right to use the semaphore, and release the right to use the semaphore again after reading the data.

It should be noted that when using semaphores to synchronize shared memory access, it is necessary to ensure that each process performs read and write operations in a certain order. Otherwise, problems such as deadlock may occur. Therefore, when designing inter-process shared memory communication, it is necessary to carefully consider the read and write order of data, and adopt an appropriate synchronization mechanism to ensure the correctness and consistency of data.

2. Mutex synchronization

Mutex is also a common synchronization mechanism that can be used to implement shared memory access between multiple processes. In Linux, you can use the mutex in the pthread library to realize the synchronization of shared memory between processes.

The following is an example program that uses mutexes to implement shared memory synchronization:

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

#define SHM_SIZE 1024

// 共享内存结构体
typedef struct {
    pthread_mutex_t mutex;
    char data[SHM_SIZE];
} shm_data_t;

int main() {
    int fd;
    shm_data_t *shm_data;
    pthread_mutexattr_t mutex_attr;
    pthread_mutex_t *mutex;
    
    // 打开共享内存文件
    if ((fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666)) == -1) {
        perror("shm_open");
        exit(1);
    }

    // 调整共享内存文件大小
    if (ftruncate(fd, sizeof(shm_data_t)) == -1) {
        perror("ftruncate");
        exit(1);
    }

    // 将共享内存映射到进程地址空间中
    if ((shm_data = mmap(NULL, sizeof(shm_data_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    // 初始化互斥量属性
    pthread_mutexattr_init(&mutex_attr);
    pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);

    // 创建互斥量
    mutex = &(shm_data->mutex);
    pthread_mutex_init(mutex, &mutex_attr);

    // 在共享内存中写入数据
    pthread_mutex_lock(mutex);
    sprintf(shm_data->data, "Hello, world!");
    pthread_mutex_unlock(mutex);

    // 在共享内存中读取数据
    pthread_mutex_lock(mutex);
    printf("Received message: %s\n", shm_data->data);
    pthread_mutex_unlock(mutex);

    // 解除共享内存映射
    if (munmap(shm_data, sizeof(shm_data_t)) == -1) {
        perror("munmap");
        exit(1);
    }

    // 删除共享内存文件
    if (shm_unlink("/my_shm") == -1) {
        perror("shm_unlink");
        exit(1);
    }

    return 0;
}

In this sample program, the mutex in the pthread library is used to synchronize the read and write operations of the shared memory. The program first creates a shared memory file and maps it into the process address space. Then, use the pthread_mutex_init() function to create a mutex and initialize it as part of the shared memory. Before writing shared memory data, the program waits for the mutex using the pthread_mutex_lock() function. Once the right to use the mutex is obtained, the program can write data in the shared memory area. After writing the data, the program uses the pthread_mutex_unlock() function again to release the right to use the mutex. Before reading shared memory data, the program waits for the mutex again using the pthread_mutex_lock() function. Once the right to use the mutex is acquired, the program can read data in the shared memory area. After reading the data, the program uses the pthread_mutex_unlock() function again to release the right to use the mutex. Finally, the program unmaps the shared memory and deletes the shared memory file.

The use of mutexes to synchronize shared memory access has the following considerations:

1. The mutex needs to be initialized. Before creating a mutex, you need to use the pthread_mutexattr_init() function to initialize the mutex attribute, and use the pthread_mutexattr_setpshared() function to set the mutex attribute to PTHREAD_PROCESS_SHARED so that multiple processes can share the mutex.

2. Before accessing the shared memory, you need to use the pthread_mutex_lock() function to obtain the right to use the mutex. Once the right to use the mutex is obtained, the program can access the shared memory. After completing the access to the shared memory, you need to use the pthread_mutex_unlock() function to release the right to use the mutex so that other processes can access the shared memory.

3. The mutex must be stored in the shared memory area. When a mutex is created, it needs to be initialized as part of a shared memory region so that multiple processes can access the same mutex.

4. The program must ensure the consistency of the mutex. When multiple processes share the same mutex, the consistency of the mutex must be guaranteed. Otherwise, multiple processes may access the shared memory area at the same time, resulting in data errors or system crashes.

In short, using mutexes to synchronize shared memory access can effectively avoid the problem of multiple processes accessing shared memory areas at the same time, thereby ensuring data consistency and program stability. In actual programming, different synchronization mechanisms need to be selected according to specific requirements to ensure the correctness and efficiency of the program.

 Information through train: Linux kernel source code technology learning route + video tutorial kernel source code

Learning through train: Linux kernel source code memory tuning file system process management device driver/network protocol stack

3. Condition variable synchronization

Under Linux, you can use condition variables (Condition Variable) to achieve synchronization between multiple processes. Condition variables are often used in conjunction with mutexes (Mutex) to synchronize access to data in a shared memory area.

A condition variable is a thread synchronization mechanism used to wait for or notify the occurrence of an event. When a process needs to wait for an event to occur, it can block itself by calling the pthread_cond_wait() function and release the mutex. Once an event occurs, other processes can notify waiting threads by calling the pthread_cond_signal() or pthread_cond_broadcast() functions. After the waiting thread receives the notification, it will reacquire the mutex and continue execution.

In shared memory communication, condition variables can be used to achieve synchronization between processes. The specific operation steps are as follows:

Initialize mutexes and condition variables. Before creating shared memory, you need to use the pthread_mutexattr_init() and pthread_condattr_init() functions to initialize the mutex attributes and condition variable attributes, respectively. Then, the mutex attributes and condition variable attributes need to be set to PTHREAD_PROCESS_SHARED using the pthread_mutexattr_setpshared() and pthread_condattr_setpshared() functions so that multiple processes can share them.

Wait on a condition variable. Before reading shared memory, a program can use the pthread_cond_wait() function to wait on a condition variable. Calling the pthread_cond_wait() function will automatically release the mutex and block the current process. Once the other process signals that the condition variable has changed, the waiting thread reacquires the mutex and continues executing.

Send a signal notifying a condition variable change. After writing data to shared memory, the program can use the pthread_cond_signal() or pthread_cond_broadcast() function to send a signal to notify the condition variable of a change. Calling the pthread_cond_signal() function sends a signal to the waiting thread that the condition variable has changed, and calling the pthread_cond_broadcast() function sends a signal to all waiting threads that the condition variable has changed.

There are several considerations for using condition variables to synchronize shared memory access:

1. Programs must use mutexes to protect shared memory. Before using the condition variable, the program must obtain the right to use the mutex to protect the data in the shared memory area from being accessed by multiple processes at the same time.

2. The program must ensure the consistency of the condition variables. When multiple processes share the same condition variable, the consistency of the condition variable must be guaranteed. Otherwise, multiple processes may access the shared memory area at the same time, resulting in data errors or system crashes.

3. The program must use condition variables correctly. When using condition variables, you need to use the pthread_cond_wait(), pthread_cond_signal() and pthread_cond_broadcast() functions correctly, otherwise it may cause deadlock or other problems.

4. The program must handle the signal correctly. When the pthread_cond_wait() function is called, the program may return early due to receiving a signal. At this time, the program needs to handle the signal correctly.

The following is a sample code that uses condition variables to synchronize shared memory between processes:

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

#define SHM_SIZE 4096
#define SHM_NAME "/myshm"
#define SEM_NAME "/mysem"

typedef struct {
    pthread_mutex_t mutex;
    pthread_cond_t cond;
    char buffer[SHM_SIZE];
} shm_t;

int main(int argc, char *argv[]) {
    int fd, pid;
    shm_t *shm;
    pthread_mutexattr_t mutex_attr;
    pthread_condattr_t cond_attr;

    // 创建共享内存区域
    fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
    if (fd < 0) {
        perror("shm_open");
        exit(1);
    }

    // 设置共享内存大小
    if (ftruncate(fd, sizeof(shm_t)) < 0) {
        perror("ftruncate");
        exit(1);
    }

    // 将共享内存映射到进程地址空间
    shm = mmap(NULL, sizeof(shm_t), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (shm == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }

    // 初始化互斥量属性和条件变量属性
    pthread_mutexattr_init(&mutex_attr);
    pthread_condattr_init(&cond_attr);
    pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
    pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);

    // 初始化互斥量和条件变量
    pthread_mutex_init(&shm->mutex, &mutex_attr);
    pthread_cond_init(&shm->cond, &cond_attr);

    // 创建子进程
    pid = fork();
    if (pid < 0) {
        perror("fork");
        exit(1);
    }

    if (pid == 0) {
        // 子进程写入共享内存
        sleep(1);
        pthread_mutex_lock(&shm->mutex);
        sprintf(shm->buffer, "Hello, world!");
        pthread_cond_signal(&shm->cond);
        pthread_mutex_unlock(&shm->mutex);
        exit(0);
    } else {
        // 父进程读取共享内存
        pthread_mutex_lock(&shm->mutex);
        pthread_cond_wait(&shm->cond, &shm->mutex);
        printf("Received message: %s\n", shm->buffer);
        pthread_mutex_unlock(&shm->mutex);
    }

    // 删除共享内存
    if (shm_unlink(SHM_NAME) < 0) {
        perror("shm_unlink");
        exit(1);
    }

    return 0;
}

In this example, the program creates a shared memory area named "/myshm" and maps it into the process address space. The program then uses mutexes and condition variables to synchronize access between processes to the shared memory area. Specifically, the parent process first locks the mutex, then waits for the condition variable to be signaled. After the child process waits for one second, it locks the mutex, writes the "Hello, world!" string to the shared memory area, then sends the condition variable signal, and releases the mutex. At this point, the parent process will receive the condition variable signal and lock the mutex, read the contents of the shared memory area, and release the mutex.

It should be noted that when using condition variables, we need to follow some rules to ensure the correctness of the program, such as locking the mutex when waiting for the condition variable, and using a while loop to check whether the value of the condition variable meets the requirements, waiting A thread that signals a condition variable must lock the mutex before waiting, unlock the mutex after waiting, a thread that waits for a condition variable signal may return early because it received the signal, and so on.

In short, using mutexes and condition variables to realize the synchronization of inter-process shared memory communication requires us to carefully consider all possible situations in the program, and correctly use mutexes and condition variable functions to synchronize access between processes.

summary

Well, this time we explained the commonly used synchronization mechanisms through the shared memory communication method between processes under Linux: semaphores, mutexes, and condition variables. I hope it will be helpful to my friends in their daily programming.

Original Author: Linux Arsenal

 

Guess you like

Origin blog.csdn.net/youzhangjing_/article/details/132024093