Linux系统编程-进程间通信(IPC)常用方式详解

        进程间通信(IPC,Inter-Process Communication)是指在操作系统中,不同进程之间进行数据交换和信息传递的机制。这种通信允许正在运行的多个进程能够相互协作、共享数据或者进行同步操作,以实现更复杂的任务和功能。Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

1.管道-pipe

管道(Pipe)是一种进程间通信(IPC)机制,允许具有亲缘关系的进程之间进行单向通信。它是Unix和类Unix操作系统中常见的IPC方式之一,通常用于在父子进程或者兄弟进程之间传递数据。

概念:

管道是一种特殊的文件描述符,它连接了两个进程,其中一个进程的输出作为另一个进程的输入。管道是单向的,即数据只能在一个方向上流动。通常有两种类型的管道:

  • 匿名管道:存在于具有亲缘关系的进程之间,通常用于父子进程之间或者通过fork()系统调用创建的进程间通信。

  • 命名管道(FIFO):是一种特殊的文件系统对象,允许不相关的进程通过文件路径来进行通信。

原理:

管道基于操作系统提供的内核缓冲区进行数据传输。在Unix系统中,管道可以通过pipe()系统调用创建。pipe()系统调用会创建一个管道,并返回两个文件描述符:

  • 第一个文件描述符用于管道的写入端(写入数据的进程将数据发送到这一端)。
  • 第二个文件描述符用于管道的读取端(读取数据的进程从这一端接收数据)。

管道的工作原理可以简单描述为:

  1. 进程A调用pipe()系统调用创建管道,得到两个文件描述符fd[0](读取端)和fd[1](写入端)。
  2. 进程A通过fork()创建进程B,此时进程A和进程B都能够访问这两个文件描述符。
  3. 进程A可以通过fd[1]写入数据到管道,而进程B可以通过fd[0]从管道中读取数据。

数据在管道中是先进先出(FIFO)的顺序进行传递,当进程向管道写入数据时,数据会被存储在内核缓冲区中,等待被读取进程读取。

局限性:

  • 单向通信:管道只支持单向数据传输,即数据只能从一个方向流动。

  • 具有亲缘关系的进程:匿名管道只能用于具有亲缘关系的进程之间,通常是父子进程或者通过fork()创建的进程。

  • 容量有限:管道的容量是有限的,通常在几千字节到几兆字节之间,这取决于操作系统的具体实现。

  • 无法进行双向通信:如果需要双向通信,通常需要创建两个管道,一个用于从父进程到子进程,另一个用于从子进程到父进程。

  • 阻塞问题:管道的读取端和写入端在一些情况下可能会发生阻塞,特别是当管道为空或者已满时。

1.1pipe()

管道(Pipe)通过pipe()系统调用来创建。pipe()函数可以在进程间创建一个管道,这个管道具有两个文件描述符:一个用于读取端,一个用于写入端。这种方式可以实现具有亲缘关系的进程之间的单向通信。

函数原型:
#include <unistd.h>

int pipe(int pipefd[2]);
参数:pipefd是一个整型数组,长度为2,用于存储管道的两个文件描述符。
pipefd[0]:用于读取管道数据的文件描述符(读取端)。
pipefd[1]:用于写入管道数据的文件描述符(写入端)。
返回值:成功时返回0,失败时返回-1,并设置errno以指示错误类型。

示例代码:父子进程使用管道pipe通信

一个进程在由pipe()创建管道后,一般再fork一个子进程,然后通过管道实现父子进程间的通信(因此也不难推出,只要两个进程中存在血缘关系,这里的血缘关系指的是具有共同的祖先,都可以采用管道方式来进行通信)。父子进程间具有相同的文件描述符,且指向同一个管道pipe,其他没有关系的进程不能获得pipe()产生的两个文件描述符,也就不能利用同一个管道进行通信。

步骤:

第一步:父进程创建管道:

第二步:父进程fork出子进程:

第三步:父进程关闭fd[0],子进程关闭fd[1]

创建步骤总结:

  • 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]和fd[1],分别指向管道的读端和写端。
  • 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管。
  • 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出,这样就实现了父子进程间通信。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>

#define BUFFER_SIZE 25

int main() {
    int pipefd[2];
    pid_t pid;
    char parent_message[BUFFER_SIZE] = "Hello, child!";
    char child_message[BUFFER_SIZE];

    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 创建子进程
    pid = fork();

    if (pid < 0) {  // 错误处理
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid > 0) {  // 父进程
        close(pipefd[0]);  // 关闭父进程的读取端

        // 向管道写入数据
        write(pipefd[1], parent_message, strlen(parent_message) + 1);
        printf("Parent wrote: %s\n", parent_message);

        close(pipefd[1]);  // 关闭写入端

        // 等待子进程结束
        wait(NULL);
    } else {  // 子进程
        close(pipefd[1]);  // 关闭子进程的写入端

        // 从管道读取数据
        read(pipefd[0], child_message, BUFFER_SIZE);
        printf("Child read: %s\n", child_message);

        close(pipefd[0]);  // 关闭读取端
    }

    return 0;
}

示例代码:使用管道(pipe)实现两个进程间的全双工通信(即双向通信)

步骤:

  1. 创建两个管道:一个用于进程A到进程B的数据传输,另一个用于进程B到进程A的数据传输。
  2. 创建子进程:使用 fork() 创建子进程。
  3. 关闭不需要的管道端:根据通信方向关闭不需要的管道端。
  4. 进行读写操作:进程A和进程B分别通过各自的管道端进行读写操作。
  5. 关闭管道:通信结束后关闭管道。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

#define BUFFER_SIZE 1024

int main() {
    int pipeAtoB[2]; // 管道A到B
    int pipeBtoA[2]; // 管道B到A
    pid_t pid;
    char buffer[BUFFER_SIZE];

    // 创建管道
    if (pipe(pipeAtoB) == -1 || pipe(pipeBtoA) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

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

    if (pid == 0) { // 子进程B
        close(pipeAtoB[1]); // 关闭子进程B中A到B的写端
        close(pipeBtoA[0]); // 关闭子进程B中B到A的读端

        // 从父进程A读取数据
        read(pipeAtoB[0], buffer, BUFFER_SIZE);
        printf("Child process received: %s\n", buffer);

        // 向父进程A发送数据
        char *message = "Hello from Child Process!";
        write(pipeBtoA[1], message, strlen(message) + 1);

        close(pipeAtoB[0]); // 关闭子进程B中A到B的读端
        close(pipeBtoA[1]); // 关闭子进程B中B到A的写端
        exit(EXIT_SUCCESS);
    } else { // 父进程A
        close(pipeAtoB[0]); // 关闭父进程A中A到B的读端
        close(pipeBtoA[1]); // 关闭父进程A中B到A的写端

        // 向子进程B发送数据
        char *message = "Hello from Parent Process!";
        write(pipeAtoB[1], message, strlen(message) + 1);

        // 从子进程B读取数据
        read(pipeBtoA[0], buffer, BUFFER_SIZE);
        printf("Parent process received: %s\n", buffer);

        close(pipeAtoB[1]); // 关闭父进程A中A到B的写端
        close(pipeBtoA[0]); // 关闭父进程A中B到A的读端

        // 等待子进程结束
        wait(NULL);
    }

    return 0;
}

注意事项:

  • 管道关闭:每个进程关闭自己不需要的管道端,以防止数据流动错误和资源浪费。
  • 同步问题:此示例程序使用简单的读写顺序进行同步。如果需要更复杂的同步机制,可能需要额外的同步方法(如信号量、条件变量等)。
  • 缓冲区大小:确保缓冲区大小足够大以容纳传递的数据。

2.命名管道-FIFO

        FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间通信。但通过FIFO,不相关的进程也能交换数据。

        FIFO是Linux基础文件类型中的一种(文件类型为p,可通过ls -l查看文件类型)。但FIFO文件在磁盘上没有数据块,文件大小为0,仅仅用来标识内核中一条通道。进程可以打开这个文件进行read/write,实际上是在读写内核缓冲区,这样就实现了进程间通信。

2.1mkfifo()

函数原型:
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
参数:
pathname:指定要创建的命名管道的路径名。
mode:指定创建的文件权限模式,类似于chmod()函数中的权限设置。通常使用S_IRUSR、S_IWUSR等宏定义来设置。
返回值:
成功:返回0。
失败:返回-1,并设置适当的错误码。

示例代码:使用命名管道fifo完成两个进程的通信

写入数据

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <string.h>

#define FIFO_PATH "./myfifo"
#define BUFFER_SIZE 1024

int main() {
    int fd;
    char buffer[BUFFER_SIZE];
    const char *message = "Hello from Process 1!";

    // 创建命名管道
    if (mkfifo(FIFO_PATH, 0666) == -1) {
        perror("mkfifo");
        exit(EXIT_FAILURE);
    }

    printf("Named pipe created at %s\n", FIFO_PATH);

    // 打开命名管道以写入数据
    fd = open(FIFO_PATH, O_WRONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 写入数据到命名管道
    strncpy(buffer, message, BUFFER_SIZE);
    if (write(fd, buffer, strlen(buffer) + 1) == -1) {
        perror("write");
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Process 1 sent message: %s\n", message);

    // 关闭管道
    close(fd);

    // 删除命名管道
    if (unlink(FIFO_PATH) == -1) {
        perror("unlink");
        exit(EXIT_FAILURE);
    }

    printf("Named pipe deleted.\n");

    return 0;
}

读取数据:

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

#define FIFO_PATH "./myfifo"
#define BUFFER_SIZE 1024

int main() {
    int fd;
    char buffer[BUFFER_SIZE];

    // 打开命名管道以读取数据
    fd = open(FIFO_PATH, O_RDONLY);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 从命名管道读取数据
    if (read(fd, buffer, BUFFER_SIZE) == -1) {
        perror("read");
        close(fd);
        exit(EXIT_FAILURE);
    }

    printf("Process 2 received message: %s\n", buffer);

    // 关闭管道
    close(fd);

    return 0;
}

通常情况下,先启动写入数据的进程,再启动读取数据的进程。如果读取进程先启动,可能会导致它在尝试打开命名管道时出现阻塞,直到写入进程也启动并打开管道为止。

  • FIFO的阻塞特性

    • FIFO 是一种特殊的文件系统对象,它具有阻塞的特性。当一个进程尝试打开一个写端的 FIFO 进行写入操作时,如果没有进程打开对应的读端,写入进程可能会被阻塞,直到有进程打开了读端。
  • 进程启动顺序的影响

    • 如果先启动了写入数据的进程,并且该进程尝试打开一个 FIFO 进行写入操作,但此时没有其他进程打开同一个 FIFO 的读端,写入进程会阻塞等待。
    • 只有当读取数据的进程启动并打开了相同的 FIFO 读端时,写入进程才能继续执行,向 FIFO 写入数据。

示例代码:使用命名管道(FIFO)实现两个非血缘关系的进程间全双工通信

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

#define FIFO_AtoB "fifo_a_to_b"
#define FIFO_BtoA "fifo_b_to_a"
#define BUFFER_SIZE 1024

int main() {
    int fd_write, fd_read;
    char buffer[BUFFER_SIZE];
    int count = 0;

    // 创建命名管道A到B
    if (mkfifo(FIFO_AtoB, 0666) == -1) {
        perror("mkfifo A to B");
        exit(EXIT_FAILURE);
    }

    // 创建命名管道B到A
    if (mkfifo(FIFO_BtoA, 0666) == -1) {
        perror("mkfifo B to A");
        exit(EXIT_FAILURE);
    }

    printf("Named pipes created successfully.\n");

    // 打开命名管道A到B以写入数据
    fd_write = open(FIFO_AtoB, O_WRONLY);
    if (fd_write == -1) {
        perror("open write");
        exit(EXIT_FAILURE);
    }

    // 打开命名管道B到A以读取数据
    fd_read = open(FIFO_BtoA, O_RDONLY);
    if (fd_read == -1) {
        perror("open read");
        exit(EXIT_FAILURE);
    }

    while (1) {
        // 读取用户输入
        printf("Process A, enter message: ");
        fgets(buffer, BUFFER_SIZE, stdin);

        // 向进程B发送数据
        write(fd_write, buffer, strlen(buffer) + 1);
        printf("Process A sent: %s\n", buffer);

        // 检查终止条件
        if (strcmp(buffer, "exit\n") == 0) {
            break;
        }

        // 从进程B读取数据
        read(fd_read, buffer, BUFFER_SIZE);
        printf("Process A received: %s\n", buffer);

        // 检查终止条件
        if (strcmp(buffer, "exit\n") == 0) {
            break;
        }

        count++;
    }

    // 关闭命名管道
    close(fd_write);
    close(fd_read);

    // 删除命名管道
    unlink(FIFO_AtoB);
    unlink(FIFO_BtoA);

    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

#define FIFO_AtoB "fifo_a_to_b"
#define FIFO_BtoA "fifo_b_to_a"
#define BUFFER_SIZE 1024

int main() {
    int fd_write, fd_read;
    char buffer[BUFFER_SIZE];
    int count = 0;

    // 打开命名管道A到B以读取数据
    while ((fd_read = open(FIFO_AtoB, O_RDONLY)) == -1) {
        perror("open read");
        sleep(1); // 等待管道创建
    }

    // 打开命名管道B到A以写入数据
    while ((fd_write = open(FIFO_BtoA, O_WRONLY)) == -1) {
        perror("open write");
        sleep(1); // 等待管道创建
    }

    while (1) {
        // 从进程A读取数据
        read(fd_read, buffer, BUFFER_SIZE);
        printf("Process B received: %s\n", buffer);

        // 检查终止条件
        if (strcmp(buffer, "exit\n") == 0) {
            break;
        }

        // 读取用户输入
        printf("Process B, enter message: ");
        fgets(buffer, BUFFER_SIZE, stdin);

        // 向进程A发送数据
        write(fd_write, buffer, strlen(buffer) + 1);
        printf("Process B sent: %s\n", buffer);

        // 检查终止条件
        if (strcmp(buffer, "exit\n") == 0) {
            break;
        }

        count++;
    }

    // 关闭命名管道
    close(fd_read);
    close(fd_write);

    return 0;
}

3.内存映射区-mmap

内存映射IO详解

存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。从缓冲区中取数据,就相当于读文件中的相应字节;将数据写入缓冲区,则会将数据写入文件。这样,就可在不使用read和write函数的情况下,使用地址(指针)完成I/O操作。使用存储映射这种方法,首先应通知内核,将一个指定文件映射到存储区域中。这个映射工作可以通过mmap函数来实现。

示例代码:使用mmap完成父子进程间通信

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

#define FILE_PATH "mapped_file.txt"
#define FILE_SIZE 4096

int main() {
    int fd;
    char *mapped_memory;

    // 创建或打开文件,并设置文件大小
    fd = open(FILE_PATH, O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    if (ftruncate(fd, FILE_SIZE) == -1) {
        perror("ftruncate");
        exit(EXIT_FAILURE);
    }

    // 创建存储映射区
    mapped_memory = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped_memory == MAP_FAILED) {
        perror("mmap");
        exit(EXIT_FAILURE);
    }

    // 关闭文件描述符
    close(fd);

    // 创建子进程
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {
        // 子进程
        printf("Child process reading from memory-mapped file: %s\n", mapped_memory);
        // 修改映射区中的数据
        strcpy(mapped_memory, "Hello from Child Process!");
        exit(EXIT_SUCCESS);
    } else {
        // 父进程
        // 写入数据到映射区
        strcpy(mapped_memory, "Hello from Parent Process!");

        // 等待子进程完成
        wait(NULL);

        // 再次读取子进程修改后的数据
        printf("Parent process reading from memory-mapped file: %s\n", mapped_memory);

        // 释放存储映射区
        if (munmap(mapped_memory, FILE_SIZE) == -1) {
            perror("munmap");
            exit(EXIT_FAILURE);
        }
    }

    return 0;
}

示例代码:使用mmap完成没有血缘关系的进程间通

写进程

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

#define FILE_PATH "shared_file.txt"
#define FILE_SIZE 4096

int main() {
    int fd;
    char *mapped_memory;

    // 打开文件,创建并设置大小
    fd = open(FILE_PATH, O_RDWR | O_CREAT, 0666);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }
    if (ftruncate(fd, FILE_SIZE) == -1) {
        perror("ftruncate");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 创建存储映射区
    mapped_memory = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped_memory == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 写入数据到映射区
    strcpy(mapped_memory, "Hello from Process 1!");

    // 保持程序运行一段时间,以便进程2读取
    sleep(30);

    // 解除映射
    if (munmap(mapped_memory, FILE_SIZE) == -1) {
        perror("munmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件
    close(fd);

    return 0;
}

读进程:

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

#define FILE_PATH "shared_file.txt"
#define FILE_SIZE 4096

int main() {
    int fd;
    char *mapped_memory;

    // 打开文件
    fd = open(FILE_PATH, O_RDWR);
    if (fd == -1) {
        perror("open");
        exit(EXIT_FAILURE);
    }

    // 创建存储映射区
    mapped_memory = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (mapped_memory == MAP_FAILED) {
        perror("mmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 读取映射区中的数据
    printf("Process 2 reads: %s\n", mapped_memory);

    // 修改映射区中的数据
    strcpy(mapped_memory, "Hello from Process 2!");

    // 保持程序运行一段时间,以便进程1看到修改
    sleep(30);

    // 解除映射
    if (munmap(mapped_memory, FILE_SIZE) == -1) {
        perror("munmap");
        close(fd);
        exit(EXIT_FAILURE);
    }

    // 关闭文件
    close(fd);

    return 0;
}

4.共享内存-shmget

共享内存是一种 IPC(进程间通信)机制,允许多个进程通过映射同一块物理内存来实现数据的共享。这种通信方式相比于管道、消息队列等方式,具有更高的性能和效率,因为数据直接在内存中传递,避免了数据的复制和系统调用带来的开销。shmget 是 "shared memory get" 的缩写,是Linux系统提供的一个系统调用函数。它用于创建新的共享内存段或获取已存在的共享内存段的标识符(ID)。

Linux中共享内存的原理涉及以下几个关键点:

  1. 物理内存映射

    • 共享内存通过将同一块物理内存映射到多个进程的虚拟地址空间来实现进程间的数据共享。
    • 多个进程可以访问同一块共享内存区域,通过读写内存中的数据进行通信和同步。
  2. 内核空间和用户空间

    • 共享内存由内核管理,但是它在各个进程的用户空间地址空间中均有映射,因此可以直接进行内存操作,避免了用户态到内核态的切换。
  3. 键值和标识符

    • 要使用共享内存,进程需要提供一个键值(通常使用 ftok 函数生成),然后通过 shmget 系统调用获取共享内存的标识符(ID)。
    • 多个进程可以使用相同的键值来获取同一个共享内存区域的标识符,从而进行共享。
  4. 操作系统的支持

    • Linux操作系统提供了一系列的系统调用来支持共享内存的创建、连接、读写和销毁,如 shmgetshmatshmdtshmctl 等。

4.1相关操作函数

4.1.1shmget()

功能:创建一个新的共享内存段或获取已存在的共享内存段的标识符。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
参数:
key:共享内存的键值,通常使用 ftok 函数生成。不同进程使用相同的 key 可以访问同一块共享内存。该参数的值一定要大于0
size:共享内存的大小(字节数)。
shmflg:权限标志和操作标志,可以通过按位或运算来指定多个选项,如创建标志、读写权限等。
返回值:
成功:返回共享内存的标识符(非负整数),用于后续操作。
失败:返回 -1,并设置 errno 来指示错误原因。

重要注意事项

  • 如果指定的 key 对应的共享内存不存在,则根据 shmflg 创建新的共享内存。
  • 多个进程可以使用相同的 key 获取同一个共享内存区域。
4.1.2ftok()

功能:ftok 函数用于根据给定的文件路径和项目标识符 proj_id 生成一个唯一的键值,以便创建或访问共享内存、消息队列等 IPC 对象。

#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
参数
pathname:指定一个已存在的文件路径名。
proj_id:项目标识符,是一个用户自定义的整数。
返回值
成功:返回一个唯一的键值(非负整数),用于后续的 IPC 对象的创建或访问。
失败:返回 -1,并设置 errno 来指示错误原因。

注意事项:

  • ftok 函数依赖于文件的 inode 号和 proj_id,因此同一个 pathnameproj_id 组合在不同系统或文件系统上可能会生成不同的键值。
  • 如果指定的文件路径不存在或者没有读取权限,ftok 将会失败并返回 -1
  • 返回的键值应用于 shmgetmsgget 等函数中,用于创建或访问对应的 IPC 对象。
4.1.3shmat()

功能:将共享内存段连接到当前进程的地址空间,返回指向共享内存的指针。

#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
shmid:共享内存的标识符,由 shmget 返回。
shmaddr:通常设为 NULL,让操作系统选择合适的地址映射。
shmflg:一般为 0,表示默认操作,无需特殊标志位。
返回值:
成功:返回指向共享内存区域的指针。
失败:返回 NULL,并设置 errno 来指示错误原因。

重要注意事项

  • shmat 将共享内存区域映射到当前进程的地址空间中,返回的指针可以用于直接访问共享数据。
  • 连接后需要记得使用 shmdt 分离共享内存,释放该进程的地址空间。
4.1.4shmdt()

功能:将共享内存段从当前进程的地址空间中分离,使得该进程不能再访问共享内存。

#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
参数:
shmaddr:指向共享内存区域的指针,通常为 shmat 返回的指针。
返回值:
成功:返回 0。
失败:返回 -1,并设置 errno 来指示错误原因。

重要注意事项

  • 使用 shmdt 将共享内存段分离后,该进程将不能再访问共享内存区域。
  • 分离操作不会销毁共享内存区域,只是断开了与当前进程的连接。
4.1.5shmctl()

功能:控制共享内存段的状态和属性,如删除共享内存段、获取状态信息等。

#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:共享内存的标识符,由 shmget 返回。
cmd:命令操作,指定要执行的操作类型,如删除、获取状态等。
buf:struct shmid_ds 结构体指针,用于存储或获取共享内存的状态信息。
返回值:
成功:根据 cmd 不同可能返回不同的值,一般成功操作返回 0。
失败:返回 -1,并设置 errno 来指示错误原因。

重要注意事项

  • 使用 shmctl 可以对共享内存进行多种控制操作,如删除共享内存、获取共享内存的状态信息等。
  • 在不需要共享内存时,应使用 shmctl 删除共享内存段,避免资源泄漏。

示例代码:

写进程:

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

#define SHM_SIZE 1024  // 共享内存的大小

int main() {
    key_t key = ftok("shmfile", 'R');  // 生成共享内存的键值
    int shmid;
    char *shmaddr;
    char buffer[SHM_SIZE];  // 缓冲区,用于存储用户输入的数据

    // 创建或获取共享内存段
    if ((shmid = shmget(key, SHM_SIZE, IPC_CREAT | 0666)) < 0) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存连接到当前进程的地址空间
    if ((shmaddr = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // 写入数据到共享内存
    printf("Enter data to write into shared memory: ");
    fgets(buffer, SHM_SIZE, stdin);
    strncpy(shmaddr, buffer, SHM_SIZE);

    // 断开共享内存连接
    shmdt(shmaddr);

    return 0;
}

读进程:

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

#define SHM_SIZE 1024  // 共享内存的大小

int main() {
    key_t key = ftok("shmfile", 'R');  // 生成共享内存的键值
    int shmid;
    char *shmaddr;

    // 获取共享内存段的标识符
    if ((shmid = shmget(key, SHM_SIZE, 0666)) < 0) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存连接到当前进程的地址空间
    if ((shmaddr = shmat(shmid, NULL, 0)) == (char *) -1) {
        perror("shmat");
        exit(1);
    }

    // 读取并打印共享内存中的数据
    printf("Data read from shared memory: %s", shmaddr);

    // 断开共享内存连接
    shmdt(shmaddr);

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

    return 0;
}

5. shm和mmap的区别


共享内存和内存映射区都可以实现进程间通信,下面来分析一下二者的区别:

实现进程间通信的方式

shm: 多个进程只需要一块共享内存就够了,共享内存不属于进程,需要和进程关联才能使用
内存映射区: 位于每个进程的虚拟地址空间中, 并且需要关联同一个磁盘文件才能实现进程间数据通信效率:

shm: 直接对内存操作,效率高
内存映射区: 需要内存和文件之间的数据同步,效率低
生命周期

内存映射区:进程退出, 内存映射区也就没有了
shm:进程退出对共享内存没有影响,调用相关函数/命令/ 关机才能删除共享内存


数据的完整性 -> 突发状态下数据能不能被保存下来(比如: 突然断电)

内存映射区:可以完整的保存数据, 内存映射区数据会同步到磁盘文件
shm:数据存储在物理内存中, 断电之后系统关闭, 内存数据也就丢失了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值