【Linux】进程通信----管道通信(上)

简介: 【Linux】进程通信----管道通信(上)

> 作者:დ旧言~

> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:理解进程通信----管道通信

> 毒鸡汤:有些事情,总是不明白,所以我不会坚持。早安!

> 专栏选自:Linux初阶

> 望小伙伴们点赞👍收藏✨加关注哟💕💕



🌟前言

当我们创建两个进程,希望它们可以相互沟通,我的内容你可以读,我的内容你可以写,这种联系我们就需要我们的管道,这个管道就可以达成两个进程或者更多进程相互通信,那这个关系在Linux中是如何实现的呢,我们又该如何理解呢?


⭐主体

学习【Linux】静态库和动态库咱们按照下面的图解:



🌙 什么是进程间通信


💫 进程间通信介绍

什么是进程间通信?

进程具有独立性,每个进程都有自己的PCB,所以进程间需要通信,并且通信的成本一定不低(通信的本质:OS需要直接或者间接给通信双方的进程提供“内存空间”,并且要通信的进程,必须看到一份公共的资源)

图解:



如何去通信

  • 采用标准的做法:System V进程间通信(聚焦在本地通信,如共享内存)、POSIX进程间通信(让通信过程可以跨主机)。
  • 采用文件的做法:管道-基于文件系统(匿名管道、命名管道)


管道通信的特点:

是面向字节流、占用内存空间、只能单向传输、有固定的大小和缓冲区等。

总结:

而我们所说的不同通信种类本质就是:上面所说的资源,是OS中的哪一个模块提供的。如文件系统提供的叫管道通信;OS对应的System V模块提供的…,(成本不低是因为我们需要让不同的进程看到同一份资源。


💫 进程间通信目的

进程间通信的目的:

  • 数据传输:一个进程需要将它的数据发送给另一个进程
  • 资源共享:多个进程之间共享同样的资源
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程)


为什么要有进程间通信?

有时候我们需要多进程协同的,完成某种业务内容。比如管道。

图解:



💫 进程间通信分类

进程间通信分类:(匿名管道和命名管道)


  • 匿名管道是只能在父子进程间使用的,它通过pipe()函数创建,并返回两个文件描述符,一个用于读,一个用于写。
  • 命名管道是可以在任意进程间使用的,它通过mkfifo()或mknod()函数创建一个特殊的文件,然后通过open()函数打开,并返回一个文件描述符,用于读或写。


🌙 管道的实现和理解


💫 管道介绍

概念:

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


分析:


任何一个文件包括两套资源:1.file的操作方法 2.有属于自己的内核缓冲区,所以父进程和子进程有一份公共的资源:文件系统提供的内核缓冲区,父进程可以向对应的文件的文件缓冲区写入,子进程可以通过文件缓冲区读取,此时就完成了进程间通信,这种方式提供的文件称为管道文件。管道文件本质就是内存级文件,不需要IO。


两个进程如何看到同一个管道文件:fork创建子进程完成


管道创建时分别以读和写方式打开同一个文件(如果只读或者只写,子进程也只会继承只读或只写,父子双方打开文件的方式一样,无法完成单向通信);父进程创建子进程,父进程以读写打开,子进程也是以读写打开(一般而言,管道只用来进行单向数据通信);关闭父子进程不需要的文件描述符,完成通信。



💫 匿名管道

概念:

我们通过文件名区分文件,但是如果当前进程的文件没有名字,这样的内存级文件称为匿名管道。让两个进程看到同一个文件,通过父进程创建子进程,子进程继承文件地址的方式,看到同一个内存级文件,此时内存级文件没有名称就是匿名管道了。匿名管道能用来父进程和子进程之间进行进程间通信。


1.pipe

作用及其使用:

  • 创建一个管道只需要调用pipe,注意头文件,返回值,以及函数的参数。
  • 头文件为#include ,调用成功返回0,调用失败返回-1。参数是输出型参数。
SYNOPSIS
       #include <unistd.h>
       int pipe(int pipefd[2]);
DESCRIPTION
    pipe() creates a pipe,pipefd[0]  refers  to  the  read end of the pipe.  pipefd[1] refers to the write end of the pipe.
RETURN VALUE
       On success, zero is returned.  On error, -1 is returned, and errno is set appropriately.


创建Makefile文件

mypipe:mypipe.cc
  g++ mypipe.cc -o mypipe
 
.PHONY:clean
clean:
  rm -f mypipe


创建管道文件,打开读写端

#include <iostream>
#include <unistd.h>
#include <cassert>
 
using namespace std;
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
 
    //0,1,2->3,4
    //[0]:读取  [1]:写入
    cout<<"fds[0]:"<<fds[0]<<endl;//3
    cout<<"fds[1]:"<<fds[1]<<endl;//4
    return 0;
}



因此,fds[0]:3代表读取,fds[1]:4代表写入;

fork子进程:

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
 
    //fork
    pid_t id = fork();
    assert(id>=0);
    if(id==0)
    {
        //子进程通信
 
        exit(0);
    }
    //父进程通信
    n = waitpid(id,nullptr,0);
    assert(n==id);
    return 0;
}


关闭父子进程不需要的文件描述符,完成通信(子进程写入,父进程读取)

#include <iostream>
#include <unistd.h>
#include <cassert>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
using namespace std;
 
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
    //fork
    pid_t id = fork();
    assert(id>=0);
    if(id==0)
    {
        //子进程通信:子进程进行写入,关闭读
        close(fds[0]);
        //通信
        const char*s = "这是子进程,正在进行通信";
        int cnt = 0;
        while(true)
        {
            cnt++;
            char buffer[1024];
            snprintf(buffer,sizeof buffer,"child->parent say:%s[%d][%d]",s,cnt,getpid());
            //写端写满的时候,在写会阻塞,等对方进行读取
            write(fds[1],buffer,strlen(buffer));//系统接口
            sleep(1);//一秒写一次
        }
        //退出前关闭子进程
        close(fds[1]);
        exit(0);
    }
    //父进程通信:父进程进行读取,关闭写
    close(fds[1]);
    //通信
    while(true)
    {
        char buffer[1024];
        //管道中如果没有数据,读端在读,默认会直接阻塞当前正在读取的进程
        ssize_t s = read(fds[0],buffer,sizeof(buffer)-1);
        if(s>0) buffer[s] = 0;
        cout<<"Get Message# "<<buffer<<"|mypid:"<<getpid()<<endl;
    }
 
    n = waitpid(id,nullptr,0);
    assert(n==id);
    
    //结束前关闭
    close(fds[0]);
    return 0;
}



2.读写特征

管道读写特征:

  • 1.读快写慢

分析:

  • 子进程休眠时,不在写入,父进程在读取(如果管道中没有数据,读端在读,此时默认会直接阻塞当前正在读取的进程)

代码:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
    pid_t id = fork();
    assert(id >= 0);
    if (id == 0) // 子进程
    {
        // 子进程通信,关闭子进程的读取端,即子进程进行写入
        close(fds[0]);
        const char *s = "你好,我是子进程,正在进行通信";
        int cnt = 0;
        while (1)
        {
            cnt++;
            char buffer[1024];
            snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]", s, cnt, getpid());
            write(fds[1], buffer, strlen(buffer));
            sleep(50); // 每一秒写一次
        }
        close(fds[1]); // 退出子进程前关闭文件写入端
        exit(0);
    }
    // 父进程
    close(fds[1]); // 父进程关闭写入端,即父进程进行读取
    while (1)
    {
        char buffer[1024];
        printf("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
        printf("888888888888888888888888888888888888!!\n");
        if (s > 0)
            buffer[s] = 0;
        printf("Get Message : %s | mypid = %d\n", buffer, getpid());
    }
    n = waitpid(id, NULL, 0);
    assert(n == id);
    close(fds[0]); // 退出程序前,关闭读取端
    return 0;
}



  • 2.读慢写快

分析:

  • 读取管道的进程一直不进行读取,而写端一直在写入。写端可以向管道内写入,但是管道是固定大小的缓冲区,不断的只写不读管道会被写满。满了以后就不能再写入了,此时写端会处于阻塞状态。

文件mypipe.cc

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
 
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
    pid_t id = fork();
    assert(id >= 0);
    if (id == 0) // 子进程
    {
        // 子进程通信,关闭子进程的读取端,即子进程进行写入
        close(fds[0]);
        const char *s = "你好,我是子进程,正在进行通信";
        int cnt = 0;
        while (1)
        {
            cnt++;
            char buffer[1024];
            snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]", s, cnt, getpid());
            write(fds[1], buffer, strlen(buffer));
            printf("count: %d\n", cnt);
        }
        close(fds[1]); // 退出子进程前关闭文件写入端
        exit(0);
    }
    // 父进程
    close(fds[1]); // 父进程关闭写入端,即父进程进行读取
    while (1)
    {
        sleep(50); // 父进程不读
        char buffer[1024];
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
        printf("Get Message : %s | mypid = %d\n", buffer, getpid());
    }
    n = waitpid(id, NULL, 0);
    assert(n == id);
    close(fds[0]); // 退出程序前,关闭读取端
    return 0;
}



拓展:

如果休息sleep(2),这种情况,写端是将数据塞到管道内,管道读取是安装指定大小读取(并非一行一行的读取,最初安装一行来读取是因为写入的慢,一次只写一行数据,数据就被读取了)。


  • 3.写入关闭,读到0

子进程写入端关闭:

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
    pid_t id = fork();
    assert(id >= 0);
    if (id == 0) // 子进程
    {
        // 子进程通信,关闭子进程的读取端,即子进程进行写入
        close(fds[0]);
        const char *s = "你好,我是子进程,正在进行通信";
        int cnt = 0;
        while (1)
        {
            cnt++;
            char buffer[1024];
            snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]", s, cnt, getpid());
            write(fds[1], buffer, strlen(buffer));
            printf("count: %d\n", cnt);
            break;
        }
        close(fds[1]); // 退出子进程前关闭文件写入端
        exit(0);
    }
    // 父进程
    close(fds[1]); // 父进程关闭写入端,即父进程进行读取
    while (1)
    {
        sleep(2); // 父进程不读
        char buffer[1024];
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            printf("Get Message : %s | mypid = %d\n", buffer, getpid());
        }
        else if (s == 0) // 写入端关闭,读到文件末尾了
        {
            printf("read: %d\n", s);
            break; // 关闭读取端
        }
    }
    n = waitpid(id, NULL, 0);
    assert(n == id);
    close(fds[0]); // 退出程序前,关闭读取端
    return 0;
}



  • 4. 读取端关闭,写入端直接关闭

关闭读取端后,写入端就没有意义了,因此OS会给写入的进程发送信号,终止该进程。

#include <stdio.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main()
{
    int fds[2];
    int n = pipe(fds);
    assert(n == 0);
    pid_t id = fork();
    assert(id >= 0);
    if (id == 0) // 子进程
    {
        // 子进程通信,关闭子进程的读取端,即子进程进行写入
        close(fds[0]);
        const char *s = "你好,我是子进程,正在进行通信";
        int cnt = 0;
        while (1)
        {
            cnt++;
            char buffer[1024];
            snprintf(buffer, sizeof buffer, "child -> parent say:%s [%d], [%d]", s, cnt, getpid());
            write(fds[1], buffer, strlen(buffer));
            printf("count: %d\n", cnt);
        }
        close(fds[1]); // 退出子进程前关闭文件写入端
        printf("子进程关闭写入端\n");
        exit(0);
    }
    // 父进程
    close(fds[1]); // 父进程关闭写入端,即父进程进行读取
    while (1)
    {
        sleep(2); // 父进程不读
        char buffer[1024];
        ssize_t s = read(fds[0], buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            printf("Get Message : %s | mypid = %d\n", buffer, getpid());
        }
        break; // 关闭读取端
    }
    close(fds[0]); // 退出程序前,关闭读取端
    printf("父进程关闭读取端\n");
    n = waitpid(id, NULL, 0);
    assert(n == id);
    return 0;
}




【Linux】进程通信----管道通信(下) https://developer.aliyun.com/article/1565748

目录
相关文章
|
16天前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
1月前
|
存储 网络协议 Linux
【Linux】进程IO|系统调用|open|write|文件描述符fd|封装|理解一切皆文件
本文详细介绍了Linux中的进程IO与系统调用,包括 `open`、`write`、`read`和 `close`函数及其用法,解释了文件描述符(fd)的概念,并深入探讨了Linux中的“一切皆文件”思想。这种设计极大地简化了系统编程,使得处理不同类型的IO设备变得更加一致和简单。通过本文的学习,您应该能够更好地理解和应用Linux中的进程IO操作,提高系统编程的效率和能力。
77 34
|
12天前
|
Linux
Linux:守护进程(进程组、会话和守护进程)
守护进程在 Linux 系统中扮演着重要角色,通过后台执行关键任务和服务,确保系统的稳定运行。理解进程组和会话的概念,是正确创建和管理守护进程的基础。使用现代的 `systemd` 或传统的 `init.d` 方法,可以有效地管理守护进程,提升系统的可靠性和可维护性。希望本文能帮助读者深入理解并掌握 Linux 守护进程的相关知识。
27 7
|
11天前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
32 5
|
11天前
|
Linux 应用服务中间件 nginx
Linux 进程管理基础
Linux 进程是操作系统中运行程序的实例,彼此隔离以确保安全性和稳定性。常用命令查看和管理进程:`ps` 显示当前终端会话相关进程;`ps aux` 和 `ps -ef` 显示所有进程信息;`ps -u username` 查看特定用户进程;`ps -e | grep &lt;进程名&gt;` 查找特定进程;`ps -p &lt;PID&gt;` 查看指定 PID 的进程详情。终止进程可用 `kill &lt;PID&gt;` 或 `pkill &lt;进程名&gt;`,强制终止加 `-9` 选项。
20 3
|
1月前
|
消息中间件 Linux C++
c++ linux通过实现独立进程之间的通信和传递字符串 demo
的进程间通信机制,适用于父子进程之间的数据传输。希望本文能帮助您更好地理解和应用Linux管道,提升开发效率。 在实际开发中,除了管道,还可以根据具体需求选择消息队列、共享内存、套接字等其他进程间通信方
68 16
|
9月前
|
监控 Linux 应用服务中间件
探索Linux中的`ps`命令:进程监控与分析的利器
探索Linux中的`ps`命令:进程监控与分析的利器
175 13
|
8月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
8月前
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
243 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
7月前
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。