Linux进程间通信——管道通信

1. 进程间通信概述

进程间通信(Inter-Process Communication, IPC)是指在两个或者多个不同得劲进程间传递或者交换信息,通过信息的传递建立几个进程间的联系,协调一个系统中的多个进程之间的行为。
1.1 进程间通信的工作原理
进程与进程之间是相互独立的,各自运行在自己的虚拟内存中。要想在进程与进程间建立联系,需要通过内核,在内核中开辟一块缓冲区,两个进程的信息在缓冲区中进行交换或者传递。其原理如下图所示。
Linux进程间通信——管道通信_第1张图片
进程间通信原理是:进程A中的数据写入到内核中,进程B中的数据也写入到内核中,两者在内核中进行交换。交换后,进程A读取内核中的数据,进程B也读取内核中的数据,这样两个进程间交换数据的通信就完成了。两个进程通过内核建立了联系,那么交换数据、传递数据、发送事件等行为就都可以实现了。
1.2 进程间通信的主要分类
在Linux系统中,常见的进程间通信主要包括管道通信、共享内存通信、信号量通信、消息队列通信、套接口(SOCKET)通信和全双工通信。
Linux系统除了支持信号和管道外,还支持SYSV(System V)子系统中的进程间通信机制。在SYSV的IPC机制中,包括共享内存、信号量、消息队列通信。

2. 管道

管道与命名管道是最基本的IPC机制之一。管道主要用于父子或者兄弟进程间的数据读写,命名管道则可以在无关联的进程间进行沟通传递数据。
2.1 管道的基本定义
所谓管道,就像生活中的煤气管道、下水管道等传输气体和液体的工具,而在进程通信意义上的管道就是传输信息或数据的工具。以下水管道为例,当从管道一端输送水流到另一端时,只有一个传输方向,不可能同时出现两个传输方向。在Linux系统中的进程通信中,管道这个概念也是如此,某一时刻只能单一方向传递数据,不能双向传递数据,这种工作模式就叫做半双工模式。半双工工作模式的管道通信是只能从一端写数据,从另一端读数据。

2.2 管道创建和管道关闭
管道由Linux系统提供的pipe()函数创建,该函数原型为:

#include 
int pipe(int filedes[2]);

pipe()函数用于在内核中创建一个管道,该管道一端用于读取管道中的数据,另一端用于将数据写入管道。在创建一个管道后,会获得一对文件描述符,用于读取和写入,然后将参数数组filedes中的两个值传递给获取到的两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向写端。
pipe()函数调用成功,返回值为0;否则返回-1,并且设置了适当的错误返回信息。此函数只是创建了管道,要想从管道中读取数据或者向管道中写入数据,需要使用read()和write()函数来完成。当管道通信结束后,需要使用close()函数关闭管道的读写端。

2.3 pipe()函数实现管道通信
(1)在父进程中调用pipe()函数创建一个管道,产生一个文件描述符filedes[0]指向管道的读端和另一个文件描述符filedes[1]指向管道的写端。
(2)在父进程中调用fork()函数创建一个一模一样的新进程,也就是所谓的子进程。父进程的文件描述符一个指向读端,一个指向写端。子进程同理。
(3)在父进程关闭指向管道写端的文件描述符filedes[1],在子进程中,关闭指向管道读端的文件描述符filedes[0]。此时,就可以将子进程中的某个数据写入到管道,然后在父进程中,将此数据读出来。
过程如下图所示:
Linux进程间通信——管道通信_第2张图片
其程序代码如下:

#include 
#include 
#include 
#define MAXSIZE 100

int main()
{
    int fd[2], pid, line;
    char message[MAXSIZE];
    /*创建管道*/
    if(pipe(fd) == -1)
    {
	perror("create pipe failed!");
	return 1;
    }
    /*创建新进程*/
    else if((pid = vfork()) < 0)
    {
	perror("not create a new process!");
	return 1;
    }
    /*子进程*/
    else if(pid == 0)
    {
	close(fd[0]);
	printf("child process SEND message!\n");
	write(fd[1], "Hello Linux!",12); /*向文件中写入数据*/ 
    }
    else
    {
	close(fd[1]);
	printf("parent process RECEIVE message is:\n");
	line = read(fd[0], message, MAXSIZE); /*读取消息,返回消息长度*/
	write(STDOUT_FILENO,message,line); /*将消息写入终端*/
	printf("\n");
	wait(NULL);
	_exit(0);
    }
    return 0;
}

结果:
在这里插入图片描述
管道特点:

  1. 只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork(),此后父子进程之间就可以应用该管道。
  2. 一般而言,进程退出,管道释放,所以管道的生命周期跟随进程。
  3. 一般而言,内核会对管道操作进行同步与互斥
  4. 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道。
    Linux进程间通信——管道通信_第3张图片

3. 命名管道

以上介绍的管道通信的方法有很多限制。受限制之一就是两个进程必须是相关联的进程。若是没有关系的进程间通信就要用命名管道。命名管道通常被称为FIFO。它作为特殊的设备文件存在于文件系统中。因此,在进程中可以使用open()和close()函数打开和关闭命名管道。

3.1 创建一个命名管道
· 命名管道可以从命令行上创建,命令行方法是使用下面这个命令:

$ mkfifo filename

· 也可以从程序里创建,相关函数:

#include 
#include 
int mkfifo(const char* pathname, mode_t mode);

该函数的参数pathname是一个文件的路径名,是创建的一个命名管道的文件名;参数mode是指文件的权限,文件权限取决于(mode&~umask)的值。
使用mkfifo()函数创建的命名管道文件与前面介绍的管道通信相似,只是它们创建方式不同。访问命名管道文件与访问文件系统中的其他文件一样,都是需要首先打开文件,然后对文件进行读写数据。如果在命名管道文件中读取数据时,并没有其他进程向命名管道文件中写入数据,则会出现进程阻塞状态;如果在写入数据的同时,没有进程从命名管道中读取数据,也会出现进程阻塞状态。
程序如下:

#include 
#include 
#include 
#include 
#include 
#define FIFO "/root/process/hello"

int main()
{
    int fd;
    int pid;
    char r_msg[BUFSIZ];
    if((pid = mkfifo(FIFO,0777))==-1) /*创建命名管道*/
    {
	perror("create fifo channel failed!");
	return 1;
    }
    else
    printf("create success!\n");
    fd = open(FIFO, O_RDWR);  /*打开命名管道*/
    if(fd == -1)
    {
	perror("cannot open the FIFO");
	return 1;
    }
    if(write(fd,"hello world", 12) == -1)  /*写入消息*/
    {
	perror("write data error!");
	return 1;
    }
    else
    printf("write data success!\n");
    if(read(fd, r_msg, BUFSIZ) == -1)  /*读取消息*/
    {
	perror("read error!");
	return 1;
    }
    else
    printf("the receive data is:  %s\n",r_msg);
    close(fd);   /*关闭文件*/
    return 0;
}

在这里插入图片描述
通过以上代码,可以了解到使用mkfifo()函数创建命名管道并进行数据传递的过程:
(1)用mkfifo()函数创建一个命名管道,命名管道文件路径为/root/process/hello
(2)调用open()函数打开该命名管道文件,以读写的方式打开。
(3)调用write()函数向文件写入信息"hello world", 同时调用read()函数读取该文件,输出到终端。
(4)调用close()函数关闭打开的命名管道文件。

你可能感兴趣的:(Linux)