【linux进程间通信(一)】匿名管道和命名管道

简介: 【linux进程间通信(一)】匿名管道和命名管道

1. 前言

众所周知,进程运行是具有独立性的,

想要进程间进行通信就要打破这种

独立性,而进程间通信的本质其实是

让不同的进程看见同一份资源!

本章重点:

本篇文章会介绍进程间通信中常见
的几种方式,并且着重讲解匿名管道
和命名管道的这两种通信手段的原理
和代码的实现.


2. 进程间通信的方法

首先通信种类分为三大类:

  • 管道
  • system V
  • POSIX

当然网络通信的本质其实也是进程

间的通信,但是本篇文章的重点是

利用管道通信!!!


3. 管道的简单介绍

首先要回答什么是管道,在学习

Linux指令时我们使用过竖划线 |

也就是管道,把从一个进程连接

到另一个进程的数据流称为“管道”

在管道通信中,管道的本质其实就是
一个被系统打开的文件,然而用管道
进行通信的本质就是让不同的进程
看见相同的资源(文件)

并且管道是单向通信的,即一个进程

不能同时从一个管道中读取和写入,

读取和写入要对应两个不同的管道!


4. 匿名管道

对于匿名管道来说,通常用于父子
进程之间的通信,在父进程使用
pipe创建好管道后,再使用fork创建
子进程,此时子进程会将父进程的
文件描述符表给拷贝过来,也就天然
的拥有这两个管道文件了!

并且此时我们会面临两个问题:

  1. 读取方将文件关闭了会发送什么?
  2. 读取方在文件中没有数据时会干什么?
  • 读端关闭后,写端进程会终止
  • 当管道无数据时读端会阻塞

有了上面的经验后,现在可以编码验证一下:

int main()
{
    //创建管道
    int pipefd[2]={0}; //0下标表示读取端,1下标表示写入端
    int n = pipe(pipefd);
    assert(n!=-1);
    (void)n;
#ifdef DEBUG//条件编译
    cout<<"[0]: "<<pipefd[0]<<" "<<"[1]: "<<pipefd[1]<<endl;
#endif
    //创建子进程
    pid_t id = fork();
    assert(id!=-1);
    if(id==0)//子进程, 构建单向通信
    {
        close(pipefd[1]);
        char buffer[1024];
        while(1)
        {
            ssize_t s = read(pipefd[0],buffer,sizeof(buffer)-1);
            if(s>0)
            {
                buffer[s]=0;
                cout<<"father# "<<buffer<<endl;
            }
            else//read的返回值等于0代表父进程的管道文件已经close了
            {
                cout<<"写入结束,子进程退出";
                break;
            }
        }
        exit(0);
    }
    //父进程写入,子进程读取
    close(pipefd[0]);
    string str = "我在给子进程发信息";
    int count=0;
    char send_buffer[1024];
    while(count<=5)
    {
        //构建一个变化的字符串
        snprintf(send_buffer, sizeof(send_buffer),"%s[%d]: %d",str.c_str(),getpid(),count++);//往缓冲区里写入数据
        //写入到管道中
        write(pipefd[1],send_buffer,sizeof(send_buffer));
        sleep(1);
    }
    close(pipefd[1]);
    pid_t ret = waitpid(id,NULL,0);
    assert(ret > 0);
    return 0;
}

总结管道的特点:

  • 管道常用于父子进程间的通信
  • 管道是面向字节流的服务
  • 管道是基于文件的,管道的生命周期随进程
  • 管道是单向通信的
  • 写快读慢,写满管道后不能再写了
  • 写慢读快,管道没有数据时,读端要等待
  • 写端关闭,读端会读到0,标识结束
  • 读端关闭,写端继续写会终止进程

5. 命名管道

匿名管道的一个限制就是必须是在

有血缘关系的进程间才能通信,使用

命名管道可以解决这个问题

匿名管道和命名管道的区别:

  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,打开用open
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

命名管道的打开规则:

  • 如果当前打开操作是为读而打开FIFO时
  1. O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  2. O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时
  1. O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  2. O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

命名管道简单通信:

读端代码:

#define SIZE 1024
#define MODE 0666
string ipcpath = "./fifo.ipc";
static void getmessage(int fd)
{
    //编写通信代码
    char buffer[SIZE];
    while(1)
    {
        memset(buffer,'\0',sizeof(buffer));
        ssize_t s = read(fd,buffer,sizeof(buffer)-1);
        if(s>0)//读取成功
        {
            cout<<"["<<getpid()<<"] "<<"client say: "<<buffer<<endl;
        }
        else if(s==0)//写端关闭,并且读到end
        {
            cerr<<"["<<getpid()<<"]"<<"read end"<<endl;
            break;
        }
        else//读取失败
        {
            perror("read");
            break;
        }
    }
}
int main()
{
    //创建管道文件
    int n = mkfifo(ipcpath.c_str(),MODE);
    if(n<0)
    {
        perror("mkfifo");
        exit(1);
    }
    //文件操作
    int fd = open(ipcpath.c_str(),O_RDONLY);//这里必须等待client进程将此管道文件打开后才能执行下面的代码
    if(fd<0)
    {
        perror("open");
        exit(2);
    }
    for(int i=0;i<4;i++)
    {
        int id = fork();
        if(id==0)
        {
            getmessage(fd);
            exit(0);
        }
    }
    
    close(fd);
    return 0;
}

写端代码:

#define SIZE 1024
#define MODE 0666
string ipcpath = "./fifo.ipc";
int main()
{
    //client不用自己创建管道文件,只需获取文件即可
    int fd = open(ipcpath.c_str(),O_WRONLY);
    if(fd<0)
    {
        perror("open");
        exit(1);
    }
    //通信过程
    string buffer;
    while(1)
    {
        cout<<"please Enter message: ";
        getline(cin,buffer);
        write(fd,buffer.c_str(),buffer.size());
    }
    close(fd);
    return 0;
}

6. 总结以及拓展

虽然匿名管道和命名管道已经包含了

对于有血缘关系和无血缘关系的进程

的通信,但是毕竟调用read和write这些

系统调用接口会不断的在用户态和内核

态进行切换,比较消耗资源,所以管道不是

最好的!

拓展: 为什么读端关闭后,写端进程会终止?

这是因为当读端关闭后,写端再使用
write操作会产生SIGPIPE信号,此进
程收到此信号进而会终止进程,可以
使用捕捉信号的方式验证这一结论


🔎 下期预告:共享内存通信方式 🔍


相关文章
|
5月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
138 16
|
4月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
86 0
|
4月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
118 0
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
84 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
82 0
|
7月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
7月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
352 5
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能
|
弹性计算 Linux 区块链
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
428 4
Linux系统CPU异常占用(minerd 、tplink等挖矿进程)
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。