在上篇文章我們複習了C檔案IO操作并且認識了檔案相關的系統調用接口。本篇文章我們要引入檔案描述符的概念。
0.檔案描述符
0.1引入檔案描述符
我們在認識open接口時知道了該接口有一個int的傳回值,但是當時我們并沒有重點介紹這個傳回值到底是什麼,而這裡我們将重點介紹這個傳回值。是以我們用man手冊查一下open函數的傳回值。根據手冊描述open函數如果打開或建立成功則會傳回一個新的檔案描述符,否則失敗則傳回-1。
那我們寫一段代碼來驗證一下這個傳回值,看看效果
#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;
}
我們看到檔案被建立出來了,輸出的fd也都是大于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。)
#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;
}
函數對應的接口 | 資料類型的對應 |
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[ ]. 是以要對檔案進行操作時,隻需要得到這個數組的下标即可。
而現在知道,檔案描述符就是從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;
}
發現是結果是: 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;
}
此時我們發現什麼都沒有,這是為什麼呢?這時候我們要談談重定向了。根據檔案描述的配置設定規則,我們建立的檔案描述符fd一定是1,雖然不再指向對應的顯示器了,但是已經指向了myfile.txt的底層資料結構了。那麼我們如何檢視到這個fd呢?這就跟輸出重定向相關了,那麼我們必須重新整理一下才能看到,那麼為什麼要重新整理呢?這是跟緩沖區相關的我們後面解釋。
0.3.1 重定向的本質
在上述代碼中,我們本來要往顯示器上面寫,最終卻變成了向指定檔案中寫,這就是輸出重定向。
當我們close(1)後,建立一個檔案時,根據檔案描述符配置設定規則,1号下标會指向建立的檔案,是以凡是往1号檔案描述符寫的内容都寫到myfile當中,而不再寫到标準輸出了。
如果我們要進行重定向,上層隻認0,1,2,3,4,5這樣的fd,我們可以在OS内部通過一定的方式調整數組的特定下标的内容(指向),我們就可以完成重定向操作!
0.3.2 dup2 -- 重定向的具體操作
上面的一對資料,都是核心資料結構,隻有OS有權限,是以我們使用者在使用的時候必須提供接口.是以作業系統提供了dup2
dup2的作用:
makes newfd be the copy of oldfd, closing newfd first if necessary。
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;
}
printf是C庫當中的IO函數,一般往 stdout 中輸出,但是stdout底層通路檔案的時候,找的還是fd:1, 但此時,fd:1下标所表示内容,已經變成了myfifile的位址,不再是顯示器檔案的位址,是以,輸出的任何消息都會往檔案中寫入,進而完成輸出重定向。
追加重定向:
輸入重定向: