在Linux系統中一切皆可以看成是文件,文件又可分為:普通文件、目錄文件、連結文件和設備文件。文件描述符(file descriptor)是內核為了高效管理已被打開的文件所創建的索引,其是一個非負整數(通常是小整數),用於指代被打開的文件,所有執行I/O操作的系統呼叫都通過檔案描述符。程式剛啟動的時候,0是標準輸入,1是標準輸出,2是標準錯誤。如果此時去開啟一個新的文件,它的文件描述會是3。
檔案描述子的運算(如: open(),creat(),close(),read()))返回的是一個檔案描述子,它是int類型的整數,即fd,其本質是檔案描述子表中的下標,它起到一個索引的作用,進程透過PCB中的檔案描述子表找到該fd所所指的文件指標filp。每個進程在PCB(Process Control Block)即進程控制塊中都保存著一份文件描述符表,而文件描述符就是這個表的索引,文件描述表中每個表項都有一個指向已打開文件的指標; 已開啟的檔案在核心中以file
結構體表示,檔案描述子表中的指標指向file
結構體。每打開一個文件,fd預設從最小的未被使用的下標開始分配。文件描述符的缺點:不能移植到UNIX以外的系統上去,也不直觀。
下面畫張圖來表示它們之間的關係:
而每個檔案中又主要包含以下這些資訊:
在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).查看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 -n11 102413 [root@localhost ~]#
Linux下最大檔案描述子的限制有兩個方面,一個是用戶級的限制,另外一個則是系統級限制。
系統級限制:sysctl命令和proc檔案系統中查看到的數值是一樣的,這屬於系統級限制,它是限制所有使用者開啟檔案描述符的總和
用戶級限制:ulimit指令看到的是用戶級的最大檔案描述符限制,也就是說每個用戶登入後執行的程式佔用檔案描述符的總數不能超過這個限制
(2).修改檔案描述子的值
1 [root@localhost ~]# ulimit-SHn 102402 [root@localhost ~]# ulimit -n3 102404 [root@localhost ~]#
以上的修改只對目前會話起作用,是臨時性的,如果需要永久修改,則要修改如下:
1 [root@localhost ~]# grep -vE'^$|^#' /etc/security/limits.conf2 * hard nofile 40963 [root@localhost ~]#
1 //默认配置文件中只有hard选项,soft 指的是当前系统生效的设置值,hard 表明系统中所能设定的最大值2 [root@localhost ~]# grep -vE'^$|^#' /etc/security/limits.conf3 * hard nofile 102404 * soft nofile 102405 [root@localhost ~]#6 // soft<=hard soft的限制不能比hard限制高
(3).修改系統限制
1 [root@localhost ~]# sysctl -wfs.file-max=4000002 fs.file-max = 4000003 [root@localhost ~]# echo350000 > /proc/sys/fs/file-max //重启后失效4 [root@localhost ~]# cat /proc/sys/fs/file-max5 3500006 [root@localhost ~]#
//以上是暫時修改檔案描述符
//永久修改把fs.file-max=400000加入/etc/sysctl.conf中,使用sysctl -p即可
下面的程序,打開/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 else21 printf("closefile fd.c success!\n");22 exit(0);23 }
执行结果:
进程通过系统调用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节点,于是虚拟文件系统就与实际文件系统的操作衔接起来了。
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結構之間的關係可以用下圖來表示:
#
#以上是檔案描述符與FILE概念介紹的詳細內容。更多資訊請關注PHP中文網其他相關文章!