MST
星途 面试题库

面试题:C语言多进程资源竞争下的内存共享与同步

在Linux系统中,用C语言创建多个进程,这些进程需要共享一块内存区域来进行数据交互。但这可能会引发资源竞争。请详细阐述如何使用共享内存(shmget等函数)和信号量(semget等函数)来实现进程间的内存共享与同步,避免数据竞争,并写出一个完整的示例代码框架,包括必要的错误处理。
23.0万 热度难度
编程语言C

知识考点

AI 面试

面试题答案

一键面试

共享内存与信号量实现进程间内存共享与同步原理

  1. 共享内存
    • shmget函数用于创建或获取共享内存段。它通过key值来标识共享内存段,key可以使用ftok函数生成。shmget返回一个共享内存标识符。
    • shmat函数用于将共享内存段连接到进程的地址空间,这样进程就可以像访问普通内存一样访问共享内存。
    • shmdt函数用于将共享内存段从进程的地址空间分离。
    • shmctl函数用于对共享内存段执行各种控制操作,如删除共享内存段等。
  2. 信号量
    • semget函数用于创建或获取信号量集。同样通过key值来标识信号量集,返回一个信号量标识符。
    • semop函数用于对信号量集中的信号量执行操作,比如获取信号量(将信号量值减1)或释放信号量(将信号量值加1)。
    • semctl函数用于对信号量集执行各种控制操作,如初始化信号量等。

通过信号量来控制对共享内存的访问,在进程访问共享内存前获取信号量,访问完后释放信号量,这样可以避免数据竞争。

示例代码框架

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

// 定义信号量操作结构体
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};

// 信号量操作函数
void semaphore_operation(int semid, int semnum, int op) {
    struct sembuf sem_op;
    sem_op.sem_num = semnum;
    sem_op.sem_op = op;
    sem_op.sem_flg = 0;
    if (semop(semid, &sem_op, 1) == -1) {
        perror("semop");
        exit(1);
    }
}

int main() {
    key_t key_shm = ftok(".", 'a');
    if (key_shm == -1) {
        perror("ftok for shm");
        exit(1);
    }
    key_t key_sem = ftok(".", 'b');
    if (key_sem == -1) {
        perror("ftok for sem");
        exit(1);
    }

    // 创建共享内存
    int shmid = shmget(key_shm, 1024, IPC_CREAT | 0666);
    if (shmid == -1) {
        perror("shmget");
        exit(1);
    }

    // 创建信号量
    int semid = semget(key_sem, 1, IPC_CREAT | 0666);
    if (semid == -1) {
        perror("semget");
        shmctl(shmid, IPC_RMID, NULL);
        exit(1);
    }

    // 初始化信号量
    union semun sem_union;
    sem_union.val = 1;
    if (semctl(semid, 0, SETVAL, sem_union) == -1) {
        perror("semctl SETVAL");
        shmctl(shmid, IPC_RMID, NULL);
        semctl(semid, 0, IPC_RMID, NULL);
        exit(1);
    }

    // 连接共享内存到进程地址空间
    char *shared_memory = (char *)shmat(shmid, NULL, 0);
    if (shared_memory == (void *)-1) {
        perror("shmat");
        shmctl(shmid, IPC_RMID, NULL);
        semctl(semid, 0, IPC_RMID, NULL);
        exit(1);
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        shmdt(shared_memory);
        shmctl(shmid, IPC_RMID, NULL);
        semctl(semid, 0, IPC_RMID, NULL);
        exit(1);
    } else if (pid == 0) {
        // 子进程
        semaphore_operation(semid, 0, -1); // 获取信号量
        // 子进程访问共享内存
        sprintf(shared_memory, "Data from child");
        printf("Child process wrote: %s\n", shared_memory);
        semaphore_operation(semid, 0, 1); // 释放信号量
        shmdt(shared_memory);
    } else {
        // 父进程
        semaphore_operation(semid, 0, -1); // 获取信号量
        // 父进程访问共享内存
        printf("Parent process read: %s\n", shared_memory);
        semaphore_operation(semid, 0, 1); // 释放信号量
        wait(NULL);
        shmdt(shared_memory);
        shmctl(shmid, IPC_RMID, NULL);
        semctl(semid, 0, IPC_RMID, NULL);
    }

    return 0;
}

在上述代码中:

  1. 首先使用ftok函数生成共享内存和信号量的key值。
  2. 然后分别调用shmgetsemget创建共享内存段和信号量集。
  3. 初始化信号量为1,表示共享内存初始状态为可访问。
  4. 通过shmat将共享内存连接到进程地址空间。
  5. 使用fork创建子进程,子进程和父进程通过获取和释放信号量来同步对共享内存的访问。
  6. 最后在程序结束时,通过shmdt分离共享内存,shmctl删除共享内存段,semctl删除信号量集。