进程通讯 (匿名管道)

版权声明:请在征得作者同意的情况下,可以进行非盈利性引用。引用请注明出处:“作者:慕华思弦 转载地址” 字样,以尊重作者的劳动成果,并保持良好的版权意识。 https://blog.csdn.net/Superman___007/article/details/82766067

程序:是有限指令的集合
进程:将程序执行一次的过程
注:分配资源的单位

进程间通信(IPC):system IPC V由于进程间彼此隔离,故有下列方法来使用进程间通信。

1、管道:匿名、命令管道
    Shell命令:  管道  command1 | command2
    系统API:  
        int pipe(int fd[2]);
           返回值:成功与否的状态
           fd文件的描述符
           pipe2
           注: 管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:            
            1. 其本质是一个伪文件(实为内核缓冲区)
            2.由两个文件描述符引用,一个表示读端,一个表示写端。
            3. 规定数据从管道的写端流入管道,从读端流出。
            管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
            管道的局限性:
            ① 数据自己读不能自己写。
            ② 数据一旦被读走,便不在管道中存在,不可反复读取。
            ③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
            ④ 只能在有公共祖先的进程间使用管道。
            常见的通信方式有,单工通信、半双工通信、全双工通信。    

2、管道的读写行为,使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
        1. 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),
           而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后
           再次read会返回0,就像读到文件末尾一样。
        2. 如果有指向管道写端的文件描述符关闭(管道写端引用计数大于0),
           而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据
           那么管道中剩余的数据都被读取后,再次read会阻塞直到管道中有数据可读
           了才读取数据并返回。
        3. 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),
           这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会
           导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。
           具体方法信号章节详细介绍。
        4. 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而
           持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据
           ,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数
           据并返回。

总结:

        ① 读管道:

        1. 管道中有数据,read返回实际读到的字节数。
        2. 管道中无数据:
        (1) 管道写端被全部关闭,read返回0 (好像读到文件结尾) 
        (2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)

       ② 写管道:

        1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
        2. 管道读端没有全部关闭:
        (1) 管道已满,write阻塞。
        (2) 管道未满,write将数据写入,并返回实际写入的字节数。

work: 统计ls -l有多少行
         ls -l   | wc -l 
         command1 | command2 | command3 
注:【指令1】正确输出1,作为【指令2】的输入描述符0 然后【指令2】的输出作为 
       【指令3】的输入 ,【指令3】输出就会直接显示在屏幕上面了。
         通过管道之后【指令1】和【指令2】的正确输出不显示在屏幕上面 

实现如上功能 : 

#include<iostream>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
using namespace std;
int main()
{
	int fd[2];//fd[0]读  fd[1]写
	if(pipe(fd)==-1)	
	{
		perror("pipe");
		return -1;
	}
	int pid=fork();
	if(pid<0)
	{
		perror("pipe");
		exit(-1);
	}
	else if(0==pid)//子进程
	{
		dup2(fd[0],0);//重定向---读
		close(fd[0]);
		close(fd[1]);	
		execlp("wc","wc","-l",NULL);		
		fprintf(stderr,"error execute wc\n");
		exit(0);
	}//父进程
	dup2(fd[1],1);//关闭描述符1,将写入端fd[1]复制给1
	close(fd[0]);
	close(fd[1]);
	execlp("ls","ls","-l","/",NULL);//读屏幕上读取---屏幕已经重定向为管道
	fprintf(stderr,"error execute ls\n");
	wait(NULL);
	exit(0);
}

   管道间的读写通讯实现 .

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<limits.h>
using namespace std;
/*
#define PIPE_BUF 4096
char buf[PIPE_BUF];
*/
int main()
{
	//在创建子进程之前:创建管道
	int fd[2]={-1,-1};
	//IPC1:匿名管道
	pipe(fd);//返回两个文件描述符fd[0]只读 fd[1]只写

	//查看匿名管道系统定义长度
	cout<<PIPE_BUF<<endl;

	int pid=fork();
	if(pid>0)
	{
		char buf[100];
		cin>>buf;
		//关闭读取
		close(fd[0]);
		//重定向
		dup2(fd[1],1);
		//写入
		write(fd[1],buf,strlen(buf));
		cout<<"父进程写入完成"<<endl;
		//关闭写入段
		close(fd[1]);
		//等待子进程结束
		wait(NULL);
	}
	else if(pid==0)
	{
		char buf1;
		//关闭写入
		close(fd[1]);
		//重定向
		dup2(fd[0],0);
		while((read(fd[0],&buf1,1))>0)
			printf("%c",buf1);
		cout<<endl;
		
		close(fd[0]);
	}
}

   shell 命令 实现管道通讯的功能 , 实现解析字符串 ,  ls -l  |  grep data.data .

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

#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
using namespace std;
int main()
{
	char title[]="[****CHH@CHH ~]";
	char cmd[255]="";
	int pid;
	char* matter[100]={NULL};	//100个地址

	int fd[2]={-1,-1};
	if(pipe(fd)==-1)
	{
		fprintf(stderr,"pipe fail\n");
		exit(-1);
	}
	int i=0;
	//父进程
	while(1)
	{
		cout<<title;	//输出标题
		gets(cmd);	//输入
		pid=fork();
		if(pid>0)
		{
			wait(NULL);
		}
		else if(0==pid)
		{
			//char a[]=" ";
			//解析字符串
			matter[i]=strtok(cmd," ");
			while((matter[++i]=strtok(NULL," "))!=NULL)
			{
				//cout<<matter[i]<<endl;
				if('|'==*(matter[i]))
				{
					matter[i]=NULL;
				}
			}
			dup2(fd[1],1);
			close(fd[1]);//两个指向管道写
			close(fd[0]);//关闭读
			//替换子进程信息
			execvp(matter[0],matter);
			fprintf(stderr,"execute ls fail\n");
			exit(-1);
		}
		//解析字符串
		//cout<<"2***"<<endl;
		matter[i]=strtok(cmd," ");
		while((matter[++i]=strtok(NULL," "))!=NULL)
		{
		}
		dup2(fd[0],0);
		close(fd[0]);//读端两个,所以关闭一次 0
		close(fd[1]);//关闭写端
		execvp(matter[3],matter+3);
		//execlp("wc","wc","-l",NULL);
		fprintf(stderr,"execute ls fail\n");
		exit(-1);
	}
	return 0;
}

   制作一个简单的 shell 终端 , 父进程来要求子进程去执行shell 命令,当子进程结束,父进程继承下一个shell .

    使用到的 API:
   1、获取用户的信息:
       struct getpwuid()
   2、获取电脑主机电脑的别名
       gethostname
   3、每一个进程都有一个工作目录    
       getcwd
   4、获取当前进程所在用户的UID
       getuid

#include<iostream>
#include<sys/types.h>
#include<pwd.h>
#include<string.h>
#include<sys/wait.h>
#include<stdio.h>

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

#include<stdlib.h>
using namespace std;

/*显示出用户的标题名*/
bool title(void)
{
	char name[100]="";
	char cwd[256]="";
	//获取用户名
	struct passwd* pd=getpwuid( getuid() );
	if(NULL==pd)
		return false;
	//显示出 [CHH@
	cout<<"["<<pd->pw_name<<"@";
	//显示出主机名
	gethostname(name,99);
	cout<<name<<" ";
	//显示工作目录
	getcwd(cwd,255);
	//判断位置
	if(!strcmp(cwd,pd->pw_dir))
		cout<<"~]$ ";
	else//绝对路径
	{
		//cout<<cwd<<"]$"<<endl;
		//查找位置:最后一个/
		char* pos=strrchr(cwd,'/');
		if(NULL!=pos &&*(pos+1)=='\0')
			cout<<pos<<"]$ ";
		else if(NULL!=pos)
			cout<<pos+1<<"]$ ";
	}
	return true;
}

//解析字符串:
void trans(char cmd[],char* argv[])
{
	int i=0;
	//先记录首地址
	argv[i]=cmd;
	while((*cmd)!='\0')
	{
		//遇到空格,说一个单词结束了
		if(' '==*cmd)
		{
			*cmd='\0';
			argv[++i]=++cmd;
		}
		else
		{
			++cmd;
		}
	}
	argv[i+1]=NULL;
}

/*输入命令,让子进程去执行shell,父进程解释命令*/
void shell()
{
	char cmd[255]="";
	int pid=-1;
	int ilen=0;
	char* argv[64]={NULL};
	//char* pch=NULL;
	int i=0;
	
	while(1)
	{
		title();
		//让系统完成输出
		cout<<endl;
		//输出:

		//输入STDIN_FILENO
		ilen=read(STDIN_FILENO,cmd,sizeof(cmd));
		if(ilen>0)
			//添加结尾  因为后面会  ls -l\n 有个换行,需要去掉.
			cmd[ilen-1]='\0';
		//解析字符串:execv函数
		trans(cmd,argv);

		pid=fork();
		if(pid>0)
		{
			wait(NULL);
		}
		else if(0==pid)
		{
			//cout<<argv[0]<<argv[1]<<endl;
			//argv[i]=strtok(cmd," ");
			while(argv[i]!=NULL)
			{
				if(!strcmp(argv[i],">"))
				{
					//cout<<i<<endl;
					//cout<<argv[i]<<endl;
					close(1);
					int fd=open("data",O_CREAT|O_TRUNC|O_WRONLY,0666);
					argv[i]=NULL;
					execvp(argv[0],argv);
				}
				else if(!strcmp(argv[i],">>"))
				{
					close(1);
					int fd=open("data",O_CREAT|O_WRONLY|O_APPEND,0644);
					argv[i]=NULL;
					execvp(argv[0],argv);
				}
				else if(!strcmp(argv[i],"|"))
				{
					int j=0;

					int fd[2]={-1,-1};
					if(pipe(fd)==-1)
					{
						fprintf(stderr,"pipe fail\n");
						exit(-1);
					}
					//cout<<fd[1]<<endl;    // 4
					//cout<<argv[i+1]<<endl;  // |   i+1=wc
					//cout<<i<<endl;	   // 2
					while(1)
					{
					//子进程
					int pid2=fork();
					if(pid2>0)
					{
						wait(NULL);
					}
					else if(0==pid2)
					{
						while(argv[++j]!=NULL)
						{
							//cout<<j<<endl;	// 1 2 3 4
							//cout<<argv[j]<<endl;	// -l | wc -l
							if(!strcmp(argv[j],"|"))
							{
							//cout<<argv[j]<<endl;	// -l
							argv[j]=NULL;
							}
						}
						dup2(fd[1],1);
						close(fd[1]);//两个指向管道写
						close(fd[0]);//关闭读
						//替换子进程信息
						execvp(argv[0],argv);
						fprintf(stderr,"execute ls fail\n");
						exit(-1);
					}
					//argv[i+3]=NULL;
					while(argv[++j]!=NULL)
					//cout<<argv[3]<<endl;	// wc
					{
						//cout<<argv[j]<<endl;
					}
					//strcpy(argv[j],"1");
					dup2(fd[0],0);
					close(fd[0]);//读端两个,所以关闭一次 0
					close(fd[1]);//关闭写端
					execvp(argv[3],argv+3);
					//execlp("wc","wc","-l",NULL);
					fprintf(stderr,"execute ls fail\n");
					exit(-1);
					}
				}
				i++;
			}
			if(-1==execvp(argv[0],argv))
				perror("execut execvp fail");
		}
	}
}

//制作一个简单的shell
//父进程来要求子进程去执行shell 命令,当子进程结束,父进程继承下一个shell
int main()
{
	shell();
}

猜你喜欢

转载自blog.csdn.net/Superman___007/article/details/82766067