歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> 《Linux系統編程》第二章筆記(二)

《Linux系統編程》第二章筆記(二)

日期:2017/3/1 11:46:58   编辑:關於Linux

大文件讀寫

系統變量類型是指一些系統實現細節相關的變量類型typedef成不暴露實現細節的變量類型,從而保證跨平台時源碼級別的兼容性。例如對於進程id,其系統變量類型是pid_t,常見的還有size_tsocklen_t等,在sys/types.h中定義。
32位Linux上,文件偏移量類型off_t類型為int,32位,即能夠尋址的文件最大為2G。若想實現32位系統上對超過2G的文件尋址,需要將_FILE_OEFFSET_BISTS宏設置為64,可以在包含其他頭文件之前#define _FILE_OEFFSET_BISTS 64或在makefile中添加-D_FILE_OEFFSET_BISTS=64,編譯時根據該條件會再次將__off64_ttypedef成off_t類型,這個變量是64位長度的。通過這個宏,在不修改源碼的前提下將只支持最大2G操作的源碼擴展成支持最大2^63-1大小。
除此之外還能使用過度型API來指明對大文件讀寫,包括open64()lseek64()等。更推薦的是在32位Linux上使用_FILE_OEFFSET_BISTS宏。此外使用宏定義的方式對大文件做支持時要注意,所有與文件讀寫相關的模塊在編譯時都要使用該宏,避免類型不一致導致的問題。
64位Linux的off_t長度默認就是64位的,因此不需要定義上述的宏。

2.4 同步I/O

同步I/O意味著系統調用確保數據被寫到磁盤上(至少是硬盤緩沖區,下同)時才返回。盡管內核提供的緩存I/O方式已經工作的很好了,但是在一些要求較高的系統中,數據需要盡快寫入磁盤,減少斷電等異常情況對數據造成破壞,可以用同步I/O要求並等待內核將數據寫入磁盤。
fsync()和fdatasync()

#include 
int fsync (int fd);

fsync()可以確保fd對應文件的數據被寫到硬盤上並更新文件時間戳,另一個系統調用int fdatasync (int fd)的功能基本一致,但是其只會寫入數據而不會更新時間戳等inode節點的元數據,因此速度稍快一些,但如果寫入後文件大小有變化,不能使用fdatasync()而應該使用fsync()。一個高效的方式是固定文件大小(也就是稀疏文件),在創建文件之後在文件長度處寫入數據,將文件大小擴展到固定值。
這兩個函數在成功時都返回0,失敗返回-1並設置errno,錯誤碼取值有:

錯誤碼 描述 EBADF 文件描述符不是一個可以寫入的描述符 EINVAL 文件描述符對應的對象不支持同步I/O EIO 發生底層錯誤

sync()

#include 
void sync (void);

該函數指示所有緩沖區都寫入硬盤而不區分哪個文件描述符,盡管標准並未約束sync()函數要在所有內核緩沖區寫入硬盤之前不能返回,但Linux系統保證了sync()在所有數據寫入硬盤之前不會返回。

O_SYNC/O_DSYNC和O_RSYNC標志
O_SYNC/O_DSYN標志的行為相同:在open()時指定該標志,則在指定文件描述符上所有的I/O操作都是同步的。但對於讀請求來說是否設置該標志都是同步的,因為不同步的話無法保證讀取數據的完整性。

open()時的參數O_SYNC/O_DSYNC有著和fsync/fdatasync類似的語義:使每次write都會阻塞等待硬盤IO完成,O_SYNC更新文件數據的同時還更新文件元數據,O_DSYNC則只更新文件數據。但是相較之下O_SYNC/O_DSYNC參數的方式內核實現效率更高,但文件在關閉之前都強制使用同步I/O,而fsync/fdatasync則能選擇在必要時候調用,靈活性更高。
O_RSYNC標志更特殊一點,其只能與O_SYNC/O_DSYN連用,表示同步讀文件數據時,也將文件的元數據一起同步讀取。

2.5 直接I/O

相較於直接I/O,以上列出的I/O數據均通過內核緩沖區再到硬盤,系統內核通過緩沖區管理I/O請求。直接I/O使內核忽略緩沖管理,直接將數據在設備和用戶緩沖區之間拷貝而且所有I/O操作都會是同步的。open()時指定O_DIRECT標志來指定直接I/O,此時請求長度、緩沖區對齊、文件偏移量必須是設備扇區大小的整數倍。

2.6 關閉文件

對某個文件讀寫完畢後需要關閉文件,close()系統調用使內核分離文件描述符和文件的關聯,關閉的文件描述符可以被系統再次分配。正確時返回0,錯誤時返回-1並設置errno。

#include 
int close (int fd);

由於內核對I/O操作采用緩沖等優化操作,關閉文件與數據是否寫入磁盤沒有關系,可能出現關閉文件之後數據還未寫入磁盤的情況,要保證文件關閉時數據一定寫入到硬盤上,可以參考同步I/O或直接I/O的做法。
關閉文件時,內核會修改inode節點在內存中的引用次數,當inode沒有再被引用時,內核會從內存中刪除該inode節點。若該inode節點對應的文件已經被刪除,close()操作執行後會導致文件被真正從文件系統中刪除(參見【臨時文件】)。
當一個進程終止時,內核對該進程所有尚未關閉的文件描述符調用close關閉,所以即使用戶程序不調用close,在終止時內核也會自動關閉它打開的所有文件。

2.7 用lseek()查找

在需要對文件進行非線性I/O時,用到lseek()調用來重新選擇文件讀寫位置。

#include 
#include 
off_t lseek (int fd, off_t pos, int origin);

lseek的行為依賴於origin參數,其取值有:
SEEK_CUR-將fd對應的文件位置設置為當前位置加上pos個字節,pos可以是正數或負數。
SEEK_END-將fd對應的文件位置設置為文件尾加上pos個字節,pos可以是正數或負數。
SEEK_SET-將fd對應的文件位置設置為pos對應的位置,pos為0時代表設置為文件起始位置。
調用正確時返回值為設置之後的位置,錯誤時返回-1並設置errno,因此可以使用lseek (fd, 0, SEEK_CUR)來確認當前文件位置。
lseek()可能產生如下錯誤碼:

錯誤碼 描述 EBADF 文件描述符沒有指向已經打開的文件 EINVAL 參數不是可選范圍或計算出的文件位置為負數 ESPIPE 文件描述符對應的文件不能被改變文件位置,例如管道或套接字

lseek()和O_APPEND
使用lseek()可以設置文件的讀寫位置,而在open()時O_APPEND指定了每次寫入時在文件末尾追加。那使用O_APPEND標志打開文件後用lseek()是否能夠做到在任意位置上讀寫?下面的代碼測試一下:

#include 
#include 
#include  
int main ()    
{    
    //假設test.txt裡是123
    int fd = open("test.txt", O_RDWR|O_APPEND);
    //將讀寫位置設置到文件起始
    lseek(fd, 0, SEEK_SET);
    write(fd, "end", 3);
    char temp[128];
    read(fd, temp, sizeof(temp));
    //寫入後在當前位置讀取,讀取出來的是空字符串,說明當前文件位置在末尾
    printf("first read:%s\n", temp);//first read:
    //設置到文件起始再讀一次
    lseek(fd, 0, SEEK_SET);
    read(fd, temp, sizeof(temp));
    //讀到全部內容
    printf("second read:%s\n", temp);//second read:123end
    return 0;   
}  

可見以O_APPEND標志打開,每次寫入前文件位置都被改到文件末尾,與lseek()無關。
下面的代碼測試多進程寫入同一個文件時用O_APPEND標志和lseek()函數的區別:

#include 
#include 
#include  
#include 
int main(int argc, char *argv[])
{
    if(argc < 3 || argc > 5)
        return -1;
    int mode = O_CREAT|O_WRONLY;
    if(argc == 4 && (argv[3][0] == 'x' || argv[3][0] == 'X'))
        mode |= O_APPEND;
    int fd = open(argv[1], mode);
    if(fd == -1)
    {
        perror("open");
        return -1;
    }
    for(int i = 0; i

編譯後使用./a.out 1.txt 20000000 & ./a.out 1.txt 2000000./a.out 2.txt 20000000 x & ./a.out 2.txt 2000000 x命令使兩個進程並發寫入,一次是使用O_APPEND標志保證寫入的原子性,一次是使用lseek()手動定位到文件尾。最後使用ll命令查看1.txt和2.txt的大小,可以看到2.txt大小正確而且比1.txt要大一些,這是因為使用lseek()定位之後可能另一個進程剛好寫入,此時文件位置已經後移,但前一個進程依然向之前的位置寫入,這就覆蓋了一部分內容。

稀疏文件
使用lseek()可以設置超過文件大小的位置,此時通過write()函數寫入文件的話文件會變為lseek()設置的偏移量相同大小,但是不會占用相應空間,這種文件稱為稀疏文件(參見第一章筆記[普通文件])。稀疏文件只有在真正寫入數據時才會在硬盤上占據相應空間,如果想在write()時確認硬盤剩余空間滿足要求,SUS標准提供了posix_fallocate()系統調用來提前分配硬盤空間。

2.8 定位讀寫

如果只是想在特定位置上進行I/O操作,但不改變內核保存的文件位置的話,可以使用定位讀寫的相關系統調用。用法在於如果多個線程同時對一個文件描述符(或指向同一個文件句柄的不同描述符)做讀寫時,使用定位讀寫能夠避免文件位置的競爭,lseek()修改文件位置後所有引用相同文件句柄的描述符都會生效。Linux提供了兩個調用來實現該功能。
pread()和pwrite()

#define _XOPEN_SOURCE 500
#include 
ssize_t pread (int fd, void *buf, size_t count, off_t pos);

上面的_XOPEN_SOURCE宏定義要定義在unistd.h被包含之前,這樣能夠引用pread()和pwrite(),具體參考Linux開發手冊。

該函數調用指示內核從pos的位置讀取count個字符到buf中而不改變當前文件位置。

#define _XOPEN_SOURCE 500
#include 
ssize_t pwrite (int fd, const void *buf, size_t count, off_t pos);

該函數調用指示內核向pos的位置寫入count個字符而不改變當前文件位置。嚴格來說相比定位讀寫,lseek()+read()/write()的方式性能有所不及,因為前者是一個系統調用,而後者是兩個系統調用,當然,相比於I/O操作的性能,系統調用的性能作用非常有限。
除了不改變文件位置外,這兩個函數與read()/write()行為、可能的錯誤碼一致。

2.9 截短文件

Linux提供了兩個系統調用來改變文件大小。

#include 
#include 
int ftruncate (int fd, off_t len);

int truncate (const char *path, off_t len);

這兩個系統調用都能將文件改變到len指定的長度,區別在於ftruncate需要指定文件描述符,而truncate指定文件位置,不需要提前打開文件。這兩個系統調用在len大於文件原有長度時,會將中間充填為0。ftruncate()不會改變當前文件位置指針。

2.10 I/O多路復用

應用程序經常會在多個文件上有I/O操作,例如socket通訊、鍵盤輸入、事件循環等。對於單線程對所有文件做I/O的架構而言,按順序挨個阻塞讀寫文件是效率極端低下而且容易出錯的做法。
在文件較少而且數量固定的時候,可以選擇每個線程對單一文件做阻塞I/O操作,這樣某個文件的阻塞不會對整體邏輯和其他文件的I/O造成影響,但處理的文件變多時會造成系統負擔。
另一個選擇是一個線程內對所有文件使用非阻塞I/O,這樣對某個文件的操作無法立即完成時不會影響其他文件的讀寫。但是這樣有效率問題:每次嘗試做I/O操作時都有一次系統調用,效率較低;此外如果一段時間內所有文件都未准備好,程序會在循環中會一直占用CPU資源做嘗試,而使用sleep()來放棄CPU時間又引入新的問題:到底多久才合適,時間短的話效果不好,時間長的話實時性又有影響。最好有個機制能夠喚醒阻塞的線程,這樣能最大程度提高實時性,對系統資源來說也不會被浪費。
I/O多路復用就是一個比較好的選擇,其允許同時在多個文件上阻塞(直到有文件做好I/O准備)並使線程睡眠,當被監聽的文件有任意一個就緒時調用返回並通知哪些文件的哪些操作可以執行,此時執行對應操作能夠保證立即執行並返回。Linux提供了三種I/O多路復用方案:select()/pool()/epoll()

2.10.1 select()

select()由BSD Unix首次引入。

#include 
#include 
#include 
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

在監聽的文件准備好I/O之前或者超出一定時間之前,select()調用都會阻塞。這保證了當沒有文件能夠無需阻塞的進行I/O操作時,線程將不占用CPU資源。

n-要監聽的所有文件集合中最大值加1。

readfds、writefds、exceptfds-分別代表監聽可讀就緒的文件集合、可寫就緒的文件集合、有異常或帶外數據就緒的文件集合,均可為NULL。select()返回時,每個集合中只留有准備就緒的文件描述符。
對集合的操作,Linux提供了幾個宏來支持:

fd_set writefds;
FD_ZERO(&writefds);//初始化文件描述符集合
FD_SET(fd, &writefds); //將fd加入到文件集合
FD_CLR(fd, &writefds); //將fd從文件集合中刪除
FD_ISSET(fd, &readfds);//測試fd是否在文件集合中

fd_set類型在本質上是一個保存有數組的結構體,每一位bit都用來表示fd,因此select()支持的文件描述符是有上限的,由FD_SETSIZE設定,Linux上該值是1024。
在有文件就緒時,select()返回上述集合中I/O就緒文件的數量,若超出時限則返回0,失敗返回-1並設置errno。

timeout-描述時間的結構:

#include 
struct timeval {
    long tv_sec; /* seconds */
    long tv_usec; /* microseconds */
};

若該參數不是NULL,則在tv_sec秒又tv_usec微秒後返回,即使當時沒有文件准備就緒。若該結構兩個屬性都是0,則select()會立即返回並告知哪些文件就緒。在大多數Unix系統中,每次調用返回後該結構中的值都是不確定的,每次調用後需要重新設置,新版本的Linux中,每次返回後該結構中的值是剩余時間。
由於類Unix系統大多實現了select(),因此可以借助select()做可移植的微秒級的sleep()

struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500;
/* sleep for 500 microseconds */
select (0, NULL, NULL, NULL, &tv);
錯誤碼 描述 EBADF 某個文件描述符非法 EINTR 等待時捕獲了一個信號,可以再次調用 EINVAL 參數不合法 ENOMEM 沒有足夠內存

由上面的描述可以看出,select()雖然提供了阻塞等待多個文件就緒的I/O多路復用機制,但是也有一些不便之處
* 首先受到fd_set類型的限制,select()等待的文件數量是有上限的
* 文件描述符集合每次都要從用戶空間拷貝到內核空間,集合較大時效率不高
* 內核在select()內部要遍歷每個文件描述符,調用其poll方法,在文件描述符集合較大時效率不高
* 每次select()之後都要FD_ZERO()->FD_SET()->select()來清理和設置每個文件描述符集合
* 在select()後需要遍歷每個監聽的文件描述符集合,確認哪些描述符依然在集合中,即哪些文件已經做好I/O准備
查看select/poll/epoll對比。

#include 
#include 
#include 
#include 
#define TIMEOUT 5 //阻塞超時時間
#define BUF_LEN 1024 //用戶緩沖區大小
int main (void)
{
    struct timeval tv;
    fd_set readfds;
    int ret;
    //將標准輸入對應的文件描述符放到監聽的文件集合中
    FD_ZERO(&readfds);
    FD_SET(STDIN_FILENO, &readfds);
    //設置超時時間
    tv.tv_sec = TIMEOUT;
    tv.tv_usec = 0;
    //開始阻塞等待用戶輸入
    ret = select (STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);
    if (ret == -1)
    {
        perror (”select”);
        return 1;
    }
    else if (!ret)
    {
        printf (”%d seconds elapsed.\n”, TIMEOUT);
        return 0;
    }
    //判斷標准輸入是否依然在監聽的文件集合中
    if (FD_ISSET(STDIN_FILENO, &readfds))
    {
        char buf[BUF_LEN+1];
        int len;
        //無需阻塞的讀取文件
        len = read (STDIN_FILENO, buf, BUF_LEN);
        if (len == -1)
        {
            perror (”read”);
            return 1;
        }
        if (len)
        {
            buf[len] = ’\0’;
            printf (”read: %s\n”, buf);
        }
        return 0;
    }
    fprintf (stderr, ”This should not happen!\n”);
    return 1;
}

POSIX定義了一個類似select()的系統調用pselect(),相比select()多了屏蔽信號處理的功能(即在pselect()時,不會被信號處理阻塞住,具體參看信號處理部分),且不會改變timeval結構中的時間,可選用的時間精度更高(只是理論上更高,但不可靠)。

2.10.3 poll()

poll()由System V首次引入。
poll()也是I/O多路復用的解決方案,解決了一些select()的不足。

#include 
int poll (struct pollfd *fds, unsigned int nfds, int timeout);

poll()采用結構數組的方式表示監聽的文件描述符,數組的長度由nfds指定,timeout是超時的毫秒數,負值代表無超時時間,0代表立即返回。pollfd結構體的定義如下:

#include 
struct pollfd {
int fd; /*文件描述符*/
short events; /*要監聽的I/O類型*/
short revents; /*就緒的I/O類型*/
};

events和revents字段都是可以做操作的掩碼,以下是合法的可監聽事件:

掩碼值 描述 POLLIN 普通或優先級帶數據可讀 POLLRDNORM 普通數據可讀 POLLRDBAND 優先級帶數據可讀 POLLPRI 高優先級數據可讀 POLLOUT 普通數據可寫 POLLWRNORM 普通數據可寫 POLLWRBAND 優先級帶數據可寫

以下掩碼可能出現在revents中:

掩碼值 描述 POLLER 文件描述符上有錯誤 POLLHUP 文件描述符上有掛起事件 POLLNVAL 文件描述符非法

其中POLLIN | POLLPRI相當於select()的讀就緒,POLLOUT | POLLWRBAND相當於select()的寫就緒,POLLOUT等價於POLLWRNORM。

poll()返回大於等於0的值代表就緒的文件描述符數量,-1代表發生錯誤。

錯誤碼 描述 EBADF 有非法文件描述符 EFAULT fds指針超過進程地址空間 EINTR 請求事件前收到了一個信號,可以再次調用 EINVAL nfds大小超過了RLIMIT_NOFILE值 ENOMEM 沒有足夠內存

poll的使用例子:

#include 
#include 
#include 
#define TIMEOUT 5 /*poll超時時間*/
int main (void)
{
    struct pollfd fds[2];
    int ret;
    /*監聽標准輸入的可讀事件*/
    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;
    /***strong text**監聽標准輸出的可寫事件,基本每次都是可寫的*/
    fds**strong text**[1].fd = STDOUT_FILENO;
    fds**strong text**[1].events = POLLOUT;
    /*阻塞等待*/
    ret = poll (fds, 2, TIMEOUT * 1000);
    if (ret == -1)
    {
        perror (”poll”);
        return 1;
    }
    if (!ret)
    {
        printf (”%d seconds elapsed.\n”, TIMEOUT);
        return 0;
    }
    if (fds[0].revents & POLLIN)
        printf (”stdin is readable\n”);
    if (fds[1].revents & POLLOUT)
        printf (”stdout is writable\n”);
    return 0;
}

使用poll時無需每次調用之前都重新設定pollfd數組,內核會負責revents字段的清理,比select()方便了一些。

Linux提供了專用函數ppoll(),特性與pselect()類似,提供了秒級和納秒級的控制,以及信號屏蔽功能。

2.10.4 poll()和select()

相比之下poll()比select()更有優勢,列出。主要在於
* poll()無需計算需要監聽的文件描述符的最大數量+1。
* poll()在較大的文件描述符需要監聽時更有效率。由於select()的fd_set用bit位來表示監聽的fd值,當fd較大時需要查找很多bit位才能找到需要監聽的文件描述符,例如要監聽的fd是1000時,select()要逐個bit的查找,一直找到1000對應的那個比特。而poll的效率則與fd的大小無關,只需要將fd放入數組即可。
* select()在返回時,文件描述符集合是被修改後的,如果下次select()需要監聽同樣的文件描述符,還需要再次設置一遍。而poll()返回時無需對文件描述符集合做多余操作,因為輸入(events)和輸出(revents)是分離的。
* select()在返回時timeout參數也是未定義的(Linux上是剩余時間,盡管如此,如果想重復利用的話還需要再次設置),poll()則不會改變該參數。

select()也有一些優勢:
* select()相較於poll()移植性更好,某些類Unix系統不支持poll()
* select()超時的時間控制精度更高,為微秒級

在Linux上比select()和poll()更有優勢的是epoll()系統調用,在之後章節列出。

2.11 內核內幕

2.11.1 虛擬文件系統

虛擬文件系統(VFS)是內核的文件操作的抽象機制,允許內核在無需了解文件系統類型的情況下使用文件系統函數並操作文件系統數據。

虛擬文件系統為開發人員提供了便利:開發人員無需針對不同的文件系統調用不同的接口,而是統一使用read()、write()等系統調用即可,從設計的角度上來說,類似於設計模式中的適配器模式——盡管不同的文件系統接口、存儲數據的方式不同,但對外提供了統一的接口。
例如用戶調用一個read()系統調用後,編譯器將該系統調用轉化為合適的陷阱態(通過軟中斷實現用戶態到內核態的切換)交給內核處理,內核來確保調用對應文件系統的相關函數,文件系統的函數來做具體讀取數據的工作並將數據返回。

2.11.2 頁緩存

頁緩存技術是在內存中保存最近在磁盤文件系統上訪問過的數據的方式。相比CPU的運算速度,磁盤I/O的速度太慢,為了避免CPU頻繁等待磁盤I/O,利用速度較快的內存保存可能會訪問到的數據。此外數據被訪問後,有可能在短時間內被再次訪問。

內核在查找文件系統數據時,首先到頁緩存中查找。數據第一次被查找時,內核從硬盤上讀取並存入頁緩存,等下次再次讀取相同位置數據時,直接從內存中獲取。頁緩存的大小是動態變化的,當頁緩存占用了正常的應用程序內存時,內核會對頁緩存做清理,釋放掉使用最少的緩存。對於很可能會被使用的頁緩存,內核采用交換的方式將其交換到硬盤上,通過/proc/sys/vm/swappiness來調整磁盤交換和內存緩存的關系,數值高代表傾向於使用磁盤交換,數值低代表傾向於在內存中保留頁緩存。
讀取文件中的數據一般是順序連續讀取的,內核實現了頁緩存預讀技術——每次讀磁盤時讀取更多數據到頁緩存中。
頁緩存對開發者來說是透明的,一般無需了解其內部實現。查看頁緩存資料。

2.11.3 頁回寫

2.3.6 write()的行為中描述的,內核使用緩沖區來延遲寫操作。當進程發起寫請求時,數據被拷入內核緩沖區,並將緩沖區標記為“髒”的——意味著內存數據要比磁盤數據新。最終緩沖區的數據要寫入磁盤,這個動作叫做回寫,回寫的觸發有以下幾種:
* 當空閒內存低於一個閥值時。空閒內存不足時,需要釋放一部分緩存,由於只有不髒的頁面才能被釋放,所以要把髒頁面都回寫到磁盤,使其變成干淨的頁面。
* 當髒頁在內存中駐留時間超過一個閥值時。確保髒頁面不會無限期的駐留在內存中,從而減少了數據丟失的風險。
* 當用戶進程調用 sync() 和 fsync() 系統調用時。給用戶提供一種強制回寫的方法,應對回寫要求嚴格的場景。
頁回寫的阈值在/proc/sys/vm中配置。回寫是由一些叫做pdflush的內核線程操作,當達到觸發條件時,pdflush線程會被喚醒並將髒緩沖區寫入磁盤。可能會有多個pfdflush線程在回寫,這樣能夠保證在向某個塊設備阻塞的寫入時其他塊設備的寫入不會被阻塞。緩沖區在內核中用buffer_head結構來表示。

重定向與復制文件描述符

在使用Linux時,經常用到例如./a.out>a.txt 2>&1的重定向語句。這個語句的含義是將a.out的輸出重定向到a.txt上,並將錯誤輸出重定向到1對應的文件描述符上。其中文件描述符1即標准輸出可以省略,上面的命令全寫是./a.out 1>a.txt 2>&1的形式。&1代表文件描述符1,只所以加上 &,是因為如果像重定向符之前那樣寫成1的話其實是重定向到名為1的文件中。<能對輸入進行重定向,一個模擬cp命令的cat < 1.txt > 2.txt,從1.txt中獲取數據並重定向到2.txt。
關閉標准輸入或輸出的方式,可以用&-表示關閉文件描述符,例如2>&-表示關閉標准錯誤輸出,<&-表示關閉標准輸入。也可以將標准輸出和錯誤輸出重定向到/dev/null這個黑洞文件中,任何寫入這個文件的數據都被遺棄。
從系統編程的角度來說,其實是不同的文件描述符指向了同一個文件句柄,可以使用dup()系列調用來實現。

#include
int dup(int fd);//返回一個與fd指向相同文件句柄的fd
int dup2(int fd1, int fd2);//返回與fd指向相同文件句柄的fd且保證值等於fd2

在沒有dup2()的時候,如果像要將文件描述符2重定向到文件描述符1,需要先close(2),再dup(1),Linux在分配文件描述符時分配的是最小可用的文件描述符,因此能夠成功返回2。dup2()省去了其中的close()操作。由於新、老文件描述符都指向同一個文件句柄,因此文件偏移量是共享的。但是由於新、老兩個文件描述符是不同的數值,它們之間的標志不同(參見【文件描述符】章節中的【進程級的文件描述符表】)目前只有close-on-exec標志,該標志默認是未設置的。Linux提供了int dup3(int fd1, int fd2, int flags)系統調用,用第三個參數指定文件描述符的標志,目前只支持O_CLOEXEC

Copyright © Linux教程網 All Rights Reserved