进程通讯 (匿名管道)
程序:是有限指令的集合
进程:将程序执行一次的过程
注:分配资源的单位
进程间通信(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();
}