IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> 【Linux】进程间通信——管道与内存映射 -> 正文阅读

[系统运维]【Linux】进程间通信——管道与内存映射

文件描述符

? ? ? ?Linux系统中一切皆文件,系统中一切都被抽象成文件。对这些文件的读写都需要通过文件描述符来完成。这些文件描述符存储在文件描述符表中,内核为每一个进程维护了一个文件描述符表。文件描述符表能够存储的打开的文件数是有限制的,默认为1024个。
在这里插入图片描述
当一个进程被启动之后,内核PCB的文件描述符表中就已经分配了三个文件描述符:

  • STDIN_FILENO:标准输入,通过该文件描述符将数据输入到终端文件。
  • STDOUT_FILENO:标准输出,通过该文件描述符将数据通过终端输出。
  • STDERR_FILENO:标准错误,通过该文件描述符将错误信息通过终端输出。

管道

管道的本质是内核中的一块内存(内核缓冲区),管道数据通过循环队列来维护,管道对应的内核缓冲区大小为64KB。

  • 管道分为读端和写端,管道中的数据只能从写端流向读端,要实现双向同时通信需要设置两个管道。
  • 管道中的数据只能读一次,数据一旦被读出就从管道中被抛弃。
  • 对管道的读写操作默认是阻塞的。
  • 管道内部保证同步机制。
  • 数据以字节流的形式写入管道。
    在这里插入图片描述

读管道,需要根据写端的状态进行分析。

  1. 写端未关闭
    A. 管道无数据 -> 阻塞。如果管道中被写入了数据,阻塞解除,继续读。
    B. 管道有数据 -> 不阻塞。如果管道中的数据被读完了,再继续读,管道会阻塞。
  2. 写端关闭
    A.管道无数据 -> 阻塞解除,read函数返回0。
    B.管道有数据 -> read函数先将数据读出,数据读完之后read函数返回0,阻塞解除。

写管道,需要根据读端的状态进行分析。

  1. 读端未关闭
    A. 管道未写满 -> 不阻塞。一直写数据。
    B. 管道写满 -> 阻塞。当读端将管道数据读走后,阻塞解除,继续写。
  2. 读端关闭
    A.管道破裂,收到信号SIGPIPE,进程异常终止。

匿名管道

匿名管道只能实现有血缘关系的进程间通信。
在这里插入图片描述

// 子进程写数据,父进程读数据
#include <fcntl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main()
{
    // 1. 创建匿名管道
    int fd[2];
    int ret = pipe(fd);
    if (ret == -1)
    {
        perror("pipe");
        exit(0);
    }
    // 2. 创建子进程 -> 父进程中能够操作管道的文件描述符也被复制到子进程中
    pid_t pid = fork();

    // 子进程
    if (pid == 0)
    {
        // 关闭管道的读端
        close(fd[0]);
        // 文件描述符的重定向,默认输出到终端,设置输出到管道
        dup2(fd[1], STDOUT_FILENO);
        // 3. 在子进程中执行 execlp("ps", "ps", "aux", NULL);
        execlp("ps", "ps", "aux", NULL);
        perror("execlp");
    }

    // 父进程
    else if (pid > 0)
    {
        // 关闭管道的写端
        close(fd[1]);
        // 4.父进程读管道
        char buf[4096];

        while (1)
        {
            memset(buf, 0, sizeof(buf));
            int len = read(fd[0], buf, sizeof(buf));
            if (len == 0)
            {
                // 管道的写端关闭, 如果管道已无数据, 解除阻塞, read函数返回0
                break;
            }
            printf("%s\n\nlen = %d\n\n", buf, len);
        }
        close(fd[0]);

        // 回收子进程资源
        wait(NULL);
    }
    return 0;
}

有名管道

? ? ? ?有名管道既可以进行有血缘关系的进程间通信,也可以进行没有血缘关系的进程间通信。有名管道在磁盘上有实体文件,由于有名管道也是将数据存储到内核缓冲区中,所以其大小永远为0,打开这个磁盘上的管道文件就可以得到操作有名管道的文件描述符,通过文件描述符读写管道存储在内核中的数据。
? ? ? ?有名管道操作需要通过open函数得到读写管道的文件描述符,如果只是读端打开了或者只是写端打开了,进程会阻塞在这里不会向下执行,直到在另一个进程中将管道的对端打开,当前进程的阻塞才解除。
在这里插入图片描述

// 写管道的进程
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    // 1. 创建有名管道文件
    int ret = mkfifo("./testfifo", 0664);
    if (ret == -1)
    {
        perror("mkfifo");
        exit(0);
    }
    printf("管道文件创建成功!\n");

    // 2. 打开管道文件
    // 如果先打开写端, 读端还没有打开, open函数会阻塞, 当读端也打开之后, open解除阻塞
    int wfd = open("./testfifo", O_WRONLY);
    if (wfd == -1)
    {
        perror("open");
        exit(0);
    }
    printf("以只写的方式打开管道文件成功!\n");

    // 3. 循环写管道
    int i = 0;
    while (i < 10)
    {
        char buf[1024];
        sprintf(buf, "hello fifo, 我在写管道-%d\n", i);
        write(wfd, buf, strlen(buf));
        i++;
        sleep(1);
    }
    close(wfd);
    printf("管道的写端已经关闭\n");
    return 0;
}
// 读管道的进程
#include <fcntl.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    // 1. 打开管道文件
    // 如果先打开读端, 写端还没有打开, open函数会阻塞, 当写端被打开, 阻塞就解除了
    int rfd = open("./testfifo", O_RDONLY);
    if (rfd == -1)
    {
        perror("open");
        exit(0);
    }
    printf("以只读的方式打开文件成功!\n");

    // 2. 循环读管道
    while (1)
    {
        char buf[1024];
        memset(buf, 0, sizeof(buf));
        int len = read(rfd, buf, sizeof(buf));
        printf("读出的数据: %s\n", buf);
        if (len == 0)
        {
            // 管道的写端关闭, 如果管道已无数据, 解除阻塞, read函数返回0
            printf("管道的写端已经关闭!\n");
            break;
        }
    }
    close(rfd);
    return 0;
}

内存映射

? ? ? ?内存映射区对应的内存空间在进程的用户区(用于加载动态库的区域),进程间通信使用的内存映射区在每个进程内部都有一块。进程将各自的内存映射区和同一个磁盘文件进行映射,当进程A中的内存映射区数据被修改,数据会被自动同步到磁盘文件,同时和磁盘文件建立映射关系的其他进程内存映射区中的数据也会和磁盘文件进行数据的实时同步,保障了各个进程之间的数据共享。

  • 内存映射既可以实现有血缘关系的进程间通信也可以实现没有血缘关系的进程间通信。
  • 内存映射区的读写是非阻塞的。
  • 进程退出后内存映射区也就没有了。
    在这里插入图片描述
void *mmap(
    void *addr,    // 创建内存映射区的位置,一般委托内核分配,指定为NULL。
    size_t length, // 创建的内存映射区的大小,单位:字节。
    int prot,      // 内存映射区的操作权限。PROT_READ:读内存映射区;PROT_WRITE: 写内存映射区。
    int flags,     // MAP_SHARED: 多个进程共享数据;MAP_PRIVATE: 映射区是私有的,不能同步给其他进程。
    int fd,        // 文件描述符,内存映射区通过该文件描述符和磁盘文件建立关联。
    off_t offset   // 磁盘文件的偏移量。
);

? ? ? ?由于创建子进程会发生虚拟地址空间的拷贝,那么在父进程中创建的内存映射区也会被拷贝到子进程中,这样子进程可以直接使用内存映射区。对于没有血缘关系的进程间通信,需要在每个进程中分别创建内存映射区,但是这些进程的内存映射区必须要关联相同的磁盘文件,这样才能实现进程间的数据同步。

// 写数据进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int main()
{
    // 1. 打开一个磁盘文件
    int fd = open("./english.txt", O_RDWR);

    // 需要进行文件扩展!!!
    int code = ftruncate(fd, 4000);
    if (code == -1)
    {
        perror("ftruncate");
        exit(0);
    }

    // 2. 创建内存映射区
    void *ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(0);
    }

    const char *pt = "You don't get to choose how you're going to die, or when. You can only decide how you're going to live now.\n";
    memcpy(ptr, pt, strlen(pt) + 1);

    // 释放内存映射区
    munmap(ptr, 4000);
    close(fd);

    return 0;
}
// 读数据进程
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>
#include <fcntl.h>

int main()
{
    // 1. 打开一个磁盘文件
    int fd = open("./english.txt", O_RDWR);
    // 2. 创建内存映射区
    void *ptr = mmap(NULL, 4000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    if (ptr == MAP_FAILED)
    {
        perror("mmap");
        exit(0);
    }

    // 读内存映射区
    printf("从映射区读出的数据: %s\n", (char *)ptr);

    // 释放内存映射区
    munmap(ptr, 4000);
    close(fd);

    return 0;
}

参考:https://subingwen.cn/linux/file-descriptor/
参考:https://subingwen.cn/linux/pipe/
参考:https://subingwen.cn/linux/mmap/

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-07 11:29:31  更:2022-05-07 11:32:34 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:19:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码