天天看點

[ Linux ] 檔案描述符和重定向

在上篇文章我們複習了C檔案IO操作并且認識了檔案相關的系統調用接口。本篇文章我們要引入檔案描述符的概念。

0.檔案描述符

0.1引入檔案描述符

我們在認識open接口時知道了該接口有一個int的傳回值,但是當時我們并沒有重點介紹這個傳回值到底是什麼,而這裡我們将重點介紹這個傳回值。是以我們用man手冊查一下open函數的傳回值。根據手冊描述open函數如果打開或建立成功則會傳回一個新的檔案描述符,否則失敗則傳回-1。

[ Linux ] 檔案描述符和重定向

那我們寫一段代碼來驗證一下這個傳回值,看看效果

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    int fd1 = open("log1.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd2 = open("log2.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd3 = open("log3.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    int fd4 = open("log4.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    printf("fd1: %d\n",fd1);
    printf("fd2: %d\n",fd2);
    printf("fd3: %d\n",fd3);
    printf("fd4: %d\n",fd4);
    return 0;
}      
[ Linux ] 檔案描述符和重定向

我們看到檔案被建立出來了,輸出的fd也都是大于1的整數,但是這裡有兩個問題:

  1. 為什麼fd是從3開始的呢?0,1,2去哪兒了呢?

答:0,1,2被預設打開了。0叫做标準輸入(鍵盤),1叫做标準輸出(顯示器),2叫做标準錯誤(顯示器)。

On program startup, the integer file descriptors associated with the streams stdin, stdout, and stderr are 0, 1, and 2,respectively. (在程式啟動時,與流stdin、stdout和stderr關聯的整數檔案描述符分别為0、1和2。)

[ Linux ] 檔案描述符和重定向
[ Linux ] 檔案描述符和重定向
[ Linux ] 檔案描述符和重定向
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{   
    printf("stdin:%d\n",stdin->_fileno);
    printf("stdout:%d\n",stdout->_fileno);
    printf("stderr:%d\n",stderr->_fileno);
    return 0;
}      
[ Linux ] 檔案描述符和重定向
函數對應的接口 資料類型的對應
fopen/fclose/fread/fwrite..... FILE* ->FILE
open/close/read/write...... fd

是以我們使用的C語言接口一定封裝了系統調用接口。

2.fd為什麼是0,12,3,4,5......

一般而言,一個程序可不可以打開多個檔案?答案當然是可以的,是以在核心中,一個程序:打開的檔案 = 1:n

是以系統在運作中有可能會存在大量的被打開的檔案,作業系統要對被打開的檔案進行管理。那麼作業系統如何管理這些被打開的檔案呢?--->先描述再組織 是以一個檔案被打開在核心中要建立被打開檔案的核心資料結構--先描述。 struct file 内部包含了大量的内容和屬性。作業系統将多個檔案的struct file用連結清單連接配接起來,是以對被打開檔案的管理轉換成了對連結清單的增删查改!那麼程序如何和打開的檔案建立映射關系呢?是以我們在task_struct中包含一個struct files_srruct * fs指針指向strcut files_struct内部有一個指針數組,存的就是打開檔案的fd_array[ ]. 是以要對檔案進行操作時,隻需要得到這個數組的下标即可。

[ Linux ] 檔案描述符和重定向

而現在知道,檔案描述符就是從0開始的小整數。當我們打開檔案時,作業系統在記憶體中要建立相應的資料結構來描述目标檔案。于是就有了fifile結構體。表示一個已經打開的檔案對象。而程序執行open系統調用,是以必須讓程序和檔案關聯起來。每個程序都有一個指針*fifiles, 指向一張表fifiles_struct,該表最重要的部分就是包涵一個指針數組,每個元素都是一個指向打開檔案的指針!是以,本質上,檔案描述符就是該數組的下标。是以,隻要拿着檔案描述符,就可以找到對應的檔案

0.2 檔案描述符的配置設定規則

通過上文的了解我們知道了檔案描述符是從3開始的,因為0,1,2預設分給了stdin,stdout,stderr.那麼當我們關閉0或者2我們再重新建立一個檔案時,他的檔案描述符會是幾呢?

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    close(0);
    int fd = open("myfile.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd < 0) 
    {
        perror("open");
        return 1;
    }
    printf("fd:%d\n",fd);
    close(fd);

    return 0;
}      
[ Linux ] 檔案描述符和重定向
[ Linux ] 檔案描述符和重定向

發現是結果是: fd: 0或者fd:2 可見,檔案描述符的配置設定規則:在files_struct數組當中,找到目前沒有被使用的最小的一個下标,作為新的檔案描述符。

0.3 重定向

那如果我們關閉fd為1呢?根據檔案描述符的配置設定規則,建立檔案的檔案描述符fd會是1嗎?我們來看代碼:

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    close(1);
    int fd = open("myfile.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd < 0) 
    {
        perror("open");
        return 1;
    }
    printf("fd:%d\n",fd);
    close(fd);

    return 0;
}      
[ Linux ] 檔案描述符和重定向

此時我們發現什麼都沒有,這是為什麼呢?這時候我們要談談重定向了。根據檔案描述的配置設定規則,我們建立的檔案描述符fd一定是1,雖然不再指向對應的顯示器了,但是已經指向了myfile.txt的底層資料結構了。那麼我們如何檢視到這個fd呢?這就跟輸出重定向相關了,那麼我們必須重新整理一下才能看到,那麼為什麼要重新整理呢?這是跟緩沖區相關的我們後面解釋。

[ Linux ] 檔案描述符和重定向

0.3.1 重定向的本質

在上述代碼中,我們本來要往顯示器上面寫,最終卻變成了向指定檔案中寫,這就是輸出重定向。

當我們close(1)後,建立一個檔案時,根據檔案描述符配置設定規則,1号下标會指向建立的檔案,是以凡是往1号檔案描述符寫的内容都寫到myfile當中,而不再寫到标準輸出了。

[ Linux ] 檔案描述符和重定向

如果我們要進行重定向,上層隻認0,1,2,3,4,5這樣的fd,我們可以在OS内部通過一定的方式調整數組的特定下标的内容(指向),我們就可以完成重定向操作!

0.3.2 dup2 -- 重定向的具體操作

上面的一對資料,都是核心資料結構,隻有OS有權限,是以我們使用者在使用的時候必須提供接口.是以作業系統提供了dup2

[ Linux ] 檔案描述符和重定向

dup2的作用:

makes newfd be the copy of oldfd, closing newfd first if necessary。

[ Linux ] 檔案描述符和重定向

dup2的使用:

假設我們要實作剛剛輸出重定向的操作,那麼根據dup2的描述,他是copy什麼呢?參數怎麼傳?

dup2是copy數組下标對應的内容(檔案位址的拷貝)最終的結果是newfd的内容是oldfd的一份拷貝,是以最後隻剩下oldfd的内容。是以dup2是将oldfd拷到newfd内。是以如果要輸出重定向,是要将fd為3的内容拷貝到fd為1内部。是以最後的内容和fd為3的内容保持一緻。

是以輸出重定向 dup2(3,1); 我們使用代碼來驗證一下

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

int main()
{
    int fd = open("myfile.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);
    if(fd < 0) 
    {
        perror("open");
        return 1;
    }
    
    dup2(fd,1);
    
    printf("檔案的fd:%d\n",fd);
    fflush(stdout);
    close(fd);
    return 0;
}      
[ Linux ] 檔案描述符和重定向

printf是C庫當中的IO函數,一般往 stdout 中輸出,但是stdout底層通路檔案的時候,找的還是fd:1, 但此時,fd:1下标所表示内容,已經變成了myfifile的位址,不再是顯示器檔案的位址,是以,輸出的任何消息都會往檔案中寫入,進而完成輸出重定向。

追加重定向:

[ Linux ] 檔案描述符和重定向

輸入重定向:

[ Linux ] 檔案描述符和重定向