??
文章目錄
一、引言?
二、引入文件描述符fd
2、1 觀察fd的值
2、2 fd保存的位置
三、詳解文件描述符fd
3、1 為什么要有文件描述符呢
3、2 到底什么是文件操作符呢
四、文件描述符的使用
4、1 驗(yàn)證文件描述符
4、1、1 驗(yàn)證stdin、stdout、stdout
4、1、2 驗(yàn)證fd值的大小順序
4、2 輸入輸出重定向
4、2、1 dup2的使用?
?五、Linux下一切皆文件
???♂??作者:@Ggggggtm????♂?
???專欄:Linux從入門到精通? ??
???標(biāo)題:文件描述符??
????寄語:與其忙著訴苦,不如低頭趕路,奮路前行,終將遇到一番好風(fēng)景????
一、引言?
? 在Linux操作系統(tǒng)中,文件描述符是一種用于訪問文件或輸入/輸出資源的抽象概念,它是為了更有效地管理和操作文件、設(shè)備、套接字等資源而引入的。文件描述符的作用和重要性在操作系統(tǒng)和編程中具有深遠(yuǎn)意義。
? 通過?文件操作(C語言vs系統(tǒng)調(diào)用)上篇文章?文件操作(C語言vs系統(tǒng)調(diào)用)?對(duì)系統(tǒng)調(diào)用 open的講解后,我們知道文件描述符是一個(gè)整數(shù)。當(dāng)時(shí)它代表的含義是什么呢?又有什么意義呢?本篇文章會(huì)對(duì)文件描述符進(jìn)行詳細(xì)解釋。
二、引入文件描述符fd
2、1 觀察fd的值
? 我們學(xué)習(xí)系統(tǒng)調(diào)用后,知道open調(diào)用完之后,會(huì)返回一個(gè)整型的值,該值就是文件描述符。那我們不妨打印出來觀察一下fd值的大小和規(guī)律。代碼如下:
#include<stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> int main() { int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd1); int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd2); int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd3); int fd4 = open("log4.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd4); close(fd1); close(fd2); close(fd3); close(fd4); return 0; }
? 上述代碼就是打開了四個(gè)文件,然后打印他們的文件描述符fd的值。我們看輸出結(jié)果:
? 我們驚奇的發(fā)現(xiàn),文件描述符的值好像是有規(guī)律的!從上述打印的結(jié)果觀察出,fd的值是從3開始,依次往上加一遞增的。那么問題來了:為什么從3開始呢?有0、1、2嗎?
? 我們?cè)趯W(xué)習(xí)C語言時(shí),可能會(huì)聽說過:當(dāng)我們運(yùn)行程序時(shí),系統(tǒng)會(huì)默認(rèn)幫我們打開標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤。那標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯(cuò)誤到底是什么呢?
? 首先我們要知道Linux下,一切皆文件。難道顯示器和鍵盤也是文件嗎?答案是是的?。。ê竺嫖覀円矔?huì)對(duì)Linux下一切皆文件進(jìn)行講解)沒錯(cuò),當(dāng)程序運(yùn)行起來時(shí),系統(tǒng)會(huì)默認(rèn)幫我們打開三個(gè)標(biāo)準(zhǔn)文件的!而這三個(gè)文件分別對(duì)應(yīng)的fd值就是0、1、2。
2、2 fd保存的位置
? 我們知道當(dāng)程序運(yùn)行起來時(shí),系統(tǒng)會(huì)默認(rèn)幫我們打開三個(gè)標(biāo)準(zhǔn)文件后,那么這三個(gè)文件描述符fd的只會(huì)被保存起來的。如果不保存起來,怎么向標(biāo)準(zhǔn)輸出,也就是顯示器打印值呢?或者又怎么從標(biāo)準(zhǔn)輸入,也就是鍵盤讀取值呢?所以他們的fd值一定會(huì)被保存起來的。那么問題來了,這個(gè)值保存在了哪里了呢?
? 上述調(diào)用fopen時(shí),返回值為FILE* ,F(xiàn)ILE是什么類型呢?FILE就是一個(gè)結(jié)構(gòu)體!fd其實(shí)就是保存在了一個(gè)結(jié)構(gòu)體,該結(jié)構(gòu)體就是一個(gè)文件結(jié)構(gòu)體FILE。該結(jié)構(gòu)體中包含的文件的我們進(jìn)場(chǎng)所說到的緩沖區(qū)和文件描述符fd。
struct _IO_FILE { int _flags; /* High-order word is _IO_MAGIC; rest is flags. */ #define _IO_file_flags _flags //緩沖區(qū)相關(guān) /* The following pointers correspond to the C++ streambuf protocol. */ /* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */ char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; //封裝的文件描述符 #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; /* This used to be _offset but it's too small. */ #define __HAVE_COLUMN /* temporary */ /* 1+column number of pbase(); 0 is unknown. */ unsigned short _cur_column; signed char _vtable_offset; char _shortbuf[1]; /* char* _save_gptr; char* _save_egptr; */ _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
三、詳解文件描述符fd
3、1 為什么要有文件描述符呢
? 到這里會(huì)一直有疑問:到底什么是文件描述符呢(就是一個(gè)整數(shù)嗎)?為什么要有文件描述符呢?接下來會(huì)詳細(xì)解釋。
? 首先,一個(gè)進(jìn)程可能會(huì)打開很對(duì)文件。上面的代碼中就打開了四個(gè)文件,如果想的話甚至可能會(huì)更多。內(nèi)存中不僅僅只有一個(gè)進(jìn)程吧!那么就很有可能在系統(tǒng)中打開了很多文件。那么操作系統(tǒng)要不要對(duì)這些文件進(jìn)行管理呢?必須要進(jìn)行管理?。。≡趺垂芾砟??先描述,后組織!??!
? 怎么描述呢?我們都知道,Linux操作系統(tǒng)使用C語言寫的。那么問題就轉(zhuǎn)換成了C語言用什么來描述一個(gè)對(duì)象呢?不就是結(jié)構(gòu)體嗎!關(guān)鍵問題來了:怎么進(jìn)行組織呢?這就與文件描述符有關(guān)系了。接著往下看。
? 當(dāng)一個(gè)程序運(yùn)行起來加載到內(nèi)存后,創(chuàng)建對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu),就會(huì)變成一個(gè)進(jìn)程。其中關(guān)鍵是進(jìn)程控制塊PCB。在Linux內(nèi)核中,PCB為task_struct。其中,task_struct中就包含了一個(gè)文件結(jié)構(gòu)體指針files_struct* fs。改指針就是指向的文件結(jié)構(gòu)體。重點(diǎn)是該文件結(jié)構(gòu)體中有一個(gè)指針數(shù)組file* fd_arry[](文件映射表),該指針數(shù)組指向的就是我們所打開的文件。我們所新打開的文件fd的值是文件描述表中最小的為空的位置。具體可結(jié)合下圖理解:
? 我們?cè)趯?duì)上述的組織過程進(jìn)行從頭到尾的詳細(xì)闡述一下:首先進(jìn)程調(diào)用fwrite?-> 找到所傳入的FILE*??-> 找到FILE中的fd?-> 調(diào)用內(nèi)部封裝的write(系統(tǒng)調(diào)用)?-> 找到進(jìn)程的task_struct -> 找files_struct* fs -> 找到?files_struct -> 扎到files_struct中的file* fd_array[] -> fd_array[fd] -> 該位置的值是一個(gè)指針,該指針指向的就是對(duì)應(yīng)的file -> 找到該文件后,再進(jìn)行操作。
? 相信到這里,你就會(huì)對(duì)為什么要引入文件操作符fd就會(huì)清楚了。一個(gè)很重要的原因就是便于管理文件和操作。
3、2 到底什么是文件操作符呢
? 剛開始我們只知道文件操作符是一個(gè)整數(shù),其具體到表的含義并不知道。通過對(duì)上述的了解后,我們也就知道了文件描述符是一個(gè)下標(biāo),是用來標(biāo)識(shí)和操作文件或者輸入輸出設(shè)備的整數(shù)。每個(gè)打開的文件(包括標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出)都會(huì)被分配一個(gè)唯一的文件描述符。文件描述符的使用主要有以下幾個(gè)方面:
- 文件操作:通過文件描述符,我們可以對(duì)文件進(jìn)行讀取、寫入、定位等操作。例如,可以使用文件描述符來打開、關(guān)閉、讀取、寫入文件。
- 輸入輸出重定向:文件描述符可以用于在程序運(yùn)行時(shí)動(dòng)態(tài)地將輸入輸出重定向到其他文件。例如,可以將程序的輸出重定向到文件中,或者將文件作為程序的輸入。
- 管道通信:文件描述符可以用于實(shí)現(xiàn)進(jìn)程間的通信,其中最常見的方式是使用管道。通過創(chuàng)建管道,并使用文件描述符將數(shù)據(jù)從一個(gè)進(jìn)程傳遞給另一個(gè)進(jìn)程。
? 接下來下面我們會(huì)對(duì)上述的三個(gè)使用進(jìn)行講解的。
四、文件描述符的使用
4、1 驗(yàn)證文件描述符
4、1、1 驗(yàn)證stdin、stdout、stdout
? 首先我們要驗(yàn)證三個(gè)標(biāo)準(zhǔn):stdin、stdout、stdout中是否包含文件描述符fd。首先stdin、stdout、stdout是數(shù)據(jù)類型什么類型的呢?結(jié)合我們所學(xué)的文件操作函數(shù),數(shù)據(jù)類型不就是FILE*嗎。其中封裝的 _fileno 就是我們所說的文件描述符fd。驗(yàn)證代碼如下:
#include<stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> int main() { //_fileno就是文件描述符fd printf("%d\n%d\n%d\n",stdin->_fileno,stdout->_fileno,stderr->_fileno); int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd1); close(fd1); return 0; }
? 輸出結(jié)果如下:
4、1、2 驗(yàn)證fd值的大小順序
? 我們上述提到:新打開的文件fd的值是文件描述表中最小的為空的位置。怎么證明呢?不要忘記了可以通過close結(jié)合fd,進(jìn)行關(guān)閉系統(tǒng)默認(rèn)打開的標(biāo)準(zhǔn)文件。驗(yàn)證代碼如下:
#include<stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> int main() { close(0) int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd1); close(fd1); return 0; }
? 上述代碼就是把fd值為0(標(biāo)準(zhǔn)輸入)文件關(guān)閉,我們?cè)傩聞?chuàng)建文件,在觀察它的fd值。結(jié)果如下:
? 確實(shí),值為0了。我們?cè)谕ㄟ^下述代碼再次驗(yàn)證:
#include<stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> int main() { close(0); int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd1); int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw- printf("open success, fd: %d\n", fd2); close(fd1); close(fd2); return 0; }
? 輸出結(jié)果如下:?
?
? 確實(shí),我們只關(guān)閉了fd值為0(標(biāo)準(zhǔn)輸入)文件關(guān)閉,同時(shí)打開兩個(gè)文件。其中一個(gè)fd的值為0,另一個(gè)為3。我們所述的是正確的。
4、2 輸入輸出重定向
?我們平常的輸入是從標(biāo)準(zhǔn)輸入(鍵盤)讀取,輸出往標(biāo)準(zhǔn)輸出(顯示器)打印!輸入和輸出重定向就是不再從標(biāo)準(zhǔn)輸入讀取,向標(biāo)準(zhǔn)輸出打印了,就是從指定的文件讀取,或只輸入到指定文件。什么原理呢?
? 首先,我們知道默認(rèn)是輸出到標(biāo)準(zhǔn)輸出(顯示器)上。我們能不能關(guān)閉標(biāo)準(zhǔn)輸出,然后打開一個(gè)我們想輸出到指定的文件,這是新打開的文件fd的值不就是我們剛剛關(guān)閉的標(biāo)準(zhǔn)輸出文件的fd的值嘛?。。?/span>?根據(jù)我們上述詳解文件描述知道,操作系統(tǒng)內(nèi)部只認(rèn)識(shí)文件標(biāo)識(shí)符fd,并不認(rèn)識(shí)所謂的stdin、stdout。此時(shí)新打開的文件不就稱為了標(biāo)準(zhǔn)輸出嗎?。?!我們可結(jié)合下圖理解:
? 通過上述講解,思路是有了。我們?cè)倏创a實(shí)現(xiàn):
#include<stdio.h> #include<sys/types.h> #include<sys/stat.h> #include<fcntl.h> #include<unistd.h> #include<string.h> int main() { close(1); int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT); if(fd<0) { perror("open"); return 1; } printf("fd: %d\n", fd); // stdout->FILE{fileno=1}->log.txt printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); fprintf(stdout, "hello fprintf\n"); const char *s = "hello fwrite\n"; fwrite(s, strlen(s), 1, stdout); printf("fd: %d\n", fd); fflush(stdout); close(fd); return 0; }
? ?我們?cè)賮砜摧敵鼋Y(jié)果:
? 確實(shí)不在輸出到屏幕上,而是我們制定的文件。這不就是輸出重定向嗎。?
4、2、1 dup2的使用?
? dup2函數(shù)的作用有兩個(gè)方面:一是復(fù)制文件描述符,二是重新分配文件描述符。
- 復(fù)制文件描述符:
dup2
可以用來復(fù)制一個(gè)已打開的文件描述符,創(chuàng)建一個(gè)新的文件描述符,使其指向相同的文件、管道或套接字。這種方式可以實(shí)現(xiàn)重定向輸入、輸出或錯(cuò)誤流的功能。- 重新分配文件描述符: 使用
dup2
可以將一個(gè)文件描述符重新指定為指定的文件、管道或套接字。如果新的文件描述符已經(jīng)被打開,則系統(tǒng)會(huì)自動(dòng)關(guān)閉它。? 這里我們主要講解復(fù)制文件描述符。
? 在每次輸入重定向前,都需要調(diào)用close關(guān)閉標(biāo)準(zhǔn)輸出,未免會(huì)有點(diǎn)麻煩。有沒有其他的方法呢?答案是有的。我們可以看一下dup系列的系統(tǒng)調(diào)用接口。如下圖:
? 這里我們就講解dup2的使用。?具體如下圖:
? ?一共是有兩個(gè)參數(shù):oldfd、newfd,?newfd是拷貝oldfd得到的。
? 我們想要的效果是:把新打開的文件fd值為3的地址拷貝到fd值為1的地址處。那么結(jié)合我們上面說到newfd是拷貝oldfd得到的。那么dup2的使用方法不就是dup2(3,1)。我們?cè)倏摧敵鲋囟ㄏ虻拇a:
#include<sys/types> #include<sys/stat.h> #include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <string.h> int main() { int fd = open("file.txt", O_WRONLY | O_CREAT, 0644); if (fd < 0) { perror("open"); return -1; } // 復(fù)制文件描述符 int newfd = dup2(fd, stdout); // 將標(biāo)準(zhǔn)輸出重定向到文件 dup2(3,1); if (newfd < 0) { perror("dup2"); return -1; } printf("Hello, World!\n"); // 輸出將被重定向到文件 close(fd); return 0; }
? 運(yùn)行結(jié)果如下:
? 輸入重定向的原理與輸出重定向的原理相同,本篇文章就不再做過多解釋。?
?五、Linux下一切皆文件
??在Linux中,"一切皆文件"(Everything is a file)是一個(gè)重要的概念,用于描述Linux操作系統(tǒng)中所有資源和設(shè)備都以文件的形式進(jìn)行訪問和處理。
? 這個(gè)概念可以理解為,無論是硬盤上的文件、網(wǎng)卡、設(shè)備、進(jìn)程等,都被抽象為文件的形式存在。在Linux系統(tǒng)中,通過文件系統(tǒng)(File System)來管理和訪問這些資源。
具體來說,"一切皆文件"可以被解釋為:
文件:在Linux中,普通的文件就是我們常見的文本文件、二進(jìn)制文件等。它們被組織成一個(gè)層次結(jié)構(gòu)的目錄樹,通過路徑來定位和訪問。
目錄:目錄也是一種文件,它包含了其他文件和目錄的信息。通過目錄,可以組織和管理文件的層次結(jié)構(gòu)。
設(shè)備文件:Linux將硬件設(shè)備(如磁盤、網(wǎng)絡(luò)接口等)和虛擬設(shè)備(例如打印機(jī),輸入設(shè)備)都看作是文件來處理。通過設(shè)備文件,可以讀取和寫入設(shè)備的數(shù)據(jù)。文章來源:http://www.zghlxwxcb.cn/news/detail-650136.html
- 進(jìn)程:在Linux系統(tǒng)中,每個(gè)正在運(yùn)行的進(jìn)程都有與之關(guān)聯(lián)的文件。通過讀取和寫入相應(yīng)的文件,可以與進(jìn)程進(jìn)行通信和交互。
? 通過將所有資源都抽象為文件,Linux提供了一套統(tǒng)一的接口,可以使用相同的命令和工具來訪問和管理不同的資源。這種統(tǒng)一性使得Linux操作系統(tǒng)更加靈活和強(qiáng)大,同時(shí)也方便了開發(fā)者和系統(tǒng)管理員進(jìn)行各種操作和配置。文章來源地址http://www.zghlxwxcb.cn/news/detail-650136.html
到了這里,關(guān)于【Linux從入門到精通】文件描述符詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!