Linus进程间通信(一)管道、命名管道的原理及实现

进程间通信

每个进程各自有不同额用户地址空间,任何一个进程的全局变量在另一个进程中多看不到,所以进程间要交换数据必须通过内核,在内核中开辟一段缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess
Communication)。

实现进程间通信的方式有:管道(pipe),命名管道(fifo),消息队列,信号量,共享内存...

管道

:是一种IPC机制,由pipe函数创建:

#include <unistd.h>

int pipe(int filedes[2]);

调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通 过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道 的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开
的问件,通过read(filedes[0]);或者write(filedes[1]);向这个问件读写数据其实是在读写内核缓 冲区。pipe函数调用成功返回0,调用失败返回-1。

1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。

2. 父进程调用fork创建子进程,那么子进程也有两个蚊件描述符指向同一管道。

3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

代码演示:

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

int main()
{
	int _pipe[2];
	int ret = pipe(_pipe);
	if(ret == -1 )
	{
		printf("create pipe failed!\n");
		return 2;
	}
	pid_t id = fork();
	if(id < 0)
	{
		printf("fork error!\n");
		return 0;
	}
	else if(id == 0) //child
	{
		close(_pipe[1]);
		char mesg[1000];
		int i = 0;
		while(i < 5)
		{
			memset(mesg, '\0', sizeof(mesg));
			read(_pipe[0], mesg, sizeof(mesg));
			printf("%s\n",mesg);
			i++;
		}
	}
	else//father
	{
		close(_pipe[0]);
		int j = 0;
		while(j < 5)
		{
			char *_mesg_c = "i am you father!!!";
			write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
			sleep(2);
			j++;
		}
	}
	return 1;
}

使用管道有一些限制:

两个进程通过一个管道只能实现单向通信。比如上面的例子,父进程写子进程读,如果有时候也需要子进程写父进程读,就必须另开一个管道。

管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间 通信,也可以用进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信, 总之需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。 也就是说,管道通信是需要进程之间有关系。

使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):

1.  如果所有指向管道写端的文件描述符都关闭了(管道写端的引用计数等于0),而仍然有进程 从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。

测试代码:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>

int main()
{
	int _pipe[2];
	int ret = pipe(_pipe);
	if(ret == -1)
	{
		printf("create pipe error! errno code is: %d\n",errno);
		return 1;
	}
	pid_t id = fork();
	if(id < 0)
	{
		printf("fork error!\n");
		return 2;
	}
	else if(id == 0)//child
	{
		close(_pipe[0]);
		int i = 0;
		char *_mesg_c = NULL;
		while(i < 10)
		{
			_mesg_c = "i am child!";
			write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
			sleep(1);
			i++;
		}
		close(_pipe[1]);
	}
	else//father
	{
		close(_pipe[1]);
		char _mesg[1000];
		int j = 0;
		while(j < 100)
		{
			memset(_mesg, '\0', sizeof(_mesg));
			int ret = read(_pipe[0], _mesg, sizeof(_mesg));
			printf("%s, code is: %d\n",_mesg,ret);
			j++;
		}
		if(waitpid(id, NULL, 0) < 0)
			return 3;
		return 0;
	}

}

2、如果有指向管道写端的文件描述符没关闭(管道写端的引用计数大于0),而持有管道写进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读
取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。

代码测试:

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>

int main()
{
	int _pipe[2];
	int ret = pipe(_pipe);
	if(ret == -1)
	{
		printf("create pipe error! errno code is: %d\n",errno);
		return 1;
	}
	pid_t id = fork();
	if(id < 0)
	{
		printf("fork error!\n");
		return 2;
	}
	else if(id == 0)//child
	{
		close(_pipe[0]);
		int i = 0;
		char *_mesg_c = NULL;
		while(i < 20)
		{
			if(i < 10)
			{
				_mesg_c = "i am child!";
				write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
			}
			sleep(1);
			i++;
			if(i >= 14)
			{
				_mesg_c = "i am child!";
				write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
			}
		}
		close(_pipe[1]);
	}
	else//father
	{
		close(_pipe[1]);
		char _mesg[1000];
		int j = 0;
		while(j < 20)
		{
			memset(_mesg, '\0', sizeof(_mesg));
			int ret = read(_pipe[0], _mesg, sizeof(_mesg));
			printf("%s, code is: %d\n",_mesg,ret);
			j++;
		}
		if(waitpid(id, NULL, 0) < 0)
			return 3;
	}
<span style="white-space:pre">	</span>return 0;
}

3.  如果所有指向管道读端的文件描述符都关闭了(管道读端的引用计数等于0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。

#include<stdio.h>
#include<unistd.h>
#include<errno.h>
#include<string.h>
#include<sys/wait.h>

int main()
{
	int _pipe[2];
	int ret = pipe(_pipe);
	if(ret == -1)
	{
		printf("create pipe error! errno code is: %d\n",errno);
		return 1;
	}
	pid_t id = fork();
	if(id < 0)
	{
		printf("fork error!\n");
		return 2;
	}
	else if(id == 0)//child
	{
		close(_pipe[0]);
		int i = 0;
		char *_mesg_c = NULL;
		while(i < 20)
		{
			if(i < 10)
			{
				_mesg_c = "i am child!";
				write(_pipe[1], _mesg_c, strlen(_mesg_c)+1);
			}
			sleep(1);
			i++;
		}
	}
	else//father
	{
		close(_pipe[1]);
		char _mesg[1000];
		int j = 0;
		while(j < 3)
		{
			memset(_mesg, '\0', sizeof(_mesg));
			int ret = read(_pipe[0], _mesg, sizeof(_mesg));
			printf("%s, code is: %d\n",_mesg,ret);
			j++;
		}
		close(_pipe[0]);
		sleep(10);
		if(waitpid(id, NULL, 0) < 0)
			return 3;
	}
	return 0;
}

4.  如果有指向管道读端的文件描述符没关闭(管道读端的引用计数大于0),而持有管道读端 进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再 次write会阻塞,直到管道中有空位置了才写入数据并返回。

命名管道(FIFO)

文件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的 路径名来标识一个IPC通道。

命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似。

创建命名管道

我们可以使用两下函数之一来创建一个命名管道,他们的原型如下:

#include <sys/types.h>

#include <sys/stat.h>

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

int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0);

这两个函数都能创建一个FIFO文件,注意是创建一个真实存在于文件系统中的文件,filename指定了文件名,而mode则指定了文件的读写权限。mknod是比较老的函数,而使用mkfifo函数更加简单和规范,所以建议在可能的情况下,尽量使用mkfifo而不是mknod。

//fifo_write.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100

int main()
{
	int ret = mkfifo(_PATH_, 0777);
	if( ret < -1)
	{
		printf(" mkfile error!\n");
		return 1;
	}
	int fd = open(_PATH_, O_WRONLY);
	if(fd < 0)
	{
		printf("open error!\n");
	}
	char buf[_SIZE_];
	memset(buf, '\0', sizeof(buf));
	while(1)
	{
		fflush(stdin);
		scanf("%s",buf);
		int ret = write(fd, buf, sizeof(buf)+1);
		if(ret < 0 )
		{
			printf("write error!\n");
			break;
		}
		if(strncmp(buf, "quit", 4) == 0)
		{
			break;
		}
		close(fd);
		return 0;
	}
}

//fifo_read.c

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>

#define _PATH_ "/tmp/file.tmp"
#define _SIZE_ 100

int main()
{
	int fd = open(_PATH_, O_RDONLY);
	if(fd < 0)
	{
		printf("open file error!\n");
		return 1;
	}
	char buf[_SIZE_];
	memset(buf, '\0', sizeof(buf));
	while(1)
	{
		fflush(stdout);
		int ret = read(fd, buf, sizeof(buf));
		if(ret <= 0 )
		{
			printf("read end or error!\n");
			break;
		}
		printf("%s\n",buf);
	//	fflush(stdout);
		//sleep(2);
		if(strncmp(buf, "quit", 4) == 0)
		{
			break;
		}
	}
	close(fd);
	return 0;
}

时间: 2024-08-06 23:59:54

Linus进程间通信(一)管道、命名管道的原理及实现的相关文章

进程间通信二(命名管道)

在前一篇文章中,我们看到了如何使用匿名管道来在进程之间传递数据,这个方式有一个缺陷,就是这些进程必须由一个共同的祖先进程启动,这在不相关的的进程之间交换数据带来了不便.而另一种通信方式——命名管道,可以解决不相关进程间的通信问题. 1.什么是命名管道?命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似. 由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与文件操作非常的统一,也使它的

邮槽 匿名管道 命名管道 剪贴板 进程通讯 转自http://www.cnblogs.com/kzloser/archive/2012/11/04/2753367.html#

邮槽 通信流程: 服务器 客户端 注意: 邮槽是基于广播通信体系设计出来的,它采用无连接的不可靠的数据传输 邮槽可以实现一对多的单向通信,我们可以利用这个特点编写一个网络会议通知系统,而且实现这一的系统所需要编写的代码非常少.如果读者是项目经理,就可以给你手下每一位员工的机器上安装上这个系统中的邮槽服务器端程序,在你自己的机器上安装油槽的客户端程序,这样,当你想通知员工开会,就可以通过自己安装的邮槽客户端程序.将开会这个消息发送出去,因为机器上都安装了邮槽服务器端的程序,所以他们都能同时收到你发

Linux进程间通信 -- 使用命名管道

在前一篇文章—— Linux进程间通信 -- 使用匿名管道 中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关的的进程之间交换数据带来了不方便.这里将会介绍进程的另一种通信方式——命名管道,来解决不相关进程间的通信问题. 一.什么是命名管道 命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似. 由于Linux中所有

进程间通信——命名管道

概念 管道一个不足之处是没有名字,因此只能用于具有亲缘关系的进程间通信,命名管道(named pipe或FIFO)解决了这一问题. FIFO提供一个路径名与之关联,以FIFO文件的形式存储于文件系统中.文件系统中路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC通道. 对文件系统来说,匿名管道(管道)是不可见的,它的作用仅限于在父进程和子进程两个进程间进行通信.而命名管道是一个可见的文件,因此,他可以用于任意两个进程间进行通信,不管这两个进程是不是父子进程,也不管这两

Linux下进程间通信之命名管道(FIFO)

匿名管道的一个不足之处是没有名字,因此,只能用于具有亲缘关系的进程间通信.在命名管道(FIFO)提出后,该限制得到了克服.FIFO不同于pipe在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中.命名管道是一个设备文件,因此,即使进程与创建FIFO的进程不存在亲缘关系,只要可以访问该路径,就能够相互通信. FIFO总是按照先进先出的原则工作,第一个被写入的数据将首先从管道中读出. Linux下有两种方式创建FIFO,一是在shell下交互的建立一个命名管道,二是在程序中使用系统函

使用命名管道实现进程间通信

创建命名管道 命名管道常常用于应用程序之间的通迅,由于不需要进行序列化和反序列化操作,效率是非常高的.相比TCP通信方式,效率更高,但比共享内存要低点.命名管道可以在本地机器或者局域网内机器实现进程间通信,所以是最佳的通信方式. 创建一个NamedPipeServerStream: NamedPipeServerStream pipeServer = new NamedPipeServerStream(_pipName, PipeDirection.InOut, 10); 这里表示命名管道服务器

Linux进程间通信(IPC)编程实践(二) FIFO命名管道

在前一篇文章中,我们讲解了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关的的进程之间交换数据带来了不方便.这里将会介绍进程的另一种通信方式--命名管道,来解决不相关进程间的通信问题. 什么是命名管道 命名管道也被称为FIFO文件,它是一种特殊类型的文件,它在文件系统中以文件名的形式存在,但是它的行为却和之前所讲的没有名字的管道(匿名管道)类似. 由于Linux中所有的事物都可被视为文件,所以对命名管道的使用也就变得与

Linux管道pipe的实现原理

管道: 管道是进程间通信的主要手段之一.一个管道实际上就是个只存在于内存中的文件,对这个文件的操作要通过两个已经打开文件进行,它们分别代表管道的两端.管道是一种特殊的文件,它不属于某一种文件系统,而是一种独立的文件系统,有其自己的数据结构.根据管道的适用范围将其分为:无名管道和命名管道. ●     无名管道 主要用于父进程与子进程之间,或者两个兄弟进程之间.在linux系统中可以通过系统调用建立起一个单向的通信管道,且这种关系只能由父进程来建立.因此,每个管道都是单向的,当需要双向通信时就需要

IPC——命名管道

Linux进程间通信——使用命名管道 转载:http://blog.csdn.net/ljianhui/article/details/10202699 在前一篇文章——Linux进程间通信——使用匿名管道中,我们看到了如何使用匿名管道来在进程之间传递数据,同时也看到了这个方式的一个缺陷,就是这些进程都由一个共同的祖先进程启动,这给我们在不相关的的进程之间交换数据带来了不方便.这里将会介绍进程的另一种通信方式——命名管道,来解决不相关进程间的通信问题. 一.什么是命名管道 命名管道也被称为FIF