第十章 I/O重定向和管道
0.摘要
概念與技巧
-I/O重定向:概念與原因
-標準輸入,輸出和標準錯誤的定義
-重定向標準I/O到檔案
-使用fork來為其他程式重定向
-管道(Pipe)
-建立管道後呼叫fork
相關的系統呼叫與函式
-dup,dup2
-pipe
1.shell程式設計
首先將介紹編寫shell指令碼時的I/O重定向和管道起的作用.然後,本章將介紹作業系統中對I/O重定向的支援.最後,寫一個程式來改變程序的輸入和輸出流..
utmp函式列出使用者列表
2.標準I/O與重定向的若干概念
shell中的三種流分別位如下三種:
1.標準輸入–需要處理的資料流
2.標準輸出–結果資料流
3.標準錯誤輸出–錯誤訊息流
2.1 3個標準檔案描述符
stdin:0
stdout:1
stderr:2
2.2預設的連線:tty
在unix和linux中,shell預設連結著stdin,stdout,stderr三個檔案。很多程式沒有輸入,只是把正確的輸出寫到檔案描述符位1,並且把錯誤訊息寫到檔案描述符2中.
2.3重定向I/O的是shell來操作而不是程式
當使用輸出重定向標誌,命令cmd>filename是由shell將檔案描述符1定位到檔案.把原有的連結打斷.
本章的三個主要的工作:
1.who>userlist 將stdout連線到一個檔案
2.sort
3.如何將stdin定向到檔案
程序是從檔案描述符讀取資料的.並且由將標準輸入重定向到可以從檔案讀入資料.
3.1.方法1:close then open
開始的時候,0連線輸入,1連線輸出,2連接出錯.三種標準流是被連線到終端裝置上的.通過檔案close可以指定標準的輸入輸出關閉。如果呼叫close來關閉程式的stdin(0),之後開啟open一個指定檔案,則它會按照最低可用檔案描述符來匹配,得到開啟新檔案對應到stdin位置
close(0);
fd = open(<file path>,O_RDONLY);
3.2.方法2:open..close..dup..close
1.open開啟一個檔案,fd不為0,
2.close(0)關閉檔案描述符為0。
3.dup(fd)來將檔案描述符fd做一個複製。把複製得到的檔案fd根據最低可用檔案描述符原則與0相連。
4.close(fd)關閉最先開啟的檔案。
其中在這個方法中,close(0)和dup(fd)可以結合在一起作為一個單獨的系統呼叫dup2
fd=open(<file path>)
close(0)
dup(fd)
close(fd)
3.3.方法3:open..dup2..close
#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<stdlib.h>
//#define DUP2
#define CLOSE_DUP
void main()
{
int fd;
int newfd; //the new file descriptor
char line[100];
fgets(line,100,stdin);
printf("%s",line);
fd = open("./data",O_RDONLY); //read data from file which creates by myself
#ifdef CLOSE_DUP
close(0);
dup(fd);
#else
newfd = dup2(fd,0);
#endif
if(newfd !=0)
{
perror("gouge cuole");
exit(1);
}
close(fd); //every time you wouldn't use the file descriptor, you can close it
fgets(line,100,stdin);
printf("%s",line);
}
4.為其他程式重定向I/O:who>userlist
虛擬碼:
pid=fork()
if(pif ==0 )
{
close(1);
fd=creat(<file>,0644);//open a newho file
execlp("who","who",NULL);
perror("execlp");
exit(1);
}
if(pid!=0)
{
wait(NULL);
}
重定向到檔案的小結:
1.標準輸入、輸出以及錯誤輸出分別對應於檔案描述符0、1、 2
2.核心總是使用最低可用檔案描述符
3.檔案描述符集合通過exec呼叫傳遞,且不會被改變。
5.管道程式設計
管道是核心中的一個單向的資料通道。管道有一個讀取端和一個寫入端。實現who|sort的兩個技巧:如何建立管道,如何使標準輸入和輸出通過管道聯絡起來。
5.1建立管道
result = pipe(int array[2]);
其中管道的輸出端是array[0]
中的檔案描述符表示,輸入端是array[1]
中的檔案描述符表示.
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
void main()
{
int len,i,apipe[2]; /*two file descriptor*/
char buf[BUFSIZ];
if(pipe(apipe)==-1)
{
perror("could not make pipe");
exit(1);
}
printf("Got a pipe! It is file descriptors: {%d %d \n",apipe[0],apipe[1]);
while(fgets(buf,BUFSIZ,stdin))
{
len = strlen(buf);
if(write(apipe[1],buf,len)!=len)
{
perror("writing to pipe");
break;
}
for(i=0;i<len;i++) /*wipe*/
buf[i]='X'; //nothing will be changed
if((len = read(apipe[0],buf,BUFSIZ))==-1)
{
perror("reading from price");
break;
}
if(write(1,buf,len)!=len){ /*send*/
perror("writing to stdout"); /*to*/
break; /*pipe*/
}
}
}
可以看到上述程式碼建立一個管道,預設使用3和4檔案描述符.執行過程如圖
當然很少有自己拿個管道和自己玩的,一般用在程序間通訊的比較多,要把pipe和fork結合起來玩.
5.2使用fork來共享管道
父程序建立子程序後,他們之間共享管道,兩個程序都訪問管道的兩端。而程序建立時複製了檔案描述符表等其他內容。因此管道可以用作程序間的資料交流
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#define CHILD_MESS "I want a cookie\n"
#define PARENTS_MESS "I am the parents\n"
#define opps(m,x) {perror(m);exit(x);}
void main()
{
int apipe[2];
int len;
int read_len;
char buf[BUFSIZ];
if(pipe(apipe)==-1)
{
opps("pipe",1);
}
switch(fork())
{
case -1:
opps("fork ",1);
case 0:
len = strlen(CHILD_MESS);
while(1){
if(write(apipe[1],CHILD_MESS,len)!=len) //write the CHILD_MESS to parents
opps("child wirte",3);
sleep(5);
}
/*parent reads from pipe and write to pipe*/
default:
len = strlen(PARENTS_MESS);
while(1){
if(write(apipe[1],PARENTS_MESS,len)!=len) //write the PARENTS_MESS to apipe[0]
opps("parents write",4);
sleep(1);
read_len = read(apipe[0],buf,BUFSIZ);
if(read_len <=0)
break;
write(1,buf,read_len);
}
}
}
當然這個管道存在大家都可以往裡面寫,可以在裡面讀,所以就容易造成混亂.所以要讓子程序寫,讓父程序讀,在不同的程序中關閉無用的檔案描述符.
5.3技術細節:管道並非檔案
管道像檔案一樣,是一種不帶有任何結構的位元組序列,另一方面,管道又與檔案不同.
1.從管道中讀資料
(1)管道讀取 阻塞
當程序試圖從管道中讀取資料時,程序被掛起直到讀取完成,隨之而來的如何避免這個問題?(flush寫完成之後flush一下?)
(2)管道的讀取結束標誌
當所有寫者關閉了管道的寫資料端時,試圖從管道讀取資料的呼叫返回0,意味著檔案的結束
(3)多個讀者可能引起麻煩
管道是一個佇列。當程序從管道中讀取資料之後,資料已經不存在了。所以多個讀者可能會出現讀取資料不完整的情況.
2.向管道中寫資料
(1)寫入資料阻塞直到管道有空間去接納新的資料
如果一次寫入量超過管道大小,則等管道傳輸完前一部分,再匯入一部分。
(2)寫入必須保證一個最小的塊大小:POSIX保證核心不會被拆分成小於512位元組的塊,而linux保證的是4096位元組連續.
(3 )若無讀者在讀取資料,則寫操作執行失敗
如果所有的讀者關閉了管道的埠,那麼首先,核心傳送SIGPIPE訊息給程序。若程序被終止,則無任何事情發橫。否則,write返回-1報錯,errno設定位EPIPE
以上已經介紹了了如何在一個程序中輸入,輸出資料.那麼資料是怎麼在兩個程序直接傳遞,切是雙向傳遞呢?還有上述說的管道只是在父子程序之間能夠起到傳遞資料的作用,且是單項傳遞的,如何做到雙向傳遞?如何在兩個距離很遠的計算機之間傳遞資訊?如何做到可靠傳輸?