Linux——进程间通信、管道

这篇具有很好参考价值的文章主要介绍了Linux——进程间通信、管道。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

进程间通信介绍

什么叫进程间的通信

进程间的通信就是在不同进程之间传播或交换信息。
举个例子:
古时,两军交战不斩来使;
因为两军互相是独立的,所以使节就是两军之间传话的进行传话的;
而在OS中,进程之间也是相互独立的,但某项工作并不是一个进程就可以完成,而是多个进程之间相互协助完成;
所以,要让进程之间联系起来就将进程间的信息传递起来,这样就有了进程间的通信。

进程间通信目的

通过上述分析,我们就可以得到进程间通信的目的:

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

进程间通信发展

  • 管道
  • System V进程间通信
  • POSIX进程间通信

进程间通信的本质

进程间通信的本质就是让不同的进程看到同一份资源。

首先我们需要知道进程是独立的,比如:现在有两个进程A、B,A进程此时需要B进程上执行任务的信息,那么应该如何获取???
是A进程自己去B里面将数据拷贝出来呢还是B进程访问A进程将那一部分数据拷贝给A呢???
显而易见,都不合适,因为我们前面说了,进程是独立的。上面两种方法都违背了独立性。
正确的方式是,来一个第三方,它拥有者A、B进程需要的数据,A、B进程需要的时候来找第三方,如下图:
Linux——进程间通信、管道,Linux,linux,通信,笔记

进程间通信的分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V 消息队列
  • System V 共享内存
  • System V 信号量

POSIX IPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

什么是管道???

管道就是我们之前指令中所用到的一个个 |(竖划线)
我们把从一个进程连接到另一个进程的数据流称为一个“管道”。
比如:统计我们当前使用云服务器上的登录用户个数。Linux——进程间通信、管道,Linux,linux,通信,笔记
Linux——进程间通信、管道,Linux,linux,通信,笔记
上述,whowc命令是两个程序,执行起来变成两个程序;who将标准输出写入管道、wc将管道中的数据读取出来,从而实现数据的传输。

通过上述,我们发现,管道的传输是单向的;一个进程在这个管道要么读、要么写。
tips:如果既要读也要写,那么就可以反方向再创建一个管道。

匿名管道

匿名管道实现父子进程间通信的原理:让父子进程先看到同一份被打开的文件资源,然后父子进程就可以对该文件进行写入或是读取操作,实现父子进程间通信。Linux——进程间通信、管道,Linux,linux,通信,笔记

tips:
这里父子进程看到的同一份文件资源是由操作系统来维护的,所以当父子进程对该文件进行写入操作时,该文件缓冲区当中的数据并不会进行写时拷贝。
管道虽然用的是文件的方案,但操作系统一定不会把进程进行通信的数据刷新到磁盘当中,因为这样做有IO参与会降低效率,而且也没有必要。也就是说,这种文件是一批不会把数据写到磁盘当中的文件,换句话说,磁盘文件和内存文件不一定是一一对应的,有些文件只会在内存当中存在,而不会在磁盘当中存在。所以称之为匿名管道。

pipe函数

#include <unistd.h>

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

匿名管道的使用

  1. 父进程调用pipe函数创建管道。
    Linux——进程间通信、管道,Linux,linux,通信,笔记

  2. 父进程创建子进程。
    Linux——进程间通信、管道,Linux,linux,通信,笔记

  3. 父进程关闭写端,子进程关闭读端。
    Linux——进程间通信、管道,Linux,linux,通信,笔记

  • 管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。

  • 从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取。

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

Linux——进程间通信、管道,Linux,linux,通信,笔记


建立管道
Makefile文件

mypipe:mypipe.cc
	g++ -o $@ $^ -std=c++11 -g
.PHONY:clean
clean:
	rm -f mypipe
//子进程向管道中写入、父进程从管道中读出;
//关闭子进程的读出、关闭父进程的写入;
#include<iostream>
#include<cassert>
#include<unistd.h>
#include<cstring>
#include<sys/types.h>
#include<sys/wait.h>
#define MAX  1024
using namespace std;
 
int main()
{
    //第1步,创建管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n==0);
    (void)n;//防止编译器警告,意料之中用assert,意料之外用if
    cout<<"pipefd[0]:"<<pipefd[0]<<",pipefd[1]:"<<pipefd[1]<<endl;
 
 
    //第2步,创建子进程
    pid_t id = fork();
    if(id<0)
    {
        perror("error");
        return 1;
    }
 
    //子写,父读
    //第3步,父子关闭不需要的fd,形成单向通信的管道
    if(id==0)
    {
        //child
        close(pipefd[0]);
        //只向管道写入,没有打印
        int cnt = 10;
        while(cnt)
        {
            char message[MAX];
            snprintf(message,sizeof(message),"hello father,I am a child,pid: %d, cnt: %d",getpid(),cnt);
            cnt--;
            //子写
            write(pipefd[1],message,strlen(message));
            sleep(1);
 
        }
        exit(0);
    }
 
    //father
    close(pipefd[1]);
 
    //父读
    char buffer[MAX];
    while(true)
    {
        ssize_t n = read(pipefd[0],buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n] = 0; //'\0',当作字符串
            cout<<getpid()<<","<<"child say:"<<buffer<<"to me!"<<endl;
        }
 
    }
 
    pid_t rid = waitpid(id,nullptr,0);
    if(rid == id)
    {
        cout<<"wait success!"<<endl;
    }
 
    return 0;
}

Linux——进程间通信、管道,Linux,linux,通信,笔记

Linux——进程间通信、管道,Linux,linux,通信,笔记
因为管道是单向的,所以fork()之后需要确定父、子进程谁读谁写;
从写端进入管道的数据会被内核缓冲,一直等待读端来读取;

管道的4种情况

首先,我们需要知道,管道在同一时刻只允许一个进程对其进行写入或是读取操作,所以也就是一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

为了避免多个进程对同一管道进行操作的情况,进而导致同时读写,我们让进程按照预定的先后次序运行,一个公共资源同一时刻只能被一个进程使用

  1. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)

  2. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
    对于1、2,我们知道了管道并不能被两个进程同时读写,所以并不会是说当一方停止写入导致管道没数据了另一方还在读取,或者说管道满了还在写入;
    读端读取数据的条件是管道里面有数据,写端进程写入数据的条件是管道当中还有空间;
    若是条件不满足,则相应的进程就会被挂起,直到条件满足后才会被再次唤醒。

  3. 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件尾
    也就是读端将写端写入管道中的数据读完了,读端也读不出来数据了,所以返回0,表示读到文件尾

  4. 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过向目标进程发送SIGPIPE(13)信号,终止目标进程
    写端一直写,但没人读啊,还有必要写吗???所以OS直接就把写段干掉,那么代码没有跑完,也就是一定会退出信号,可以看下面代码

#include <iostream>
#include <cassert>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MAX 1024

using namespace std;

int main()
{
    // 1.建立管道
    int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n == 0);
    (void)n; // 防止编译器警告,意料之中用assert,意料之外用if

    cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << endl;
    // pipefd[0]: 3, pipefd[1]: 4
    // 读端、、、、、、写端

    // 2.创建子进程
    pid_t id = fork();
    if (id < 0)
    {
        perror("fork");
        return 1;
    }
    // 子写、父读
    // 3.父子关闭不需要的fd,形成单向通信的管道
    if (id == 0)
    {
        // child
        close(pipefd[0]); // 关闭读端
        // w
        int cnt = 0;
        while (true)
        {
            char msg[MAX];
            snprintf(msg, sizeof msg, "hello father , I am child, pid: %d, cnt: %d", getpid(), cnt);
            cnt++;
            write(pipefd[1], msg, strlen(msg));
            sleep(1);

            if (cnt > 3)
                break;
        }
        cout << "child close w point" << endl;
        
        exit(0);
    }

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

    // r -
    char buffer[MAX];
    while (true)
    {
        ssize_t n = read(pipefd[0], buffer, sizeof buffer - 1);
        if (n > 0)
        {
            buffer[n] = '\0'; // '\0', 当做字符串
            cout << getpid() << ", "
                 << "child say: " << buffer << " to me!" << endl;
        }
        else if (n == 0)
        {
            cout << "child quit, me too !" << endl;
            break;
        }
        cout << "father return val(n): " << n << endl;
        sleep(1);

        break;
    }

    cout << "read point close" << endl;
    close(pipefd[0]);

    sleep(5);

    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if (rid == id)
    {
        cout << "wait success, child exit sig: " << (status & 0x7F) << endl;
    }

    return 0;
}

Linux——进程间通信、管道,Linux,linux,通信,笔记

管道的5种特性

  1. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用于父子,仅限于此
  2. 匿名管道,默认给读写端要提供同步机制
  3. 面向字节流的
  4. 管道的生命周期是随进程的
    管道的本质也是文件,那么打开文件的进程退出后,文件struct file也会释放,所以管道的生命周期是随进程的
  5. 管道是单向通信的,半双工通信的一种特殊情况

需要双方通信时,需要建立起两个管道。

Linux——进程间通信、管道,Linux,linux,通信,笔记

管道的大小

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
	int pipefd[2] = {0};
    int n = pipe(pipefd);
    assert(n==0);
    (void)n;//防止编译器警告,意料之中用assert,意料之外用if
	
	pid_t id = fork(); //fork创建子进程
	if (id == 0)
	{
		//child 
		close(fd[0]); //读端关闭
		char c = 'a';
		int cnt= 0;
		//子进程一直进行写入,一次写入一个字节
		while (1)
		{
			write(fd[1], &c, 1);
			cnt++;
			printf("%d\n", cnt); //打印当前写入的字节数
		}
		close(fd[1]);
		exit(0);
	}
	//father
	close(fd[1]); //写端关闭

	//父进程不进行读取

	waitpid(id, NULL, 0);
	close(fd[0]);
	return 0;
}

Linux——进程间通信、管道,Linux,linux,通信,笔记

写端最多写65536字节的数据就被操作系统挂起了,所以当前管道的最大容量是65536字节。

使用指令查看
Linux——进程间通信、管道,Linux,linux,通信,笔记

命名管道

  • 管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
  • 如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
  • 命名管道是一种特殊类型的文件

创建一个命名管道

从命令行创建,使用mkfifo filename
Linux——进程间通信、管道,Linux,linux,通信,笔记

从程序里创建,相关函数

int mkfifo(const char *filename,mode_t mode);

mkfifo参数含义:

  • 第一个参数表示要创建的命名管道
  • 第二个参数表示创建命名管道文件的默认权限

mkfifo函数的返回值。

  • 创建成功,返回0
  • 创建失败,返回-1
int n = mkfifo(FILENAME, 0666);
if (n < 0)
{
   	  std::cerr << "errno: " << errno  << " , "
                << "errstring" << strerror(errno) << std::endl;
	  return false;
}

命名管道的打开规则

如果当前打开操作是为读而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  • O_NONBLOCK enable:立刻返回成功

如果当前打开操作是为写而打开FIFO时

  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

匿名管道与命名管道的区别

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

用命名管道实现server&client通信

  • 对于服务端,先使用mkfifo创建一个命名管道文件,然后以读的方式打开该命名管道文件,服务端就可以从该命名管道当中读取客户端发来的信息
  • 对于客户端,因为服务端首先运行起来,之后命名管道文件就已经被创建了,所以客户端不需要套创建文件,只需以写的方式打开该命名管道文件,最后客户端就可以将信息写入到命名管道文件当中,进而实现和服务端的通信
  • 如何让客户端和服务端使用同一个命名管道文件,我们让客户端和服务端包含同一个头文件,该头文件当中提供这个共用的命名管道文件的文件名,也就是#include "commi.h"

Makefile以及#include "commi.h"文章来源地址https://www.toymoban.com/news/detail-835810.html

//#include "commi.h"
#define FILENAME ".fifo"

//Makefile
.PHONY:all
all:server client

server:server.cc
	g++ -o $@ $^ -std=c++11

client:client.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf server client fifo
服务端(server)
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "commi.h"

using namespace std;

bool Makefifo()
{
    int n = mkfifo(FILENAME, 0666);
    if (n < 0)
    {
        std::cerr << "errno: " << errno << ", errstring" << strerror(errno) << std::endl;
        return false;
    }
    std::cout << "mkfifo success...  read" << std::endl;
    return true;
}

int main()
{
BEGIN:

    int rfd = open(FILENAME, O_RDONLY);
    if (rfd < 0)
    {
        std::cerr << "errno: " << errno << ", errstring" << strerror(errno) << std::endl;
        if(Makefifo()) goto BEGIN;
        else return 1;
    }
    std::cout << "open success...  read" << std::endl;
    char buffer[1024];
    while (true)
    {
        ssize_t s = read(rfd, buffer, sizeof buffer - 1);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "Client sys# " << buffer << std::endl;
        }
        else if (s == 0)
        {
            std::cout << "client quit , serve quit too!" << std::endl;
            break;
        }
    }

    close(rfd);
    std::cout << "close success...  read" << std::endl;

    return 0;
}
客户端(client)
#include <iostream>
#include <cstring>
#include <cerrno>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "commi.h"

using namespace std;

int main()
{
    int wfd = open(FILENAME, O_WRONLY);
    if (wfd < 0)
    {
        std::cerr << "errno: " << errno << ", errstring" << strerror(errno) << std::endl;
        return 1;
    }
    std::cout << "open fifo success...  write" << std::endl;

    std::string msg;
    while (true)
    {
        std::cout << "Please Enter#" ;
        std::getline(std::cin, msg);

        ssize_t s = write(wfd, msg.c_str(), msg.size());
        if (s < 0)
        {
            std::cerr << "errno: " << errno << ", errstring" << strerror(errno) << std::endl;
            break;
        }
    }

    close(wfd);
    std::cout << "close fifo success...  " << std::endl;

    return 0;
}

到了这里,关于Linux——进程间通信、管道的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linux】进程通信 — 管道

    从本章开始,我们开始学习进程通信相关的知识,本章将来详细探讨一下管道,学习匿名管道和命名管道的原理和代码实现等相关操作。目标已经确定,接下来就要搬好小板凳,准备开讲了…🙆🙆🙆🙆 在我们之前的学习中,我们知道进程是具独立性的。但是不要以为进程

    2024年02月16日
    浏览(40)
  • linux——进程间通信——管道

     ✅1主页::我的代码爱吃辣 📃2知识讲解:Linux——进程间通信——管道通信 ☂️3开发环境:Centos7 💬4前言:进程间通信(InterProcess Communication,IPC)是指在不同进程之间传播或交换信息。 目录 一.什么是进程间通信 二.进程间通信目的  三.进程间通信发展 四.什么是管道

    2024年02月08日
    浏览(43)
  • [Linux]进程间通信--管道

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

    2024年02月09日
    浏览(40)
  • Linux进程通信:无名管道

    (1)数据传输:进程间数据传输; (2)通知事件:一个进程向另一个或一组进程发送消息,通知某个事件的发生(如子进程终止时需通知父进程); (3)资源共享:多个进程共享资源,需要内核提供同步互斥机制; (4)进程控制:某进程需要控制另一个进程的执行(如

    2023年04月24日
    浏览(55)
  • 【Linux】进程间通信(匿名管道 & 命名管道)-- 详解

    如何理解进程间通信? 进程具有独立性,所以进程想要通信难度是比较大的,成本高。 在日常生活中,通信的本质是传递信息,但站在程序员角度来看, 进程间通信的本质:让不同的进程看到同一份资源(内存空间) 。 进程间通信就是进程之间互相传递数据,那么进程间

    2024年04月28日
    浏览(51)
  • 【Linux】进程间通信——进程间通信的介绍和分类、管道、匿名管道、命名管道、匿名管道与命名管道的区别

      进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中

    2024年02月05日
    浏览(55)
  • 【LInux】进程间通信 -- 匿名管道

    我们在学习进程管理,进程替换时,都强调了 进程的独立性 ,那进程间通信是什么?这好像和进程的独立性 相矛盾 吧? 那么今天,我们就来学习 进程间通信 ,和第一种通信方式 – 管道 进程间通信,并没有破坏进程的独立性这一特点,这点我们在 管道 讲解 而进程通信的

    2023年04月19日
    浏览(35)
  • 【Linux】进程间通信 -- 命名管道

    在管道的通信中,除了 匿名管道 ,还有一个 命名管道 。 匿名管道只支持具有 “亲戚关系” 的进程间通信,而命名管道就可以支持不同的,任意的进程通信。 那就下来就开始我们今天的学习。 匿名管道的两种使用方式: 指令的 \\\' | \\\' 和pipe()函数 命名管道也有两种使用方式

    2023年04月20日
    浏览(38)
  • linux——进程间通信——命名管道

     ✅1主页::我的代码爱吃辣 📃2知识讲解:Linux——进程间通信——命名管道 ☂️3开发环境:Centos7 💬4前言:命名管道是一种特殊的文件存放在文件系统中,而不是像管道那样存放在内核中。命名管道可以用于任何两个进程间的通信,而不限于同源的两个进程。当进程对

    2024年02月08日
    浏览(50)
  • Linux进程间通信【命名管道】

    ✨个人主页: 北 海 🎉所属专栏: Linux学习之旅 🎃操作环境: CentOS 7.6 阿里云远程服务器 命名管道通信属于 IPC 的其中一种方式,作为管道家族,命名管道的特点就是 自带同步与互斥机制、数据单向流通 ,与匿名管道不同的是:命名管道有自己的名字,因此可以被没有血

    2024年02月08日
    浏览(66)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包