Linux进程通信——管道


进程间通信(IPC:Inner Proceeding Communication)

进程是操作系统实现程序独占系统运行的假象的方法,是对处理器、主存、I/O设备的抽象表示。每个进程都是一个独立的资源管理单元,每个进程所看到的是自己独占使用系统的假象,因此各个进程之间是不能够直接的访问对方进程的资源的,不同的进程之间进行信息交互需要借助操作系统提供的特殊的进程通信机制。

进程之间的通信,从物理上分,可以分为同主机的进程之间的通信和不同主机间的进程之间的通信。从通信内容方式上分,可以分为数据交互、同步通信、异步通信。

Linux系统进程之间的通信方式大致如下图所示

\

集合上述两种从物理和内容方式的划分,可以这样理解上图:

(1)同主机进程间数据交互机制:无名管道(PIPE)、有名管道(FIFO)、消息队列(Message Queue)和共享内存(Shared Memory)。

(2)同主机进程间同步通信机制:信号量(Semaphore)。

(3)同主机进程间异步通信机制:信号(Signal)。

(4)不同主机间进程数据交互机制:套接字(Socket)、远程调用RPC(Remote Procedure Call)。

管道通信

管道又可以分为无名管道命名管道,两者的用途是不一样的。

无名管道PIPE:主要用于具有亲缘关系的进程之间的通信,无名管道的通信是单向的,只能由一段到另外一段;无名管道是临时性的,完成通信后将自动消失。一般采用先创建无名管道,再创建子进程,使子进程继承父进程的管道文件描述符,从而实现父子进程间的通信;在非亲缘关系管道之间,如果想利用无名管道进行通信,则需要借助另外的文件描述符传递机制。

有名管道FIFO:有名管道是一个实际存在的特殊文件,利用有名管道可以实现同主机任意进程之间的数据交互。

Shell中的无名管道

在Shell命令行中使用无名管道是到过管道符“ | ” 实现的,如下面的shell命令将cat命令的输出通过管道传递给grep命令的输入。

\

无名管道是一种特殊的文件,这就意味着你可以向操作文件一样操作无名管道,无名管道在内核中对应的是一段特殊的内存空间,这段内存空间由操作系统进行管理,对用户是不可见的,在用户空间的应用程序中只能通过系统调用来访问它。在这段内存空间中以循环队列的方式来临时存储一个进程发往另外一个进程的信息,并且在通信完成后就会自动释放相应的空间。

Linux C编程中的无名管道

1、创建无名管道

创建无名管道需要使用pipe(int _pipedes[2])函数,这个函数的参数是一个含有两个元素的整型数组,如果执行成功,这个整形数组将分别存储无名管道读端的文件描述符和写端的文件描述符,利用这两个读、写文件描述符,我们可以像读写文件一样,操作无名管道的读写。如pipe( )函数调用失败将返回-1。

2、读写无名管道

下面的程序是在一个程序中完成,即一个进程既充当读进程又充当写进程。

#include<stdio.h>
#include<unistd.h>
#include<string.h>

int main()
{
        int p[2];
        char *str = "HelloWorld";  //待写入无名管道的字符串
        char buf[128]; //读取无名管道时的缓冲区
        memset(buf,'\0',128); //清空缓冲区

        if(pipe(p) == -1)
        {
                printf("function pipe() calls failed.");
                return -1;
        }

        write(p[1],str,strlen(str));  //写无名管道

        read(p[0],buf,strlen(str));  //读无名管道

        printf("%s\n",buf); //打印读取的内容

        return 0;

}
下面的程序模拟了具有亲缘关系的父子进程之间的通信,子进程向父进程发送字符串str,遵循上面的先创建管道再创建子进程,这样子进程会复制父进程的管道文件描述符。  
#include<stdio.h>
#include<unistd.h>
#include<string.h>

int main()
{
        int p[2];
        int pid;
        char *str = "HelloWorld";
        char buf[128];
        memset(buf,'\0',128);

        if(pipe(p) == -1)
        {
                printf("function pipe() calls failed.");
                return -1;
        }

        if((pid=fork()) == -1)  //创建一个子进程
        {
                printf("function fork() calls failed.\n");
                return -1;
        }
        else if(pid == 0)  //在子进程中
        {
                printf("In sub : pid=%d\n",getpid());
                write(p[1],str,strlen(str)); //向无名管道中写入str
        }else {  //在父进程中
                printf("In father : pid=%d\n",getpid());
                read(p[0],buf,strlen(str));  //读取无名管道
                printf("In father : buf=%s\n",buf);
        }
}
上述程序的输出结果:

\

注意:读写无名管道的read和write方法默认是阻塞式的读写,当读取管道,但是这时管道中没有数据,也没有进程向写端写数据的时候,读操作会发生阻塞;同理如果,向管道中写数据,但是这时管道已经满了(管道是一个特殊的内存空间文件,默认大小是PIPE_BUF,一般是4096字节),写操作将发生阻塞。另外如果,写管道时,读端已经关闭,那么写操作将收到SIGPIPE信号,write函数返回-1.

命名管道

命名管道是实际存在的一种文件,在shell中,我们可以使用mknod命令创建一个命名管道,下面是mknod的用法,并且利用mknod创建了一个p_file文件,可以发现这个文件的文件类型是p

\

Linux C编程中的命名管道

1、创建命名管道

在编程中,可以使用mkfifo(char *path, _mode_t _mode)函数创建一个命名管道,mkfifo有两个参数,第一个是指定要创建的命名管道的名字,第二个是生成的命名管道文件的模式。

2、读写命名管道

和无名管道一样,命名管道的实质仍然是一段内核空间管理的内存,但是在使用write和read之前需要先使用open函数打开命名管道文件

下面的程序是两个没有亲缘关系的进程之间利用命名管道进行通信

写进程源码如下

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>

int main()

{	
	int fd;
	int res;
	char *str = "HelloWorld";
	res = mkfifo("myfifo",0766);

	if(res != 0)
	{
		printf("function nkfifo() calls failed.");
		return -1;
	}

	fd = open("myfifo",O_WRONLY); //create fifo file named "myfifo"
	if(fd == -1)
	{
		printf("function open() calls failed.");
		return -1;
	}
	printf("This proceeding id =%d, fifo file file ID=%d\n",getpid(),fd);
	
	res = write(fd,str,strlen(str));  //write to fifo file, This will be blocking if no read
	if(res == -1)
	{
		printf("function write() calls failed.");
		return -1;
	}
	printf("write ok.\n");
	close(fd);
	return 0;
	
}
读取管道程序的源码如下
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>

int main()
{
	int fd;
	int res;
	char buf[4096];
	memset(buf,'\0',4096);
	
	fd = open("myfifo",O_RDONLY);  //open fifo file
	if(fd == -1)
	{
		printf("function open() calls failed.");
		return -1;
	}

	printf("This proceeding id=%d, fd=%d\n",getpid(),fd);

	read(fd,buf,sizeof(buf)); //read fifo into buffer, This is a blocking method

	printf("read : buf=%s\n",buf);

	close(fd);
	return 0;

}
需要先运行写进程,因为需要创建管道文件,write方法将被阻塞,因为没有读关联到管道;继续在另外的终端,运行读取进程,读进程可以读取到写进程发送的数据,并且写进程的阻塞将会解除。

\

总结:

无名管道主要用于具有亲缘关系的父子进程之间的通信,是临时性的,需要先创建管道,再创建子进程;管道都是单向的,若要实现双向通信,则需要两个管道。命名管道是实际存在的文件,使用前需要先打开,管道默认的read和write操作都是阻塞式的。

相关内容