Bootstrap

【LInux】管道详解

📢博客主页:https://blog.csdn.net/2301_779549673
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述


📢前言


🏳️‍🌈1. 进程间通信目的

数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

🏳️‍🌈2. 管道

什么是管道
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个管道

在这里插入图片描述

🏳️‍🌈3. 匿名管道

#include <unistd.h>
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端返回值:
成功返回0,失败返回错误代码

在这里插入图片描述

3.1 示例代码

例⼦:从键盘读取数据,写⼊管道,读取管道,写到屏幕

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(){
    int fds[2];
    char buf[100];
    int len;

    if(pipe(fds) == -1){
        perror("make pipe");
        exit(1);
    }

    // read from stdin
    while(fgets(buf, 100, stdin)){
        len = strlen(buf);
        if(write(fds[1], buf, len) != len){
            perror("write to pipe");
            break;
        }

        memset(buf, 0x00, sizeof(buf));

        // read from pipe
        if((len = read(fds[0], buf, 100)) == -1){
            perror("read from pipe");
            break;
        }
   		// write to stdout
        if(write(1, buf, len) != len){
            perror("write to stdout");
            break;
        }
    }
}

3.2 用fork来共享管道原理

在这里插入图片描述

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


int main() {
    int fd[2];
    pid_t pid;

    // 创建管道
    if (pipe(fd) == -1) {
        perror("pipe");
        return 1;
    }

    // 创建子进程
    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    } 
    else if (pid > 0) {  // 父进程
        // 父进程关闭管道读端
        close(fd[0]);
        // 父进程向管道写入数据
        char* message = "Hello from parent!";
        write(fd[1], message, sizeof(message));
        // 父进程关闭管道写端
        close(fd[1]);
    } 
    else {  // 子进程
        // 子进程关闭管道写端
        close(fd[1]);
        // 子进程从管道读取数据
        char buffer[100];
        read(fd[0], buffer, sizeof(buffer));
        printf("Child received: %s\n", buffer);
        // 子进程关闭管道读端
        close(fd[0]);
    }

    return 0;
}
  1. 创建管道
    使用pipe(fd)函数创建一个管道,fd[0]是管道的读端,fd[1]是管道的写端。
  2. 创建子进程
    使用fork()函数创建一个子进程。fork()函数返回值有三种情况:
    如果返回值小于 0,表示创建子进程失败。
    如果返回值等于 0,表示当前是子进程。
    如果返回值大于 0,表示当前是父进程,返回值为子进程的 PID。
  3. 父进程操作
    父进程首先关闭管道的读端fd[0]。
    然后父进程向管道写入数据,这里写入字符串 “Hello from parent!”。
    最后父进程关闭管道的写端fd[1]。
  4. 子进程操作
    子进程首先关闭管道的写端fd[1]。
    然后子进程从管道读取数据,将读取的数据存储在buffer中。
    最后子进程打印出读取的数据,并关闭管道的读端fd[0]。
    这段代码实现了在父进程和子进程之间通过管道进行通信,验证了图中所示的管道共享原理。

3.3 站在文件描述符角度-深度理解管道

总的来说可以归结为下面这张图,但是与上面代码有所不同的是,上述代码仅仅只能实现一次性的管道传输。
在这里插入图片描述
这部分代码实现了父子进程之间持续的双向通信,只要不手动终止程序,它们就可以一直交换消息。

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

#define BUFFER_SIZE 100

int main() {
    int parent_to_child[2];
    int child_to_parent[2];
    pid_t pid;

    // 创建两个管道,一个用于父进程到子进程,一个用于子进程到父进程
    if (pipe(parent_to_child) == -1 || pipe(child_to_parent) == -1) {
        perror("pipe");
        return 1;
    }

    pid = fork();
    if (pid < 0) {
        perror("fork");
        return 1;
    } else if (pid > 0) { // 父进程
        close(parent_to_child[0]); // 关闭父进程到子进程管道的读端
        close(child_to_parent[1]); // 关闭子进程到父进程管道的写端

        char buffer[BUFFER_SIZE];
        while (1) {
            // 父进程向子进程发送消息
            printf("Parent: Enter a message to send to child (or 'quit' to exit): ");
            fgets(buffer, BUFFER_SIZE, stdin);
            buffer[strcspn(buffer, "\n")] = 0; // 去除换行符

            if (strcmp(buffer, "quit") == 0) {
                break;
            }

            write(parent_to_child[1], buffer, strlen(buffer) + 1);

            // 父进程从子进程接收消息
            ssize_t n = read(child_to_parent[0], buffer, BUFFER_SIZE);
            if (n > 0) {
                printf("Parent received from child: %s\n", buffer);
            }
        }

        close(parent_to_child[1]);
        close(child_to_parent[0]);
    } else { // 子进程
        close(parent_to_child[1]); // 关闭父进程到子进程管道的写端
        close(child_to_parent[0]); // 关闭子进程到父进程管道的读端

        char buffer[BUFFER_SIZE];
        while (1) {
            // 子进程从父进程接收消息
            ssize_t n = read(parent_to_child[0], buffer, BUFFER_SIZE);
            if (n > 0) {
                if (strcmp(buffer, "quit") == 0) {
                    break;
                }
                printf("Child received from parent: %s\n", buffer);
            }

            // 子进程向父进程发送消息
            printf("Child: Enter a message to send to parent (or 'quit' to exit): ");
            fgets(buffer, BUFFER_SIZE, stdin);
            buffer[strcspn(buffer, "\n")] = 0; // 去除换行符

            if (strcmp(buffer, "quit") == 0) {
                break;
            }

            write(child_to_parent[1], buffer, strlen(buffer) + 1);
        }

        close(parent_to_child[0]);
        close(child_to_parent[1]);
    }

    return 0;
}

3.4 站在内核角度-管道本质

从内核角度来看,管道本质上是一种特殊的文件系统机制,用于实现进程间通信(IPC)。以下是对管道本质的内核级理解:

  1. 文件结构与 inode
    文件结构(file structure):每个进程在操作管道时,都会有一个对应的文件结构。在图中可以看到进程 1 和进程 2 都有各自的文件结构。这个文件结构包含了进程对管道操作所需要的信息,如文件模式(f_mode)、文件位置(f_pos)、文件标志(f_flags)、文件引用计数(f_count)等。
    inode:文件结构通过f_inode指针指向一个 inode。inode 是内核中的一种数据结构,用于存储文件(这里是管道)的元数据信息,如文件的所有者(f_owner)、文件操作函数指针(f_op)等。对于管道来说,inode 中的操作函数指针会指向与管道操作相关的函数(如管道的读和写操作)。
  2. 管道的读写操作
    管道写操作(write):当进程 1 执行管道写操作时,它通过自己的文件结构和 inode 找到对应的管道数据结构,并调用 inode 中的写操作函数。写操作函数会将数据写入管道的数据页(data page)。
    管道读操作(read):当进程 2 执行管道读操作时,同样通过自己的文件结构和 inode 找到管道数据结构,并调用 inode 中的读操作函数。读操作函数会从管道的数据页中读取数据。
  3. 数据页
    数据存储:管道的数据页是实际存储数据的地方。当一个进程向管道写入数据时,数据被存储在这个数据页中,而另一个进程从管道读取数据时,也是从这个数据页中获取数据。数据页在内核中被维护,并且通过 inode 和文件结构来访问。
  4. 管道的内核实现机制
    同步与互斥:内核在实现管道时,需要处理多个进程对管道的并发访问问题。这通常涉及到同步和互斥机制,例如使用锁来确保在一个进程进行读或写操作时,其他进程不会同时进行冲突的操作。
    缓冲区管理:管道的数据页实际上就是一个缓冲区,内核需要管理这个缓冲区的大小和数据的存储与读取顺序。当缓冲区满时,写操作需要阻塞;当缓冲区空时,读操作需要阻塞。

综上所述,从内核角度来看,管道是通过文件系统相关的数据结构(文件结构、inode 和数据页)以及相应的操作函数来实现进程间通信的一种机制,它涉及到对数据的缓冲、同步和互斥等内核级操作。


👥总结

本篇博文对 管道 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;