进程间通信---管道

文章模块:

1.进程间通信的实质
2.管道
3.管道的特点
4.管道的四种情况
5.命名管道
6.匿名管道的代码实现
7命名管道的代码实现

一、进程间通信的实质

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

如图:

进程间通信---管道_第1张图片

二、管道

管道是一种最基本的IPC机制,由pipe函数创建。

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

pipe函数的调用过程:

1.父进程创建管道

2.创建一个子进程

3.父进程关闭读端fd[0],子进程关闭写端fd[1]

进程间通信---管道_第2张图片

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

进程间通信---管道_第3张图片

    父进程调用fork创建一个子进程,子进程继承父进程的文件描述符集,则子进程也有两个文件描述符指向同一管道的两端。

进程间通信---管道_第4张图片

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

三、管道的特点

1.只能单向通信

2.匿名管道只适用于具有血缘关系的进程间通信

3.依赖于文件系统,生命周期随进程

4.自带同步功能,防止读到垃圾数据,以互斥为前提

5.基于无格式字节流

6.管道的缓冲区是有限的(管道存在于内存中,在管道创建时,为缓冲区分配一个页面大小)

下面解释一下

为什么管道只能单向通信?

          来看下Linux的实现,数据只能单向移动的意思是FIFO,于是Linux实际中构建了一个循环队列。具体的则是,在内核申请一个缓冲区,作为pipe()操作中匿名管道的实体,缓冲区设置两个指针,一个读指针,一个写指针。并保证读指针向前移动不能超过写指针,否则唤醒写进称并让读指针睡眠,直到读满需要的字节数。同理,读指针向前移动也不能超过写指针,否则唤醒写进程并让读进程睡眠,直到写满要求的字节数。说白了,管道在操作系统内部就是一个环形队列。

pipe()返回的两个文件句柄最后指向的其实是一个inode,只不过是一个read only一个是write only。试想同时有两个进程读[或者写,假设只有两个进程]的后果。由于i_count会等于2--如果小于2则说明两个进程同时关闭了写句柄,因此会退出读函数。此时两个进程会分别认为对方才是写者而反复醒来,反复监测,然而没有数据,于是反复睡眠。如果有多个进程,两进程同时读[或者写],会造成数据混乱,因为读指针只有一个,而你不能保证读写的顺序。

所以,想要使用管道完成双向通信,只需要创建两个管道就可以了。

为什么管道只适用于有血缘关系的进程间通信?

     因为管道是通过文件描述符来控制数据读写的。所以只有血缘关系的进程才会产生指向同一个管道的文件描述符。

管道容量是多少?

     用命令 ulimit -a可以查看,----->512Bytes*8 = 4096Bytes

     也可以用程序代码验证

四、管道的四种情况

1.读端不读了,但不关闭读文件描述符,当管道写满时

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

2.写端不写了,但不关闭写文件描述符,当管道中数据读完

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

3.写端一直写,读端关闭读文件描述符

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

4.读端一直读,写端关闭写文件描述符

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

1、2体现了同步机制

五、命名管道 FIFO

可以用于两个无血缘关系的进程间通信。命名管道和管道不通的在于它提供一个路径名与之关联,以FIFO的文件形式存储于2文件系统中,命名管道是一个设备文件,因此,即使进程与创建命名管道的进程之间无任何血缘关系,只要可以访问该路径,就能够通过命名管道相互通信。命名管道总是按照先进先出的原则工作。

六、代码实现--------------匿名管道

mypipe

/*************************************************************************
	> File Name: mypipe.c
	> Author: ZX
	> Mail: [email protected] 
	> Created Time: Sat 13 May 2017 06:49:35 PM PDT
 ************************************************************************/

#include 
#include 
#include 

int main()
{
	int fd[2];

	int ret = pipe(fd);
	if(ret < 0)
	{
		perror("pipe failed");
	}
	pid_t id = fork();
	if(id == 0)
	{
		//child
		close(fd[0]);//write -- close read
		int i = 0;
		char* msg = "hello world!";
		while(i < 10)
		{
			ssize_t s = write(fd[1], msg, strlen(msg));
			printf("i am writing! %d\n",i++);
			fflush(stdout);
		}
	}
	else
	{
		//father
	//	close(fd[1]);
	//	char buf[1024];
	//	while(1)
	//	{
	//		ssize_t s = read(fd[0], buf, sizeof(buf));
	//		if(s > 0)
	//		{
	//			buf[s] = 0;
	//			printf("father read: %s\n", buf);
	//		}
	//		if(s == 0)
	//		{
	//			printf("end of file!\n");
	//		}
	//	}

		int status = 0;
		waitpid(id, &status, 0);
	}

	return 0;
}


七、代码实现--------命名管道

server

/*************************************************************************
	> File Name: server.c
	> Author: ZX
	> Mail: [email protected] 
	> Created Time: Sat 13 May 2017 07:26:10 PM PDT
 ************************************************************************/

#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
	int ret = mkfifo("./mypipe", S_IFIFO | 0666);
	if(ret < 0)
	{
		perror("mkfifo failed!");
		return 1;
	}

	int fd = open("./mypipe", O_RDONLY);
	if(fd < 0)
	{
		perror("open failed!");
		return 2;
	}
	else
	{
		while(1)
		{
			char buf[1024];
			memset(buf, '\0', sizeof(buf));
			ssize_t s = read(fd, buf, sizeof(buf));
			if(s > 0)
			{
				buf[s-1] = 0;
				printf("server read:%s", buf);
			}
			else if(s == 0)
			{
				printf("client closed!server is closing!\n");
				break;
			}
		}
	}
	return 0;
}
/*************************************************************************
	> File Name: client.c
	> Author: ZX
	> Mail: [email protected] 
	> Created Time: Sat 13 May 2017 07:26:10 PM PDT
 ************************************************************************/

#include 
#include 
#include 
#include 
#include 
#include 

int main()
{
	int fd = open("./mypipe", O_WRONLY);
	if(fd < 0)
	{
		perror("open failed!");
		return 1;
	}
	else
	{
		while(1)
		{
			printf("Please Enter: ");
			fflush(stdout);
			char msg[1024];
			memset(msg, '\0', sizeof(msg)-1);
			ssize_t s = read(0, msg, sizeof(msg));
			write(fd, msg, sizeof(msg));
		}
	}
	return 0;
}










你可能感兴趣的:(Linux,进程间通信,Linux基础知识)