程序通訊 (匿名管道)
程式:是有限指令的集合
程序:將程式執行一次的過程
注:分配資源的單位
程序間通訊(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. 如果有指向管道
而持有管道寫端的程序也沒有向管道中寫資料,這時有程序從管道讀端讀資料,
那麼管道中剩餘的資料都被讀取後,再次read會阻塞,直到管道中有資料可讀
了才讀取資料並返回。
3. 如果所有指向管道讀端的檔案描述符都關閉了
這時有程序向管道的寫端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();
}