【操作系统---14】进程间通信——管道

文章目录

    • 进程间通信:
    • 管道本质:
    • 匿名管道:
      • 实际操作(接口):
      • 具体使用:
      • 读写特性:
      • 匿名管道的简单实现:
        • 代码示例:
        • 代码注意事项:
      • 在minishell中的实现:
    • 命名管道:
      • 命名管道的简单创建:
        • 代码示例:
      • 命名管道文件的打开特性:
        • 代码示例:
        • 代码运行测试图:
    • 同步与互斥:
    • 字节流服务


进程间通信:

进程的独立性导致进程之间无法通信,操作都是自己的虚拟地址,无法访问别人的地址

system V: 管道、共享内存、消息队列、信号量


管道本质:

内核当中的一块缓冲区

		原理:让多个进程通过访问到相同的缓冲区来实现通信

		(通过系统调用的IO接口来实现---遵循一切皆文件的思想)

		半双工通信(可选方向的单向传输)

匿名管道:

只能用于具有亲缘关系的进程间通信(子进程通过复制父进程的文件描述符获取管道的操作句柄)

 		一个进程创建匿名管道,操作系统在内核创建一块缓冲区,返回两个文件描述符作为管道的操作句柄
	
		一个用于读,一个用于写

 		但是这个缓冲区在内核中没有标记

实际操作(接口):

		#include 

  		int pipe(int pipefd[2]);

		pipefd:至少具有两个int型元素的数组,创建一个管道,通过pipefd获取管道操作句柄

		pipefd[0]用于从管道读取数据

		pipefd[1]用于向管道写入数据

		成功返回0,失败返回-1

具体使用:

		先要创建亲缘关系的进程,创建管道要在创建子进程之前,否则复制不了管道的操作句柄

		子进程读,父进程写

读写特性:

		1.若管道中没有数据,则read会阻塞,直到数据被写入

		2.若管道中数据满了,则write会阻塞,直到数据被读取

		缓冲区大小为64K,每次写入1字节,总共写入65536字节,写满write就阻塞,直到read读走数据

		3.若管道的所有读端被关闭,则write会触发异常,进程退出

		4.若管道的所有写端被关闭,则read会返回0,不仅仅指的是没读到数据,还表示没人能向里边写数据

描述符被复制了,子进程和父进程的写端都被关闭colse(pipefd[1]),才能算全部写端被关闭

若所有写端被关闭,read是不会在阻塞的,每次读完数据都会返回0

若所有读端被关闭,write会出发异常,write之后的程序代码执行不到

ls | grep make 表示ls的内容写入到管道中,grep的输入从标准输入变成管道读端

匿名管道的简单实现:

		ls		浏览目录,将结果写入到标准输出

		grep make	从标准输入循环读取数据,对读取到的数据进行过滤匹配

		匿名管道实现就是创建两个进程,一个运行ls,一个运行grep

		ls的标准输出重定向到管道写入端

		grep的标准输入重定向到管道读取端

		dup2(pipefd[1],1)将标准输出重定向到管道写入端

		dup2(pipefd[0],0)将标准输入重定向到管道读取端

代码示例:

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

int  main()
{
	    int pipefd[2];
	    int ret=pipe(pipefd);

	    if(ret<0)
	    {
			perror("pipe error");
			return -1;
	    }

	    int pid1=fork();
	    
	    if(pid1==0)
	    {
			close(pipefd[0]);

			dup2(pipefd[1],1);

			//int execlp(const char *file, const char *arg, ...);
			execlp("ls","ls",NULL);
			exit(0);
	    }

	    int pid2=fork();
	    
	    if(pid2==0)
	    {
			close(pipefd[1]);

			dup2(pipefd[0],0);

			//int execlp(const char *file, const char *arg, ...);
			execlp("grep","grep","make",NULL);
			exit(0);
	    }

	    close(pipefd[0]);
	    close(pipefd[1]);

	    waitpid(pid1,NULL,0);
	    waitpid(pid2,NULL,0);

	    return 0;
}

代码注意事项:

		替换失败的话,就直接退出

		最后父进程waitpid(pid1)和waitpid(pid2)

		程序卡在那里是因为,grep要从标准输入读数据过滤,标准输入没数据,所以阻塞了

		grep默认一直读,所以最后需要关闭所有写端,pid2中执行grep的关闭

		重点:	父子进程都会获取到这个句柄,所以父进程最后也要关闭写端

单工通信:已经确定方向的单向通信

双工通信:既可以发又可以收的双向通信

在minishell中的实现:

		通过检测|,用|切割命令,通过以前的命令解析,不同的进程执行不同的功能

		创建两个子进程(父进程如果进行程序替换,执行完就退出了,就没有shell了)	

		一个execlp(ls)	另一个execlp(grep make)

		这两个通过管道通信

命名管道:

命名管道是在内核中的缓冲区有标识的,意味着所有的进程都可以通过这个标识找到这个管道的缓冲区实现通信

		可以适用于同一主机的任意进程通信

		p管道文件	mkfifo test.fifo

命名管道的标识其实是一个文件,可见于文件系统,意味着所有进程都可以通过打开文件进而访问到内核中的缓冲区

命名管道的简单创建:

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

		pathname是管道文件名称  mode是管道文件权限

		成功返回0,失败返回-1

代码示例:

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

int main()
{
	    //int mkfifo(const char *pathname, mode_t mode);
	    char *file="./testfifo.fifo";
	
	    umask(0);
	
	    int ret=mkfifo(file,0664);
	    
	    if(ret<0)
	    {
			perror("mkfifo error");
			return -1;
	    }
	    return 0;
}

命名管道文件的打开特性:

		1.若文件当前没有被以读的方式打开,则以O_WRONLY方式打开时会阻塞

		2.若文件当前没有被以写的方式打开,则以O_RDONLY方式打开时会阻塞

读写特性雷同于匿名管道

代码示例:

fifo_read.c

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

int main()
{
	    //int mkfifo(const char *pathname, mode_t mode);
	    char *file="./testfifo.fifo";
	
	    umask(0);

	    int ret=mkfifo(file,0664);
	    
	    if(ret<0)
	    {
			if(errno!=EEXIST)
			{
			    perror("mkfifo error");
			    return -1;
			}
	    }

	    int fd=open(file,O_RDONLY);

	    if(fd<0)
	    {
			perror("open error");
			return -1;
	    }

	    while(1)
	    {
			sleep(1);
			char buf[1024]={0};

			read(fd,buf,1023);
			printf("peer say:%s\n",buf);
	    }

	    close(fd);
	    return 0;
}

fifo_write.c

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

int main()
{
	    //int mkfifo(const char *pathname, mode_t mode);
	    char *file="./testfifo.fifo";

	    umask(0);

	    int ret=mkfifo(file,0664);
	    
	    if(ret<0)
	    {
			if(errno!=EEXIST)
			{
			   perror("mkfifo error");
			   return -1;
			}
	    }

	    int fd=open(file,O_WRONLY);

	    if(fd<0)
	    {
			perror("open error");
			return -1;
	    }

	    char buf[1024]={0};
	    while(1)
	    {
			printf("i say:");
			scanf("%s",buf);
			write(fd,buf,strlen(buf));
	    }

	    close(fd);

	    return 0;
}

代码运行测试图:

【操作系统---14】进程间通信——管道_第1张图片
管道的生命周期随进程(所有管道的操作句柄被关闭)

管道自带同步与互斥(管道的读写数据大小在不超过PIPE_BUF时是安全的,能保证操作的原子性–操作不会被打断)


同步与互斥:

同步:保证操作的时序合理性(我操作完了别人才能操作—写完才能读,否则读会阻塞)

互斥:我操作的时候别人操作不了,保证操作在同一时间的唯一性


字节流服务

传输灵活,但是会造成粘包问题—本质原因就是因为数据没有明显间隔

example:读取时sleep(5);同一时间发两条,会一次接收到

你可能感兴趣的:(ipc,操作系统)