1. 文件描述符(重點) 在Linux系統中一切皆可以看成是文件,文件又可分為:普通文件、目錄文件、鏈接文件和設備文件。文件描述符(file descriptor)是內核為了高效管理已被打開的文件所創建的索引,其是一個非負整數(通常是小整數),用於指代被打開的文件,所有執行I/O操作的系統調用都通過 ...
1. 文件描述符(重點)
在Linux系統中一切皆可以看成是文件,文件又可分為:普通文件、目錄文件、鏈接文件和設備文件。文件描述符(file descriptor)是內核為了高效管理已被打開的文件所創建的索引,其是一個非負整數(通常是小整數),用於指代被打開的文件,所有執行I/O操作的系統調用都通過文件描述符。程式剛剛啟動的時候,0是標準輸入,1是標準輸出,2是標準錯誤。如果此時去打開一個新的文件,它的文件描述符會是3。
1.1概念介紹
文件描述符的操作(如: open(),creat(),close(),read()))返回的是一個文件描述符,它是int類型的整數,即fd,其本質是文件描述符表中的下標,它起到一個索引的作用,進程通過PCB中的文件描述符表找到該fd所指向的文件指針filp。每個進程在PCB(Process Control Block)即進程式控制制塊中都保存著一份文件描述符表,文件描述符就是這個表的索引,文件描述表中每個表項都有一個指向已打開文件的指針; 已打開的文件在內核中用file
結構體表示,文件描述符表中的指針指向file
結構體。每打開一個文件,fd預設從最小的未被使用的下標開始分配。文件描述符的缺點:不能移植到UNIX以外的系統上去,也不直觀。
下麵畫張圖來表示它們之間的關係:
而每個文件中又主要包含以下這些信息:
1.2圖表解釋
在file
結構體中維護File Status Flag(file
結構體的成員f_flags
)和當前讀寫位置(file
結構體的成員f_pos
)。在上圖中,進程1和進程2都打開同一文件,但是對應不同的file
結構體,因此可以有不同的File Status Flag和讀寫位置。file
結構體中比較重要的成員還有f_count
,表示引用計數(Reference Count),後面我們會講到,dup
、fork
等系統調用會導致多個文件描述符指向同一個file
結構體,例如有fd1
和fd2
都引用同一個file
結構體,那麼它的引用計數就是2,當close(fd1)
時並不會釋放file
結構體,而只是把引用計數減到1,如果再close(fd2)
,引用計數就會減到0同時釋放file
結構體,這才真的關閉了文件。
每個file
結構體都指向一個file_operations
結構體,這個結構體的成員都是函數指針,指向實現各種文件操作的內核函數。比如在用戶程式中read
一個文件描述符,read
通過系統調用進入內核,然後找到這個文件描述符所指向的file
結構體,找到file
結構體所指向的file_operations
結構體,調用它的read
成員所指向的內核函數以完成用戶請求。在用戶程式中調用lseek
、read
、write
、ioctl
、open
等函數,最終都由內核調用file_operations
的各成員所指向的內核函數完成用戶請求。file_operations
結構體中的release
成員用於完成用戶程式的close
請求,之所以叫release
而不叫close
是因為它不一定真的關閉文件,而是減少引用計數,只有引用計數減到0才關閉文件。對於同一個文件系統上打開的常規文件來說,read
、write
等文件操作的步驟和方法應該是一樣的,調用的函數應該是相同的,所以圖中的三個打開文件的file
結構體指向同一個file_operations
結構體。如果打開一個字元設備文件,那麼它的read
、write
操作肯定和常規文件不一樣,不是讀寫磁碟的數據塊而是讀寫硬體設備,所以file
結構體應該指向不同的file_operations
結構體,其中的各種文件操作函數由該設備的驅動程式實現。
每個file
結構體都有一個指向dentry
結構體的指針,“dentry”是directory entry(目錄項)的縮寫。我們傳給open
、stat
等函數的參數的是一個路徑,例如/home/akaedu/a
,需要根據路徑找到文件的inode。為了減少讀盤次數,內核緩存了目錄的樹狀結構,稱為dentry cache,其中每個節點是一個dentry
結構體,只要沿著路徑各部分的dentry搜索即可,從根目錄/
找到home
目錄,然後找到akaedu
目錄,然後找到文件a
。dentry cache只保存最近訪問過的目錄項,如果要找的目錄項在cache中沒有,就要從磁碟讀到記憶體中。
每個dentry
結構體都有一個指針指向inode
結構體。inode
結構體保存著從磁碟inode讀上來的信息。在上圖的例子中,有兩個dentry,分別表示/home/akaedu/a
和/home/akaedu/b
,它們都指向同一個inode,說明這兩個文件互為硬鏈接。inode
結構體中保存著從磁碟分區的inode讀上來信息,例如所有者、文件大小、文件類型和許可權位等。每個inode
結構體都有一個指向inode_operations
結構體的指針,後者也是一組函數指針指向一些完成文件目錄操作的內核函數。和file_operations
不同,inode_operations
所指向的不是針對某一個文件進行操作的函數,而是影響文件和目錄佈局的函數,例如添加刪除文件和目錄、跟蹤符號鏈接等等,屬於同一文件系統的各inode
結構體可以指向同一個inode_operations
結構體。
inode
結構體有一個指向super_block
結構體的指針。super_block
結構體保存著從磁碟分區的超級塊讀上來的信息,例如文件系統類型、塊大小等。super_block
結構體的s_root
成員是一個指向dentry
的指針,表示這個文件系統的根目錄被mount
到哪裡,在上圖的例子中這個分區被mount
到/home
目錄下。
file
、dentry
、inode
、super_block
這幾個結構體組成了VFS(虛擬文件系統VFS,Virtual Filesystem)的核心概念。
1.3對文件描述符的操作
(1).查看Linux文件描述符
1 [root@localhost ~]# sysctl -a | grep -i file-max --color
3 fs.file-max = 392036
5 [root@localhost ~]# cat /proc/sys/fs/file-max
7 392036
9 [root@localhost ~]# ulimit -n
11 1024
13 [root@localhost ~]#
Linux下最大文件描述符的限制有兩個方面,一個是用戶級的限制,另外一個則是系統級限制。
系統級限制:sysctl命令和proc文件系統中查看到的數值是一樣的,這屬於系統級限制,它是限制所有用戶打開文件描述符的總和
用戶級限制:ulimit命令看到的是用戶級的最大文件描述符限制,也就是說每一個用戶登錄後執行的程式占用文件描述符的總數不能超過這個限制
(2).修改文件描述符的值
1 [root@localhost ~]# ulimit-SHn 10240
2 [root@localhost ~]# ulimit -n
3 10240
4 [root@localhost ~]#
以上的修改只對當前會話起作用,是臨時性的,如果需要永久修改,則要修改如下:
1 [root@localhost ~]# grep -vE'^$|^#' /etc/security/limits.conf
2 * hard nofile 4096
3 [root@localhost ~]#
1 //預設配置文件中只有hard選項,soft 指的是當前系統生效的設置值,hard 表明系統中所能設定的最大值
2 [root@localhost ~]# grep -vE'^$|^#' /etc/security/limits.conf
3 * hard nofile 10240
4 * soft nofile 10240
5 [root@localhost ~]#
6 // soft<=hard soft的限制不能比hard限制高
(3).修改系統限制
1 [root@localhost ~]# sysctl -wfs.file-max=400000
2 fs.file-max = 400000
3 [root@localhost ~]# echo350000 > /proc/sys/fs/file-max //重啟後失效
4 [root@localhost ~]# cat /proc/sys/fs/file-max
5 350000
6 [root@localhost ~]#
//以上是臨時修改文件描述符
//永久修改把fs.file-max=400000添加到/etc/sysctl.conf中,使用sysctl -p即可
1.4用程式查看文件描述符
下麵的程式,打開/home/shenlan/hello.c文件,如果此目錄下沒有hello.c文件,程式自動創建,程式中返回的文件描述符為3。因為進程啟動時,打開了標準輸入(0)、標準輸出(1)和標準出錯處理(2)三個文件,fd預設從最小的未被使用的下標開始分配,因此返回的文件描述符為3。
1 #include<stdio.h> 2 #include<sys/types.h> 3 #include<sys/stat.h> 4 #include<fcntl.h> 5 #include<stdlib.h> 6 int main() 7 { 8 int fd; 9 if((fd = open("/home/shenlan/fd.c",O_CREAT|O_WRONLY|O_TRUNC,0611))<0){ 10 perror("openfile fd.c error!\n"); 11 exit(1); 12 } 13 else{ 14 printf("openfile fd.c success:%d\n",fd); 15 } 16 if(close(fd) < 0){ 17 perror("closefile fd.c error!\n"); 18 exit(1); 19 } 20 else 21 printf("closefile fd.c success!\n"); 22 exit(0); 23 }
執行結果:
1.5進程打開一個文件的具體流程
進程通過系統調用open( )來打開一個文件,實質上是獲得一個文件描述符,以便進程通過文件描述符為連接對文件進行其他操作。進程打開文件時,會為該文件創建一個file對象,並把該file對象存入進程打開文件表中(文件描述符數組),進而確定了所打開文件的文件描述符。 open( )操作在內核里通過sys_open( )實現的,sys_open( )將創建文件的dentry、inode和file對象,併在file_struct結構體的進程打開文件表fd_array[NR_OPEN_DEFAULT]中尋找一個空閑表項,然後返回這個表項的下標(索引),即文件描述符。創建文件的file對象時,將file對象的f_op指向了所屬文件系統的操作函數集file_operations,而該函數集又來自具體文件的i節點,於是虛擬文件系統就與實際文件系統的操作銜接起來了。
2.C標準庫中的FILE結構和文件描述符
C語言中使用的是文件指針而不是文件描述符做為I/O的句柄."文件指針(file pointer)"指向進程用戶區中的一個被稱為FILE結構的數據結構。FILE結構包括一個緩衝區和一個文件描述符值.而文件描述符值是文件描述符表中的一個索引.從某種意義上說文件指針就是句柄的句柄。流(如: fopen)返回的是一個FILE結構指針, FILE結構是包含有文件描述符的,FILE結構函數可以看作是對fd直接操作的系統調用的封裝, 它的優點是帶有I/O緩存。
從文件描述符fd 到文件流 FILE* 的函數是
FILE* fdopen(int filedes,const char* mode);
早期的C標準庫中,FILE在stdio.h中定義;Turbo C中,參見譚浩強的《C程式設計》,FILE結構體中包含成員fd,即文件描述符。亦可以在安裝的Ubuntu系統的/usr/include/stdio.h中找到struct _IO_FILE結構體,這個結構體比較複雜,我們只關心需要的部分-文件描述符,但是在這個的結構體中,我們並沒有發現與文件描述符相關的諸如fd成員變數。此時,類型為int的_fileno結構體成員引起了我們的註意,但是不能確定其為文件描述符。因此寫個程式測試是最好的辦法,可以用以下的代碼測試:
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<sys/types.h> 4 #include<sys/stat.h> 5 #include<fcntl.h> 6 int main( ) 7 { 8 char buf[50] = {"ILOVE this game!"}; 9 FILE *myfile; 10 11 myfile = fopen("2.txt","w+"); 12 if(!myfile){ 13 printf("error:openfile failed!\n"); 14 } 15 printf("The openedfile's descriptor is %d\n",myfile->_fileno); 16 if(write(myfile->_fileno,buf,50)< 0){ 17 perror("error:writefile failed!\n"); 18 exit(1); 19 }else{ 20 printf("writefile successed!\n"); 21 } 22 exit(0); 23 }
程式中,使用fopen函數以讀寫打開2.txt文件,如果不存在2.txt文件,則創建此文件。並將其返回的FILE指針myfile。使用printf向標準終端列印出myfile->_fileno的值,並將myfile->_fileno作為文件描述符傳遞給write系統調用,向打開的文件寫入緩衝區數據。然後使用cat命令查看2.txt的內容。執行的結果如圖所示。_fileno的值為3,因為標準輸入、輸出、出錯為0、1、2。輸出結果如下:
因此,_fileno成員即為操作系統打開文件返回的句柄(windows系統)或文件描述符。深入學習可以閱讀人民郵電出版社《C標準庫》。當然還可以閱讀/glibc-2.9/manual/io.txti文件。Linux中,文件的描述符分配是從小到大逐個查詢文件描述符是否已經使用,然後再分配,也可以寫程式測試。
文件描述符表也稱文件描述符數組,其中存放了一個進程所打開的所有文件。文件描述符數組包含在進程打開的文件表files_struct結構中。在/include/linux/fdtable.h中定義,為一個指向file類型的指針數組---fd_array[NR_OPEN_DEFAULT],其中NR_OPEN_DEFAULT也在fdtable.h中定義,這是一個和具體的CPU體繫結構有關的變數,#define NR_OPEN_DEFAULTBITS_PER_LONG。
FILE結構和文件描述符、file結構之間的關係可以用下圖來表示: