《Linux应用进程间通信(四) — 共享内存》

1.共享内存

  共享内存:共享内存是一种最为高效的进程间通信方式。因为数据可以直接读写内存,不需要任何数据的拷贝。内核专门留出一块内存区,这段内存区可以由需要访问的进程将其中映射到自己的私有地址空间。但同时由于多个进程共享一段内存,因此也需要依靠同步机制,如互斥锁和信号量。

 

2.shmget

#include <sys/shm.h>

int shmget(key_t key, int size, int shmflg)
第一个参数key:有效地为共享内存段命名
第二个参数size:以字节为单位制定需要共享的内存容量。(实现通常将其向上取为系统页长的整数倍。但是,若应用指定的size值并非系统页长的整数倍,那么最后最后一页余下部分不可使用。)
第三个参数shmflg:权限标志
返回值: 成功:非负整数即共享内存标识符; 失败:-1

  作用:创建新的共享内存或引用一个现有的共享内存。当创建一个新的共享内存时,初始化shmid_ds结构下列成员。

  ipc_perm结构。其中mode按shmflg中的相应权限位设置

  shm_lpid、shm_nattach、shm_atime和shm_dtime都设置为0.

  shm_ctime设置为当前时间

  shm_segsz设置为请求的size

 

  如果创建一个新的共享内存段(通常在服务器进程中),则必须指定其size。如果正在引用一个现存的段(一个客户进程),则将size指定为0。当创建一个新共享内存时,段内的内容初始化为0.

 

3.shmat

#include <sys/shm.h>

char *shmat(int shmid, const void*shmaddr,int shmflg)
第一个参数shmid:共享内存标识符
第二个参数shmaddr:将共享内存映射到指定位置
  如果shmaddr为0,则此段连接到由内核选择的第一个可用地址上。(这是推荐的使用方式)
  如果shmaddr非0,并且没有指定SHM_RND,则此段连接到shmaddr所指定的地址上。
  如果shmaddr非0,并且指定了SHM_RND,则此段连接到(shmaddr—(shmaddr mod SHMLBA))所表示的地方。
  SHM_RND命令的意思是“取整”。SHMLBA的意思是“低边界地址倍数”(了解以下。现在基本使用这一种方式)
第三个地址:SHM_RND(这个标志与shm_addr联合使用,用来控制共享内存连接的地址)       SHM_RDONLY(使它连接的内存只读)       默认0:共享内存可读可写 返回值:返回一个指向共享内存第一个字节的指针; 失败:-1

  作用:第一次创建共享内存时,它不能被任何进程访问。要想启用该共享内存的访问,必须将其连接到一个进程的地址空间中。

 

4.shmdt

#include <sys/shm.h>

int shmdt(const void *shmaddr)
第一个参数shmaddr:
shmaddr:被映射的共享内存段地址,在调用shmat时的返回值。
返回值: 成功:0; 失败:-1

  作用:将共享内存从当前进程中分离(并不是删除)。 如果调用成功,shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1。

 

5.shmctl

#include <sys/shm.h>

int shmctl(int shm_id, int command, struct shmid_ds *buf);
第一个参数shm_id:shmget返回的共享内存标识符
第二个参数command:要采取的动作,可以取三个值
    IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值
    IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
    IPC_RMID:删除共享内存段
第三个参数buf:是一个指针,它指向包含共享内存模式和访问权限的结构
返回值:成功:0;失败:-1
struct shmid_ds{
  uid_t shm_perm.uid,
  uid_t shm_perm.gid;
  mode_t shm_perm.mode;
};

 

共享内存多进程数据安全

  共享内存是一种高效的多进程通信方式,但如果不加以控制,多个进程同时读写共享内存可能会导致数据不一致、竞争条件等问题。为了实现多进程读写共享内存的安全性,可以采用以下方法:

 1.互斥锁
写进程demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <pthread.h>

#define SHM_SIZE 1024

typedef struct {
    pthread_mutex_t mutex;
    char data[SHM_SIZE];
} SharedMemory;

int main() {
    key_t key = ftok("shmfile", 65); // 生成一个唯一的 key
    int shmid = shmget(key, sizeof(SharedMemory), 0666 | IPC_CREAT);

    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }

    SharedMemory *shm = (SharedMemory *)shmat(shmid, NULL, 0);

    if (shm == (SharedMemory *)-1) {
        perror("shmat failed");
        exit(1);
    }

    // 初始化互斥锁
    pthread_mutexattr_t mutex_attr;
    pthread_mutexattr_init(&mutex_attr);
    pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
    pthread_mutex_init(&shm->mutex, &mutex_attr);

    // 写入数据
    pthread_mutex_lock(&shm->mutex);
    strcpy(shm->data, "Hello from Process 1!");
    printf("Process 1: Data written to shared memory\n");
    pthread_mutex_unlock(&shm->mutex);

    // 等待进程2读取数据
    printf("Process 1: Press enter to exit...\n");
    getchar();

    // 分离共享内存
    shmdt(shm);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}
View Code

读进程demo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <pthread.h>

#define SHM_SIZE 1024

typedef struct {
    pthread_mutex_t mutex;
    char data[SHM_SIZE];
} SharedMemory;

int main() {
    key_t key = ftok("shmfile", 65); // 生成一个唯一的 key
    int shmid = shmget(key, sizeof(SharedMemory), 0666);

    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }

    SharedMemory *shm = (SharedMemory *)shmat(shmid, NULL, 0);

    if (shm == (SharedMemory *)-1) {
        perror("shmat failed");
        exit(1);
    }

    // 读取数据
    pthread_mutex_lock(&shm->mutex);
    printf("Process 2: Data read from shared memory: %s\n", shm->data);
    pthread_mutex_unlock(&shm->mutex);

    // 分离共享内存
    shmdt(shm);

    return 0;
}
View Code

  说明:

  • 在共享内存中定义一个互斥锁 pthread_mutex_t
  • 使用 pthread_mutexattr_setpshared 设置互斥锁为进程间共享。
  • 在写进程(其中一个非特定)中初始化互斥锁,读进程不需要重新初始化。
  • 互斥锁的初始化和销毁通常在同一个进程中完成(这里选择在写进程中完成)

 

2.信号量
写进程demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/types.h>

#define SHM_SIZE 1024

typedef struct {
    char data[SHM_SIZE];
} SharedMemory;

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int main() {
    key_t key = ftok("shmfile", 65); // 生成一个唯一的 key
    int shmid = shmget(key, sizeof(SharedMemory), 0666 | IPC_CREAT);

    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }

    SharedMemory *shm = (SharedMemory *)shmat(shmid, NULL, 0);

    if (shm == (SharedMemory *)-1) {
        perror("shmat failed");
        exit(1);
    }

    // 创建信号量
    int semid = semget(key, 1, 0666 | IPC_CREAT);
    union semun su;
    su.val = 1; // 初始信号量值为1,表示资源可用
    if (semctl(semid, 0, SETVAL, su) == -1) {
        perror("semctl failed");
        exit(1);
    }

    struct sembuf sb = {0, -1, 0}; // P操作:减少信号量
    semop(semid, &sb, 1);

    // 写入数据
    strcpy(shm->data, "Hello from Process 1!");
    printf("Process 1: Data written to shared memory\n");

    sb.sem_op = 1; // V操作:增加信号量
    semop(semid, &sb, 1);

    // 等待进程2和进程3读取数据
    printf("Process 1: Press enter to exit...\n");
    getchar();

    // 分离共享内存
    shmdt(shm);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    // 删除信号量
    semctl(semid, 0, IPC_RMID);

    return 0;
}
View Code

读进程demo:

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

#define SHM_SIZE 1024

typedef struct {
    char data[SHM_SIZE];
} SharedMemory;

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

int main(int argc, char *argv[]) {
    key_t key = ftok("shmfile", 65); // 生成一个唯一的 key
    int shmid = shmget(key, sizeof(SharedMemory), 0666);

    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }

    SharedMemory *shm = (SharedMemory *)shmat(shmid, NULL, 0);

    if (shm == (SharedMemory *)-1) {
        perror("shmat failed");
        exit(1);
    }

    // 获取信号量
    int semid = semget(key, 1, 0666);

    struct sembuf sb = {0, -1, 0}; // P操作:减少信号量
    semop(semid, &sb, 1);

    // 读取数据
    printf("Process %s: Data read from shared memory: %s\n", argv[0], shm->data);

    sb.sem_op = 1; // V操作:增加信号量
    semop(semid, &sb, 1);

    // 分离共享内存
    shmdt(shm);

    return 0;
}
View Code

 

3.读写锁
写进程demo:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <pthread.h>

#define SHM_SIZE 1024

typedef struct {
    char data[SHM_SIZE];
    pthread_rwlock_t rwlock;
} SharedMemory;

int main() {
    key_t key = ftok("shmfile", 65); // 生成一个唯一的 key
    int shmid = shmget(key, sizeof(SharedMemory), 0666 | IPC_CREAT);

    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }

    SharedMemory *shm = (SharedMemory *)shmat(shmid, NULL, 0);

    if (shm == (SharedMemory *)-1) {
        perror("shmat failed");
        exit(1);
    }

    // 初始化读写锁
    pthread_rwlockattr_t attr;
    pthread_rwlockattr_init(&attr);
    pthread_rwlockattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
    pthread_rwlock_init(&shm->rwlock, &attr);
    pthread_rwlockattr_destroy(&attr);

    // 写入数据
    pthread_rwlock_wrlock(&shm->rwlock);
    strcpy(shm->data, "Hello from Process 1!");
    printf("Process 1: Data written to shared memory\n");
    pthread_rwlock_unlock(&shm->rwlock);

    // 等待进程2和进程3读取数据
    printf("Process 1: Press enter to exit...\n");
    getchar();

    // 分离共享内存
    shmdt(shm);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, NULL);

    return 0;
}
View Code

读进程demo:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <pthread.h>

#define SHM_SIZE 1024

typedef struct {
    char data[SHM_SIZE];
    pthread_rwlock_t rwlock;
} SharedMemory;

int main(int argc, char *argv[]) {
    key_t key = ftok("shmfile", 65); // 生成一个唯一的 key
    int shmid = shmget(key, sizeof(SharedMemory), 0666);

    if (shmid == -1) {
        perror("shmget failed");
        exit(1);
    }

    SharedMemory *shm = (SharedMemory *)shmat(shmid, NULL, 0);

    if (shm == (SharedMemory *)-1) {
        perror("shmat failed");
        exit(1);
    }

    // 读取数据
    pthread_rwlock_rdlock(&shm->rwlock);
    printf("Process %s: Data read from shared memory: %s\n", argv[0], shm->data);
    pthread_rwlock_unlock(&shm->rwlock);

    // 分离共享内存
    shmdt(shm);

    return 0;
}
View Code

 

4.条件变量
 头文件:
#ifndef SHARED_MEMORY_H
#define SHARED_MEMORY_H

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

#define SHM_SIZE sizeof(SharedData)  // 共享内存大小
#define KEY_PATH "/tmp"             // 用于生成唯一键的路径
#define KEY_ID 1234                 // 用于生成唯一键的ID

// 共享内存结构体
typedef struct {
    char data[1024]; // 存储数据的缓冲区
    int data_ready;  // 标志数据是否准备好(0: 未准备好, 1: 准备好)
    pthread_mutex_t mutex;    // 互斥锁
    pthread_cond_t cond;      // 条件变量
} SharedData;

#endif
View Code

写进程demo:

#include "shared_memory.h"

int main() {
    key_t key = ftok(KEY_PATH, KEY_ID);
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    // 创建共享内存
    int shm_id = shmget(key, SHM_SIZE, IPC_CREAT | 0666);
    if (shm_id == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    // 附加共享内存
    SharedData *shared_data = (SharedData *)shmat(shm_id, NULL, 0);
    if (shared_data == (void *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    // 初始化互斥锁和条件变量
    pthread_mutexattr_t mutex_attr;
    pthread_condattr_t cond_attr;
    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(&shared_data->mutex, &mutex_attr);
    pthread_cond_init(&shared_data->cond, &cond_attr);

    for (int i = 0; i < 5; i++) {
        pthread_mutex_lock(&shared_data->mutex);

        // 写入数据
        snprintf(shared_data->data, sizeof(shared_data->data), "Message %d from writer", i);
        shared_data->data_ready = 1;
        printf("Writer: Wrote data: %s\n", shared_data->data);

        pthread_cond_signal(&shared_data->cond); // 通知读取进程
        pthread_mutex_unlock(&shared_data->mutex);

        sleep(1); // 模拟处理延迟
    }

    // 清理
    pthread_mutex_destroy(&shared_data->mutex);
    pthread_cond_destroy(&shared_data->cond);

    // 分离共享内存
    shmdt(shared_data);

    // 删除共享内存
    shmctl(shm_id, IPC_RMID, NULL);

    return 0;
}
View Code

读进程demo:

#include "shared_memory.h"

int main() {
    key_t key = ftok(KEY_PATH, KEY_ID);
    if (key == -1) {
        perror("ftok");
        exit(EXIT_FAILURE);
    }

    // 获取共享内存
    int shm_id = shmget(key, SHM_SIZE, 0666);
    if (shm_id == -1) {
        perror("shmget");
        exit(EXIT_FAILURE);
    }

    // 附加共享内存
    SharedData *shared_data = (SharedData *)shmat(shm_id, NULL, 0);
    if (shared_data == (void *)-1) {
        perror("shmat");
        exit(EXIT_FAILURE);
    }

    for (int i = 0; i < 5; i++) {
        pthread_mutex_lock(&shared_data->mutex);

        // 等待条件变量
        while (!shared_data->data_ready) {
            pthread_cond_wait(&shared_data->cond, &shared_data->mutex);
        }

        printf("Reader %d: Read data: %s\n", getpid(), shared_data->data);
        shared_data->data_ready = 0;

        pthread_mutex_unlock(&shared_data->mutex);

        sleep(1); // 模拟处理延迟
    }

    // 分离共享内存
    shmdt(shared_data);

    return 0;
}
View Code

 

总结:

  • 互斥锁:适用于简单的同步需求,确保同一时间只有一个进程访问共享内存。
  • 信号量:适用于控制对共享资源的访问数量,允许多个进程同时访问。
  • 读写锁:适用于读多写少的场景,提高并发性能。
  • 条件变量:适用于复杂的同步逻辑,需要进程间协调的场景。

 

 

共享内存的瓶颈场景

  • 频繁映射/解除映射:
    • 例如动态创建/销毁共享内存段(频繁调用 shmget/shmat/shmdt),会导致内存碎片和性能下降。
  • 同步竞争激烈:
    • 如果多个进程频繁竞争同一把锁,会导致 CPU 空转(自旋锁)或上下文切换(互斥锁)。
  • 内存拷贝开销:
    • 如果需要将共享内存中的数据拷贝到进程私有内存(如网络传输),会引入额外开销。

 

 共享内存的优化

  • 减少映射/解除映射:
    • 在进程生命周期内保持共享内存映射,避免频繁调用 shmat/shmdt
  • 优化同步机制:
    • 使用无锁数据结构(如原子操作)或细粒度锁。
  • 避免内存拷贝:
    • 直接在共享内存中处理数据,减少拷贝。

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2020-06-17 18:17  一个不知道干嘛的小萌新  阅读(335)  评论(0)    收藏  举报