欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

进程通讯 (匿名管道)

程序员文章站 2022-05-14 21:55:06
...

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

进程间通信(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[]="[****[email protected] ~]";
	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;
	//显示出 [[email protected]
	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();
}

 

相关标签: 原创