• 【Linux】进程间通信——进程间通信的介绍和分类、管道、匿名管道、命名管道、匿名管道与命名管道的区别


    进程间通信

    1.进程间通信的介绍

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

    1.1目的和发展

      目的:

      (1)数据传输:一个进程需要将它的数据发送给另一个进程。

      (2)资源共享:多个进程之间共享同样的资源。

      (3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

      (4)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
      
      发展:

      管道 -> System V进程间通信 -> POSIX进程间通信。
      

    2.进程间通信分类

      管道:匿名管道、命名管道

      System V IPC:System V 消息队列、System V 共享内存、System V 信号量

      POSIX IPC:消息队列、共享内存、信号量、互斥量、条件变量、读写锁
      

    3.管道

      管道是Unix中最古老的进程间通信的形式。我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。
    在这里插入图片描述
      

    3.1匿名管道

      匿名管道的原理:

      匿名管道的原理是使用pipe函数创建管道,并在父进程中得到两个文件描述符,一个用于从管道读数据,另一个用于向管道写数据。 子进程在创建时会自动继承这两个文件描述符,从而可以实现父子进程间的数据交换。

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

    在这里插入图片描述

    //例子:从键盘读取数据,写入管道,读取管道,写到屏幕
    #include 
    #include 
    #include 
    #include 
    int main( void )
    {
    	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);
    		// write into pipe
    			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;
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39

      

    3.1.1匿名管道的原理(文件角度)

      匿名管道的原理从文件角度来说,是利用内存中共享的一段缓冲区,以文件的方式对缓冲区实现。但因为该文件只存在于内存中,没有唯一命名,所以称为匿名管道。

      具体来说,父进程创建管道文件描述符,然后通过fork创建子进程。子进程继承了父进程的文件描述符,这样父子进程就可以通过这个文件描述符进行通信。 由于管道是半双工方式,数据传输的方向是单向的,所以如果需要进行双向通信,需要创建两个管道。

    在这里插入图片描述

    在这里插入图片描述

      

    3.1.2匿名管道的原理(内核角度)

      从内核角度来说,匿名管道的原理是利用内核缓冲区作为伪文件,这个缓冲区由读端和写端两部分组成,对应两个文件描述符。数据从写端流入,从读端流出。

      匿名管道的内部实现方式是队列,而且是环形队列。这种队列的特性是先进先出,即一端入队,另一端出队,即只能从一端写入,另一端读出。缓冲区的大小默认是4k字节,但会根据实际情况做适当调整。

      由于用队列实现,数据只能读取一次,不能重复读取。另外,匿名管道是半双工方式,数据传输的方向是单向的。此外,匿名管道只适用于有血缘关系的进程,如父子进程、兄弟进程等。

    在这里插入图片描述

    这也符合了Linux的一切皆文件的思想

      
      父进程tast_struct中有指向file_struct的指针 *file,其中files_struct是一个struct file *fd_array[],这个array的数组中指向了各种的文件。同时创建子进程,子进程是父进程的一份拷贝,所以此时父进程指向的文件,子进程也同样会指向该文件,进程间通信的前提完成:让不同的文件看到同一份资源。

      
    在这里插入图片描述
      

      接着,我们可以进行不同进程间的通信了,如果我们想要先让子进程写入,父进程读取。则我们可以在父进程和子进程所指的同一个文件中,进行不同的操作:让子进程在文件缓冲区中写入数据,让父进程在同样的文件缓冲区中读取数据。即可完成通过使用管道的通信操作(所以管道也是文件)。

    在这里插入图片描述
      

    3.1.3管道读写规则

      (1)当没有数据可读时
      O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据来到为止。
      O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。

      (2)当管道满的时候
      O_NONBLOCK disable: write调用阻塞,直到有进程读走数据。
      O_NONBLOCK enable:调用返回-1,errno值为EAGAIN。

      (3)如果所有管道写端对应的文件描述符被关闭, 则read返回0。

      (4)如果所有管道读端对应的文件描述符被关闭, 则write操作会产生信号SIGPIPE,进而可能导致write进程退出。

      (5)当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。

      (6)当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

      

    3.1.4管道特点

      (1)只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信; 通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。

      (2)管道提供流式服务

      (3)一般而言,进程退出,管道释放,所以管道的生命周期随进程

      (4)一般而言,内核会对管道操作进行同步与互斥

      (5)管道是半双工的,数据只能向一个方向流动; 需要双方通信时,需要建立起两个管道。

    在这里插入图片描述
      

    3.2命名管道

      命名管道的介绍:

      Linux命名管道是一种特殊的文件类型,它允许不具有亲缘关系的进程之间进行通信。命名管道存在于文件系统中,但同时具有管道的优点,可以用于进程间通信。进程通过操作命名管道文件进行数据交换。

      命名管道的创建可以使用命令行工具(如mkfifo命令)或者在编程语言中的调用系统调用接口(如mkfifo函数)来创建。 创建好命名管道之后,可以使用open()和read/write()函数来读取和写入数据。

      在数据传输方面,命名管道的数据传输不会写入磁盘,而是在内存中进行传递。命名管道允许多个进程通过使用相同的管道名称进行通信,而不仅仅是两个进程之间的通信。与普通管道一样,命名管道中的数据也是临时存储在内存中的。

      

    3.2.1创建命名管道

      命名管道可以从命令行上创建:

    $ mkfifo filename
    
    • 1

      
      命名管道也可以从程序里创建,相关函数有:

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

      
      创建命名管道:

    int main(int argc, char *argv[])
    {
    	mkfifo("p2", 0644);
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

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

      (1)匿名管道由pipe函数创建并打开。

      (2)命名管道由mkfifo函数创建,打开用open。

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

      

    3.2.2命名管道的打开规则

      (1)如果当前打开操作是为读而打开FIFO时
      O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO。
      O_NONBLOCK enable:立刻返回成功。

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

      

    4.命名管道实现server&client通信

    测试实现:

    在这里插入图片描述
      

    comm.hpp

    #pragma once
    
    #include
    #include
    #include
    #include
    #include
    #include 
    #include 
    
    #define FIFO_FILE "./myfifo"
    #define MODE 0664
    
    enum{
        FIFO_CREATE_ERR=1,
        FIFO_DELETE_ERR=2,
        FIFO_OPEN_ERR=3
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

      
    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 -f client server myfifo
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

      
    server.cc

    //#include
    #include"comm.hpp"
    
    using namespace std;
    
    //管理管道文件
    int main()
    {
        //创建信道
        int n=mkfifo(FIFO_FILE,MODE);
        if(n==-1)
        {
            perror("mkfifo");
            exit(FIFO_CREATE_ERR);
        }
        //sleep(5);
        //打开信道
        int fd=open(FIFO_FILE,O_RDONLY);
        if(fd<0)
        {
            perror("open");
            exit(FIFO_OPEN_ERR);
        }
        cout<<"server open file done"<<endl;
    
        //开始信道
        while(true)
        {
            char buffer[1024]={0};
            int x=read(fd,buffer,sizeof(buffer));
            if(x>0)
            {
                buffer[x]=0;
                cout<<"client say#"<<buffer<<endl;
            }
            else if(x==0)
            {
                cout<<"clinet quit,me too!\n"<<endl;
                break;
            } 
            else break;
        }
        close(fd);
    
        int m=unlink(FIFO_FILE);
        if(m==-1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

      
    client.cc

    #include
    #include"comm.hpp"
    
    using namespace std;
    
    int main()
    {
        int fd=open(FIFO_FILE,O_WRONLY);
        if(fd<0)
        {
            perror("open");
            exit(FIFO_OPEN_ERR);
        }
    
        string line;
        while(true)
        {
            cout<<"Please Enter@";
            //cin>>line;
            getline(cin,line);
    
            write(fd,line.c_str(),line.size());
        }
        close(fd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    【亲测非常好用系统开发工具】勤哲 Excel 服务器2017 V13.0.144 最好用稳定版本,带注册,无限用户,含教程
    【多线程】定时器 Timer
    湖北省科技企业孵化器和众创空间申报奖励补贴标准,2022年认定条件汇总
    Codeforces Round #813 (Div. 2) A~C
    运放失调电压失调电流,计算输入电压信号大小,设计反向放大器
    【AI】生成模型变得简单:了解它们的工作原理和不同类型
    Linux 压缩和解压
    关于多线程的一切:原子操作
    【genius_platform软件平台开发】第七十六讲:vs预处理器定义的牛逼写法!!!!(其他组牛逼conding人员告知这么配置来取消宏定义)
    云原生爱好者周刊:M1 芯片 Mac 可以成功运行 Linux
  • 原文地址:https://blog.csdn.net/Crocodile1006/article/details/134548881