一文搞懂共享内存

一.共享内存

1.1共享内存特点

共享内存是在多个进程之间共享和访问相同的内存区域的一种机制。以下是共享内存的几个特点:

  1. 快速:共享内存是一种高效的进程间通信方式,因为它直接在进程之间共享内存区域,不需要复制数据,避免了数据的拷贝开销,提高了访问数据的速度。

  2. 高容量:共享内存可以承载大量的数据,适用于需要共享大量数据的场景。

  3. 实时性:由于共享内存是直接在进程之间共享数据,进程可以实时地读取和修改共享内存中的数据,实现了实时更新和同步。

  4. 灵活性:共享内存可以作为一个共享的缓冲区,使进程可以通过读写共享内存来进行进程间的协作。进程可以根据自己的需要自由地访问和操作共享内存中的数据。

  5. 并发性:多个进程可以同时对共享内存进行读写操作,因此共享内存适用于需要并发读写的场景。

  6. 同步与互斥:由于共享内存可以被多个进程同时访问,因此需要使用信号量等同步机制来保证数据的一致性和避免竞态条件的发生。

尽管共享内存提供了高效的数据共享方式,但它也需要仔细的设计和管理,避免数据竞争和脏数据的问题。在使用共享内存时,需要正确地实现同步机制,保证多个进程之间对共享内存的访问正确和安全。

1.2共享内编程步骤

编写共享内存程序涉及以下步骤:

  1. 创建共享内存:

    • 使用 shmget 函数创建一个共享内存段,指定共享内存的大小和权限等参数。该函数返回一个标识符,用于标识共享内存段。
  2. 连接共享内存:

    • 使用 shmat 函数将共享内存段连接到当前进程的地址空间。该函数返回共享内存段的首地址。
  3. 访问和操作共享内存:

    • 使用共享内存的地址,可以直接在程序中访问和操作共享内存中的数据。
  4. 分离共享内存:

    • 使用 shmdt 函数将共享内存段与当前进程分离。分离后,进程将不能再访问共享内存中的数据。
  5. 控制共享内存:

    • 使用 shmctl 函数对共享内存进行控制,如删除共享内存段。

下面是一个简单的共享内存程序示例,演示了基本的步骤:

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

int main() {
    
    
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
    
    
        perror("ftok");
        return -1;
    }
    
    int shmid = shmget(key, sizeof(int), IPC_CREAT | IPC_EXCL | 0644);
    if (shmid == -1) {
    
    
        perror("shmget");
        return -1;
    }
    
    int *shared_memory = (int *)shmat(shmid, NULL, 0);
    if (shared_memory == (void *)-1) {
    
    
        perror("shmat");
        return -1;
    }
    
    // 在共享内存中写入数据
    *shared_memory = 12345;
    
    // 从共享内存中读取数据
    printf("Shared memory value: %d\n", *shared_memory);
    
    // 分离共享内存
    if (shmdt(shared_memory) == -1) {
    
    
        perror("shmdt");
        return -1;
    }
    
    // 删除共享内存段
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
    
    
        perror("shmctl");
        return -1;
    }
    
    return 0;
}

在以上示例中,我们首先使用 ftok 函数生成一个唯一的键值,然后使用 shmget 函数创建了一个共享内存段。使用 shmat 函数将该共享内存段连接到当前进程的地址空间。在共享内存中进行读写操作后,使用 shmdt 函数将共享内存与进程分离,并使用 shmctl 函数删除共享内存段。

请注意,这只是一个简单的示例,实际的共享内存程序可能需要更复杂的同步机制来避免竞态条件和保证数据的一致性。

1.3共享内存函数接口

1.shmget

shmget 是一个系统调用(syscall),用于创建或获取一个共享内存段。

函数原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

参数说明:

  • key:共享内存段的键值,可以通过ftok函数生成。多个进程可以通过相同的键值来获取到同一个共享内存段。
  • size:共享内存段的大小,以字节为单位。
  • shmflg:标志位,指定创建共享内存的权限和操作方式,可以是以下值的按位或(|)组合:
    • IPC_CREAT:如果共享内存不存在,则创建一个新的共享内存段。
    • IPC_EXCL:与IPC_CREAT同时使用,用于确保只有当前调用的进程可以创建共享内存段。
    • 权限标志:用于指定共享内存的权限,类似于文件权限,例如0666表示读写权限。

返回值:

  • 若调用成功,返回共享内存段的标识符(非负整数),用于之后的操作。
  • 若出现错误,返回 -1,并设置errno全局变量以指示错误。

使用 shmget 函数后,可以使用其他函数如 shmatshmdtshmctl 来创建、连接和操作共享内存段。

以下是一个示例代码,展示了如何使用 shmget 创建一个共享内存段:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    
    
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
    
    
        perror("ftok");
        return -1;
    }
    
    size_t size = 1024;  // 共享内存段的大小为1024字节
    
    int shmflg = IPC_CREAT | 0666;  // 创建共享内存段,并设置读写权限
    
    int shmid = shmget(key, size, shmflg);
    if (shmid == -1) {
    
    
        perror("shmget");
        return -1;
    }
    
    printf("Shared memory ID: %d\n", shmid);
    
    return 0;
}

以上示例中,我们使用 ftok 函数生成了一个唯一的键值,然后使用 shmget 函数创建了一个大小为1024字节的共享内存段,并设置了读写权限。注意在创建共享内存段时,需要使用 IPC_CREAT 标志来表示创建操作。

2.shmat

shmat 是一个系统调用(syscall),用于将共享内存段连接到当前进程的地址空间,使得进程可以访问和操作共享内存中的数据。

函数原型如下:

#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数说明:

  • shmid:共享内存段的标识符,即创建或获取共享内存段时 shmget 返回的值。
  • shmaddr:指定连接的地址,一般为 NULL。如果设置为 NULL,系统会选择一个可用的地址,如果设置为一个非空指针,表示使用指定的地址。
  • shmflg:标志位,用于指定连接共享内存的选项,一般使用 0

返回值:

  • 若调用成功,返回值为共享内存段的首地址(void * 类型)。
  • 若出现错误,返回值为 (void *) -1,并设置 errno 全局变量以指示错误。

在使用 shmat 函数之前,必须先调用 shmget 函数创建或获取共享内存段。

以下是一个示例代码,展示了如何使用 shmat 连接共享内存段:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    
    
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
    
    
        perror("ftok");
        return -1;
    }
    
    int shmid = shmget(key, 1024, IPC_CREAT | 0666);  // 获取共享内存段的标识符
    if (shmid == -1) {
    
    
        perror("shmget");
        return -1;
    }
    
    void *shared_memory = shmat(shmid, NULL, 0);
    if (shared_memory == (void *)-1) {
    
    
        perror("shmat");
        return -1;
    }
    
    printf("Shared memory attached at address: %p\n", shared_memory);
    
    // 访问共享内存
    
    // 分离共享内存
    if (shmdt(shared_memory) == -1) {
    
    
        perror("shmdt");
        return -1;
    }
    
    // 删除共享内存段
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
    
    
        perror("shmctl");
        return -1;
    }
    
    return 0;
}

在以上示例中,我们首先使用 ftok 函数生成了一个唯一的键值,然后使用 shmget 函数创建了一个大小为1024字节的共享内存段,并设置了读写权限。然后,我们使用 shmat 函数将共享内存段连接到当前进程的地址空间。连接成功后,可以使用 shared_memory 变量访问和操作共享内存中的数据。当结束对共享内存的操作后,需要使用 shmdt 函数将共享内存从进程中分离。

3.shmdt

shmdt 是一个系统调用(syscall),用于将共享内存段从当前进程的地址空间中分离。

函数原型如下:

#include <sys/types.h>
#include <sys/shm.h>

int shmdt(const void *shmaddr);

参数说明:

  • shmaddr:要分离的共享内存段的首地址。

返回值:

  • 若调用成功,返回值为 0
  • 若出现错误,返回值为 -1,并设置 errno 全局变量以指示错误。

在调用 shmdt 函数之后,进程将不再能够访问和操作共享内存段中的数据。

以下是一个示例代码,展示了如何使用 shmdt 分离共享内存段:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    
    
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
    
    
        perror("ftok");
        return -1;
    }
    
    int shmid = shmget(key, 1024, IPC_CREAT | 0666);  // 获取共享内存段的标识符
    if (shmid == -1) {
    
    
        perror("shmget");
        return -1;
    }
    
    void *shared_memory = shmat(shmid, NULL, 0);  // 将共享内存段连接到当前进程
    if (shared_memory == (void *)-1) {
    
    
        perror("shmat");
        return -1;
    }
    
    printf("Shared memory attached at address: %p\n", shared_memory);
    
    // 操作共享内存
    
    if (shmdt(shared_memory) == -1) {
    
      // 分离共享内存段
        perror("shmdt");
        return -1;
    }
    
    // 其他操作
    
    return 0;
}

在以上示例中,我们使用 ftok 函数生成了一个唯一的键值,然后使用 shmget 函数创建了一个大小为1024字节的共享内存段,并设置了读写权限。然后,我们使用 shmat 函数将共享内存段连接到当前进程的地址空间,并将连接得到的共享内存首地址存储在 shared_memory 变量中。在进行共享内存操作后,我们使用 shmdt 函数将共享内存段从当前进程中分离。

4.shmctl

shmctl 是一个系统调用(syscall),用于对共享内存段进行控制操作,如获取共享内存的状态信息、修改共享内存的权限等。

函数原型如下:

#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数说明:

  • shmid:共享内存段的标识符,即创建或获取共享内存段时 shmget 返回的值。
  • cmd:控制命令,用于指定要执行的操作。常见的命令值包括:
    • IPC_STAT:获取共享内存的状态信息,并存储在 buf 参数指向的 struct shmid_ds 结构体中。
    • IPC_SET:设置共享内存的状态信息,需要将新的状态信息存储在 buf 参数指向的 struct shmid_ds 结构体中。
    • IPC_RMID:删除共享内存段。
  • buf:对于某些命令,需要传递一个指向 struct shmid_ds 结构体的指针,用于传递或接收共享内存的状态信息。

返回值:

  • 若调用成功,返回值取决于具体的操作,一般为 0
  • 若出现错误,返回值为 -1,并设置 errno 全局变量以指示错误。

以下是一个示例代码,展示了如何使用 shmctl 控制共享内存段:

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    
    
    key_t key = ftok("/path/to/file", 'A');
    if (key == -1) {
    
    
        perror("ftok");
        return -1;
    }
    
    int shmid = shmget(key, 1024, IPC_CREAT | 0666);  // 获取共享内存段的标识符
    if (shmid == -1) {
    
    
        perror("shmget");
        return -1;
    }
    
    struct shmid_ds shm_info;
    
    // 获取共享内存段的状态信息
    if (shmctl(shmid, IPC_STAT, &shm_info) == -1) {
    
    
        perror("shmctl");
        return -1;
    }
    
    printf("Shared memory mode: %o\n", shm_info.shm_perm.mode);
    printf("Shared memory size: %lu bytes\n", shm_info.shm_segsz);
    
    // 修改共享内存段的权限
    shm_info.shm_perm.mode = 0666;
    if (shmctl(shmid, IPC_SET, &shm_info) == -1) {
    
    
        perror("shmctl");
        return -1;
    }
    
    // 删除共享内存段
    if (shmctl(shmid, IPC_RMID, NULL) == -1) {
    
    
        perror("shmctl");
        return -1;
    }
    
    return 0;
}

在以上示例中,我们使用 ftok 函数生成了一个唯一的键值,然后使用 shmget 函数创建了一个大小为1024字节的共享内存段,并设置了读写权限。接下来,我们使用 shmctl 函数来获取共享内存段的状态信息,并打印出其中的一些属性,比如权限和大小。然后,我们修改了共享内存段的权限,将其设置为0666。最后,我们使用 shmctl 函数将共享内存段删除。

1.4编程实战

send.c

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

union semun {
    
    
    int val;
    struct semid_ds *buf;
};

void mysemop(int semid, int num, int op)
{
    
    
    struct sembuf buf;
    buf.sem_flg = 0;   //堵塞
    buf.sem_num = num; //信号灯的编号
    buf.sem_op = op;   //pv操作
    semop(semid, &buf, 1);
}

void mysemctl(int semid, int num, int val)
{
    
    
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}

int main(int argc, char const *argv[])
{
    
    
    //1.产生一个唯一的key值
    key_t key = ftok("/home/hq/demo/进程/a.txt", 'B');
    if (key < 0)
    {
    
    
        perror("ftok is err");
        exit(0);
    }

    //2.创建共享内存
    int shmid = shmget(key, 1024, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
    
    
        if (errno == 17)
        {
    
    
            shmid = shmget(key, 1024, 0666);
        }
        else
        {
    
    
            perror("shmget is err");
            exit(0);
        }
    }

    //3.共享内存映射到私有地址,0可读可写
    char *add = shmat(shmid, NULL, 0);
    if (add == (char *)(-1))
    {
    
    
        perror("shmat is err");
        exit(0);
    }

    //4.创建信号灯集
    int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid <= 0)
    {
    
    
        if (errno == 17)
            semid=semget(key, 1, 0666);
        else
        {
    
    
            perror("semget is err");
            exit(0);
        }
    }
    else
    {
    
    
        //5.初始化信号灯集
        mysemctl(semid, 0, 0);
    }

    //6.循环写入共享内存
    while (1)
    {
    
    
        printf("%d\n", semctl(semid, 0, GETVAL));
        fgets(add, 1024, stdin);
        mysemop(semid, 0, 1);
         
        if (strcmp(add, "quit\n") == 0)
            break;
    }

    //7.取消映射
    shmdt(add);
    return 0;
}

recv.c

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

union semun {
    
    
    int val;
    struct semid_ds *buf;
};

void mysemop(int semid, int num, int op)
{
    
    
    struct sembuf buf;
    buf.sem_flg = 0;   //堵塞
    buf.sem_num = num; //信号灯的编号
    buf.sem_op = op;   //pv操作
    semop(semid, &buf, 1);
}

void mysemctl(int semid, int num, int val)
{
    
    
    union semun sem;
    sem.val = val;
    semctl(semid, num, SETVAL, sem);
}

int main(int argc, char const *argv[])
{
    
    
    //1.产生一个唯一的key值
    key_t key = ftok("/home/hq/demo/进程/a.txt", 'B');
    if (key < 0)
    {
    
    
        perror("ftok is err");
        exit(0);
    }

    //2.创建共享内存
    int shmid = shmget(key, 1024, IPC_CREAT | IPC_EXCL | 0666);
    if (shmid < 0)
    {
    
    
        if (errno == 17)
        {
    
    
            shmid = shmget(key, 1024, 0666);
        }
        else
        {
    
    
            perror("shmget is err");
            exit(0);
        }
    }

    //3.共享内存映射到私有地址,0可读可写
    char *add = shmat(shmid, NULL, 0);
    if (add == (char *)(-1))
    {
    
    
        perror("shmat is err");
        exit(0);
    }

    //4.创建信号灯集
    int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | 0666);
    if (semid <= 0)
    {
    
    
        if (errno == 17)
            semid=semget(key, 1, 0666);
        else
        {
    
    
            perror("semget is err");
            exit(0);
        }
    }
    else
    {
    
    
        //5.初始化信号灯集
        mysemctl(semid, 0, 0);
    }

    //6.循环读出
    while (1)
    {
    
    
        mysemop(semid, 0, -1);
        printf("%d\n", semctl(semid, 0, GETVAL));
        if (strcmp("quit\n", add) == 0)
            break;
        printf("%s", add);
    }

    //7.取消映射
    shmdt(add);

    //8.删除共享内存和信号灯集
    shmctl(shmid, IPC_RMID, NULL);
    semctl(semid, 0, IPC_RMID);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/m0_73731708/article/details/133151759