dup函數和dup2函數(讀APUE)

來源:互聯網
上載者:User
dup函數和dup2函數(讀APUE)

標籤:雜談
 
分類:C&C++

dup和dup2也是兩個非常有用的調用,它們的作用都是用來複製一個檔案的描述符。它們經常用來重新導向進程的stdin、stdout和stderr。這兩個函數的原形如下:

#include
<unistd.h>
    int dup( int
oldfd );
    int dup2(
int oldfd, int targetfd )

 

   
利用函數dup,我們可以複製一個描述符。傳給該函數一個既有的描述符,它就會返回一個新的描述符,這個新的描述符是傳給它的描述符的拷貝。這意味著,這兩個描述符共用同一個資料結構
。例如,如果我們對一個檔案描述符執行lseek操作,得到的第一個檔案的位置和第二個是一樣的。下面是用來說明dup函數使用方法的程式碼片段:

    int fd1,
fd2;
    ...
fd2 = dup( fd1 );

 

   
需要注意的是,我們可以在調用fork之前建立一個描述符,這與調用dup建立描述符的效果是一樣的,子進程也同樣會收到一個複製出來的描述符。
  
   
dup2函數跟dup函數相似,但dup2函數允許調用者規定一個有效描述符和目標描述符的id。dup2函數成功返回時,目標描述符(dup2函數的第
二個參數)將變成源描述符(dup2函數的第一個參數)的複製品,換句話說,兩個檔案描述符現在都指向同一個檔案,並且是函數第一個參數指向的檔案。下面
我們用一段代碼加以說明:

    
int oldfd;
    oldfd =
open("app_log", (O_RDWR | O_CREATE), 0644 );
    dup2( oldfd,
1 );
    close( oldfd
);

 

   
本例中,我們開啟了一個新檔案,稱為“app_log”,並收到一個檔案描述符,該描述符叫做fd1。我們調用dup2函數,參數為oldfd和1,這會
導致用我們新開啟的檔案描述符替換掉由1代表的檔案描述符(即stdout,因為標準輸出檔案的id為1)。任何寫到stdout的東西,現在都將改為寫
入名為“app_log”的檔案中。需要注意的是,dup2函數在複製了oldfd之後,會立即將其關閉,但不會關掉新近開啟的檔案描述符,因為檔案描述
符1現在也指向它。 
 
   
下面我們介紹一個更加深入的範例程式碼。回憶一下本文前面講的命令列管道,在那裡,我們將ls –1命令的標準輸出作為標準輸入串連到wc
–l命令。接下來,我們就用一個C程式來加以說明這個過程的實現。代碼如下面的範例程式碼3所示。 
 
   
在範例程式碼3中,首先在第9行代碼中建立一個管道,然後將應用程式分成兩個進程:一個子進程(第13–16行)和一個父進程(第20–23行)。接下來,在子進程中首先關閉stdout描述符(第13行),然後提供了ls
–1命令功能,不過它不是寫到stdout(第13行),而是寫到我們建立的管道的輸入端,這是通過dup函數來完成重新導向的。在第14行,使用dup2
函數把stdout重新導向到管道(pfds[1])。之後,馬上關掉管道的輸入端。然後,使用execlp函數把子進程的映像替換為命令ls
–1的進程映像,一旦該命令執行,它的任何輸出都將發給管道的輸入端。 
 
   
現在來研究一下管道的接收端。從代碼中可以看出,管道的接收端是由父進程來擔當的。首先關閉stdin描述符(第20行),因為我們不會從機器的鍵盤等標
准裝置檔案來接收資料的輸入,而是從其它程式的輸出中接收資料。然後,再一次用到dup2函數(第21行),讓stdin變成管道的輸出端,這是通過讓文
件描述符0(即常規的stdin)等於pfds[0]來實現的。關閉管道的stdout端(pfds[1]),因為在這裡用不到它。最後,使用
execlp函數把父進程的映像替換為命令wc -1的進程映像,命令wc -1把管道的內容作為它的輸入(第23行)。

範例程式碼3:利用C實現命令的流水線操作的代碼
 
    
1:      
#include <stdio.h>
    
2:      
#include <stdlib.h>
    
3:      
#include <unistd.h>
    
4:
    
5:      
int main()
    
6:      
...{
    
7:        
int pfds[2];
    
8:
    
9:        
if ( pipe(pfds) == 0 ) ...{
    
10:
    
11:          
if ( fork() == 0 ) ...{
    
12:
   
 13:            
close(1);
    
14:            
dup2( pfds[1], 1 );
    
15:            
close( pfds[0] );
    
16:            
execlp( "ls", "ls", "-1", NULL );
    
17:
    
18:          
} else ...{
    
19:
    
20:            
close(0);
    
21:            
dup2( pfds[0], 0 );
    
22:            
close( pfds[1] );
    
23:            
execlp( "wc", "wc", "-l", NULL );
    
24:
    
25:          
}
    
26:
    
27:        
}
    
28:
    
29:        
return 0;
    
30:      
}  

 

 

   
在該程式中,需要格外關注的是,我們的子進程把它的輸出重新導向的管道的輸入,然後,父進程將它的輸入重新導向到管道的輸出。這在實際的應用程式開發中是非常有用的一種技術。

1. 檔案描述符在核心中資料結構  
   
在具體說dup/dup2之前, 我認為有必要先瞭解一下檔案描述符在核心中的形態。
 
一個進程在此存在期間,會有一些檔案被開啟,從而會返回一些檔案描述符,從shell
 
中運行一個進程,預設會有3個檔案描述符存在(0、1、2), 0與進程的標準輸入相關聯,
 
1與進程的標準輸出相關聯,2與進程的標準錯誤輸出相關聯,一個進程當前有哪些開啟
 
的檔案描述符可以通過/proc/進程ID/fd目錄查看。 可以清楚的說明問題:
 
 
  進程表項
————————————————
 
   fd標誌 檔案指標
     
_____________________
fd 0:|________|____________|------------> 檔案表
fd 1:|________|____________|
fd 2:|________|____________|
fd 3:|________|____________|
      
|    
.......    |
      
|_____________________|
 
               
圖1
       
檔案表中包含:檔案狀態標誌、當前檔案位移量、v節點指標,這些不是本文討論的
 
重點,我們只需要知道每個開啟的檔案描述符(fd標誌)在進程表中都有自己的檔案表
 
項,由檔案指標指向。
 
2. dup/dup2函數
 
APUE和man文檔都用一句話簡明的說出了這兩個函數的作用:複製一個現存的檔案描述符。
 
#include <unistd.h>
 
int dup(int oldfd);
 
int dup2(int oldfd, int newfd);
 
從圖1來分析這個過程,當調用dup函數時,核心在進程中建立一個新的檔案描述符,此
 
描述符是當前可用檔案描述符的最小數值,這個檔案描述符指向oldfd所擁有的檔案表項。
 
 
  進程表項
————————————————
 
   fd標誌 檔案指標
     
_____________________
fd
0:|________|____________|                       _______
fd 1:|________|____________|---------------->
|            |
fd
2:|________|____________|                      
| 檔案表  |
fd 3:|________|____________|---------------->
|_____  |
      
|    
.......                             
|
      
|_____________________|
 
               
圖2:調用dup後的
 
2 所示,假如oldfd的值為1, 當前檔案描述符的最小值為3, 那麼新描述符3指向
 
描述符1所擁有的檔案表項。
 
dup2和dup的區別就是可以用newfd參數指定新描述符的數值,如果newfd已經開啟,則
 
先將其關閉。如果newfd等於oldfd,則dup2返回newfd, 而不關閉它。dup2函數返回的新
 
檔案描述符同樣與參數oldfd共用同一檔案表項。
 
APUE用另外一個種方法說明了這個問題:
 
實際上,調用dup(oldfd);
 
等效與
       
fcntl(oldfd, F_DUPFD, 0)
 
而調用dup2(oldfd, newfd);
 
等效與
       
close(oldfd);
       
fcntl(oldfd, F_DUPFD, newfd);

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.