成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Linux 文件描述符 fd 究竟是什么?

系統 Linux
一切的本源是通過 fd 來操作的,那么,這個 fd 究竟是什么?就這個點我們深入剖析。

 [[400487]]

前情概要

 我們知道有兩種文件讀寫的方式,一種是系統調用的方式,操作的對象是一個整數 fd,另一種是 Go 標準庫自己封裝的標準庫 IO ,操作對象是 Go 封裝的 file 結構體,但其內部還是針對整數 fd 的操作。所以一切的本源是通過 fd 來操作的,那么,這個 fd 究竟是什么?就這個點我們深入剖析。

fd 是什么?

fd 是 File descriptor 的縮寫,中文名叫做:文件描述符。文件描述符是一個非負整數,本質上是一個索引值(這句話非常重要)。

什么時候拿到的 fd ?

當打開一個文件時,內核向進程返回一個文件描述符( open 系統調用得到 ),后續 read、write 這個文件時,則只需要用這個文件描述符來標識該文件,將其作為參數傳入 read、write 。

fd 的值范圍是什么?

在 POSIX 語義中,0,1,2 這三個 fd 值已經被賦予特殊含義,分別是標準輸入( STDIN_FILENO ),標準輸出( STDOUT_FILENO ),標準錯誤( STDERR_FILENO )。

文件描述符是有一個范圍的:0 ~ OPEN_MAX-1 ,最早期的 UNIX 系統中范圍很小,現在的主流系統單就這個值來說,變化范圍是幾乎不受限制的,只受到系統硬件配置和系統管理員配置的約束。

你可以通過 ulimit 命令查看當前系統的配置: 

  1. ➜  ulimit -n  
  2. 4864 

如上,我系統上進程默認最多打開 4864 文件。

窺探 Linux 內核

fd 究竟是什么?必須去 Linux 內核看一眼。

用戶使用系統調用 open 或者 creat 來打開或創建一個文件,用戶態得到的結果值就是 fd ,后續的 IO 操作全都是用 fd 來標識這個文件,可想而知內核做的操作并不簡單,我們接下來就是要揭開這層面紗。

task_struct

首先,我們知道進程的抽象是基于 struct task_struct 結構體,這是 Linux 里面最復雜的結構體之一 ,成員字段非常多,我們今天不需要詳解這個結構體,我稍微簡化一下,只提取我們今天需要理解的字段如下: 

  1. struct task_struct {  
  2.     // ...  
  3.     /* Open file information: */  
  4.     struct files_struct     *files;  
  5.     // ...  

files; 這個字段就是今天的主角之一,files 是一個指針,指向一個為 struct files_struct 的結構體。這個結構體就是用來管理該進程打開的所有文件的管理結構。

重點理解一個概念:

struct task_struct 是進程的抽象封裝,標識一個進程,在 Linux 里面的進程各種抽象視角,都是這個結構體給到你的。當創建一個進程,其實也就是 new 一個 struct task_struct 出來;

files_struct

好,上面通過進程結構體引出了 struct files_struct 這個結構體。這個結構體管理某進程打開的所有文件的管理結構,這個結構體本身是比較簡單的: 

  1. /*  
  2.  * Open file table structure  
  3.  */  
  4. struct files_struct {  
  5.     // 讀相關字段  
  6.     atomic_t count;  
  7.     bool resize_in_progress;  
  8.     wait_queue_head_t resize_wait;  
  9.     // 打開的文件管理結構  
  10.     struct fdtable __rcu *fdt;  
  11.     struct fdtable fdtab;  
  12.     // 寫相關字段  
  13.     unsigned int next_fd;  
  14.     unsigned long close_on_exec_init[1];  
  15.     unsigned long open_fds_init[1];  
  16.     unsigned long full_fds_bits_init[1];  
  17.     struct file * fd_array[NR_OPEN_DEFAULT];  
  18. }; 

files_struct 這個結構體我們說是用來管理所有打開的文件的。怎么管理?本質上就是數組管理的方式,所有打開的文件結構都在一個數組里。這可能會讓你疑惑,數組在那里?有兩個地方:

  1.  struct file * fd_array[NR_OPEN_DEFAULT] 是一個靜態數組,隨著 files_struct 結構體分配出來的,在 64 位系統上,靜態數組大小為 64;
  2.  struct fdtable 也是個數組管理結構,只不過這個是一個動態數組,數組邊界是用字段描述的;

思考:為什么會有這種靜態 + 動態的方式?

性能和資源的權衡 !大部分進程只會打開少量的文件,所以靜態數組就夠了,這樣就不用另外分配內存。如果超過了靜態數組的閾值,那么就動態擴展。

可以回憶下,這個是不是跟 inode 的直接索引,一級索引的優化思路類似。

fdtable

簡單介紹下 fdtable 結構體,這個結構體就是封裝用來管理 fd 的結構體,fd 的秘密就在這個里面。簡化結構體如下: 

  1. struct fdtable {  
  2.     unsigned int max_fds;  
  3.     struct file __rcu **fd;      /* current fd array */  
  4. }; 

注意到 fdtable.fd 這個字段是一個二級指針,什么意思?

就是指向 fdtable.fd 是一個指針字段,指向的內存地址還是存儲指針的(元素指針類型為  struct file * )。換句話說,fdtable.fd 指向一個數組,數組元素為指針(指針類型為 struct file *)。

其中 max_fds 指明數組邊界。

files_struct 小結

file_struct 本質上是用來管理所有打開的文件的,內部的核心是由一個靜態數組和動態數組管理結構實現。

還記得上面我們說文件描述符 fd 本質上就是索引嗎?這里就把概念接上了,fd 就是這個數組的索引,也就是數組的槽位編號而已。 通過非負數 fd 就能拿到對應的 struct file 結構體的地址。

我們把概念串起來(注意,這里為了突出 fd 的本質,把 fdtable 管理簡化掉):

  •  fd 真的就是 files 這個字段指向的指針數組的索引而已(僅此而已)。通過 fd 能夠找到對應文件的 struct file 結構體;

file

現在我們知道了 fd 本質是數組索引,數組元素是 struct file 結構體的指針。那么這里就引出了一個 struct file 的結構體。這個結構體又是用來干什么的呢?

這個結構體是用來表征進程打開的文件的。簡化結構如下: 

  1. struct file {  
  2.     // ...  
  3.     struct path                     f_path;  
  4.     struct inode                    *f_inode;  
  5.     const struct file_operations    *f_op;  
  6.     atomic_long_t                    f_count;  
  7.     unsigned int                     f_flags;  
  8.     fmode_t                          f_mode;  
  9.     struct mutex                     f_pos_lock;  
  10.     loff_t                           f_pos;  
  11.     struct fown_struct               f_owner;  
  12.     // ...  

這個結構體非常重要,它標識一個進程打開的文件,下面解釋 IO 相關的幾個最重要的字段:

  •  f_path :標識文件名
  •  f_inode :非常重要的一個字段,inode 這個是 vfs 的 inode 類型,是基于具體文件系統之上的抽象封裝;
  •  f_pos :這個字段非常重要,偏移,對,就是當前文件偏移。還記得上一篇 IO 基礎里也提過偏移對吧,指的就是這個,f_pos 在 open 的時候會設置成默認值,seek 的時候可以更改,從而影響到 write/read 的位置;

思考問題

思考問題一:files_struct 結構體只會屬于一個進程,那么struct file 這個結構體呢,是只會屬于某一個進程?還是可能被多個進程共享?

劃重點:struct file 是屬于系統級別的結構,換句話說是可以共享與多個不同的進程。

思考問題二:什么時候會出現多個進程的  fd  指向同一個 file  結構體?

比如 fork  的時候,父進程打開了文件,后面 fork 出一個子進程。這種情況就會出現共享 file 的場景。如圖:

思考問題三:在同一個進程中,多個 fd 可能指向同一個 file 結構嗎?

可以。dup  函數就是做這個的。 

  1. #include <unistd.h>  
  2. int dup(int oldfd);  
  3. int dup2(int oldfd, int newfd); 

inode

我們看到 struct file 結構體里面有一個 inode 的指針,也就自然引出了 inode 的概念。這個指向的 inode 并沒有直接指向具體文件系統的 inode ,而是操作系統抽象出來的一層虛擬文件系統,叫做 VFS ( Virtual File System ),然后在 VFS 之下才是真正的文件系統,比如 ext4 之類的。

完整架構圖如下:

思考:為什么會有這一層封裝呢?

其實很容里理解,就是解耦。如果讓 struct file 直接和 struct ext4_inode 這樣的文件系統對接,那么會導致 struct file 的處理邏輯非常復雜,因為每對接一個具體的文件系統,就要考慮一種實現。所以操作系統必須把底下文件系統屏蔽掉,對外提供統一的 inode 概念,對下定義好接口進行回調注冊。這樣讓 inode 的概念得以統一,Unix 一切皆文件的基礎就來源于此。

再來看一樣 VFS 的 inode 的結構: 

  1. struct inode {  
  2.     // 文件相關的基本信息(權限,模式,uid,gid等)  
  3.     umode_t             i_mode;  
  4.     unsigned short      i_opflags;  
  5.     kuid_t              i_uid;  
  6.     kgid_t              i_gid;  
  7.     unsigned int        i_flags; 
  8.     // 回調函數  
  9.     const struct inode_operations   *i_op;  
  10.     struct super_block              *i_sb;  
  11.     struct address_space            *i_mapping;  
  12.     // 文件大小,atime,ctime,mtime等  
  13.     loff_t              i_size;  
  14.     struct timespec64   i_atime;  
  15.     struct timespec64   i_mtime;  
  16.     struct timespec64   i_ctime;  
  17.     // 回調函數  
  18.     const struct file_operations    *i_fop;  
  19.     struct address_space            i_data;  
  20.     // 指向后端具體文件系統的特殊數據 
  21.     void    *i_private;     /* fs or device private pointer */  
  22. }; 

其中包括了一些基本的文件信息,包括 uid,gid,大小,模式,類型,時間等等。

一個 vfs 和 后端具體文件系統的紐帶:i_private 字段。**用來傳遞一些具體文件系統使用的數據結構。

至于 i_op 回調函數在構造 inode 的時候,就注冊成了后端的文件系統函數,比如 ext4 等等。

思考問題:通用的 VFS 層,定義了所有文件系統通用的 inode,叫做 vfs inode,而后端文件系統也有自身特殊的 inode 格式,該格式是在 vfs inode 之上進行擴展的,怎么通過 vfs inode 怎么得到具體文件系統的 inode 呢?

下面以 ext4 文件系統舉例(因為所有的文件系統套路一樣),ext4 的 inode 類型是 struct ext4_inode_info 。

劃重點:方法其實很簡單,這個是屬于 c 語言一種常見的(也是特有)編程手法:強轉類型。vfs inode 出生就和 ext4_inode_info 結構體分配在一起的,直接通過 vfs inode 結構體的地址強轉類型就能得到 ext4_inode_info 結構體。 

  1. struct ext4_inode_info {  
  2.     // ext4 inode 特色字段  
  3.     // ...      
  4.     // 重要!!!  
  5.     struct inode    vfs_inode;    
  6. }; 

舉個例子,現已知 inode 地址和 vfs_inode 字段的內偏移如下:

  •  inode 的地址為 0xa89be0;
  •  ext4_inode_info 里有個內嵌字段 vfs_inode,類型為 struct inode ,該字段在結構體內偏移為 64 字節;

則可以得到:

ext4_inode_info 的地址為

  1. (struct ext4_inode_info *)(0xa89be0 - 64) 

強轉方法使用了一個叫做 container_of 的宏,如下: 

  1. // 強轉函數  
  2. static inline struct ext4_inode_info *EXT4_I(struct inode *inode)  
  3.  
  4.    return container_of(inode, struct ext4_inode_info, vfs_inode);  
  5.  
  6. // 強轉實際封裝  
  7. #define container_of(ptr, type, member) \  
  8.     (type *)((char *)(ptr) - (char *) &((type *)0)->member)  
  9. #endif 

所以,你懂了嗎?

分配 inode 的時候,其實分配的是 ext4_inode_info 結構體,包含了 vfs inode,然后對外給出去 vfs_inode 字段的地址即可。VFS 層拿 inode 的地址使用,底下文件系統強轉類型后,取外層的 inode 地址使用。

舉個 ext4 文件系統的例子: 

  1. static struct inode *ext4_alloc_inode(struct super_block *sb)  
  2.  
  3.     struct ext4_inode_info *ei;  
  4.     // 內存分配,分配 ext4_inode_info 的地址  
  5.     ei = kmem_cache_alloc(ext4_inode_cachep, GFP_NOFS);  
  6.     // ext4_inode_info 結構體初始化  
  7.     // 返回 vfs_inode 字段的地址  
  8.     return &ei->vfs_inode;  

vfs 拿到的就是這個 inode 地址。

劃重點:inode 的內存由后端文件系統分配,vfs inode 結構體內嵌在不同的文件系統的 inode 之中。不同的層次用不同的地址,ext4 文件系統用 ext4_inode_info 的結構體的地址,vfs 層用 ext4_inode_info.vfs_inode 字段的地址。

這種用法在 C 語言編程中很常見,算是 C 的特色了(仔細想想,這種用法和面向對象的多態的實現異曲同工)。

思考問題:怎么理解 vfs inode 和 ext2_inode_info,ext4_inode_info 等結構體的區別?

所有文件系統共性的東西抽象到 vfs inode ,不同文件系統差異的東西放在各自的 inode 結構體中。

小結梳理

當用戶打開一個文件,用戶只得到了一個 fd 句柄,但內核做了很多事情,梳理下來,我們得到幾個關鍵的數據結構,這幾個數據結構是有層次遞進關系的,我們簡單梳理下:

  1.  進程結構 task_struct :表征進程實體,每一個進程都和一個 task_struct 結構體對應,其中 task_struct.files 指向一個管理打開文件的結構體 fiels_struct ;
  2.  文件表項管理結構 files_struct :用于管理進程打開的 open 文件列表,內部以數組的方式實現(靜態數組和動態數組結合)。返回給用戶的 fd 就是這個數組的編號索引而已,索引元素為 file 結構;
  •  files_struct 只從屬于某進程;

    3.  文件 file 結構:表征一個打開的文件,內部包含關鍵的字段有:當前文件偏移,inode 結構地址;

  •  該結構雖然由進程觸發創建,但是 file  結構可以在進程間共享;

    4.   vfs inode 結構體:文件 file 結構指向 的是 vfs 的 inode ,這個是操作系統抽象出來的一層,用于屏蔽后端各種各樣的文件系統的 inode 差異;

  •  inode 這個具體進程無關,是文件系統級別的資源;

    5.  ext4 inode 結構體(指代具體文件系統 inode ):后端文件系統的 inode 結構,不同文件系統自定義的結構體,ext2 有 ext2_inode_info,ext4 有ext4_inode_info,minix 有 minix_inode_info,這些結構里都是內嵌了一個 vfs inode 結構體,原理相同;

完整的架構圖:

思考實驗

現在我們已經徹底了解 fd 這個所謂的非負整數代表的深層含義了,我們可以準備一些 IO 的思考舉一反三。

文件讀寫( IO )的時候會發生什么?

  •  在完成 write 操作后,在文件 file  中的當前文件偏移量會增加所寫入的字節數,如果這導致當前文件偏移量超處了當前文件長度,則會把 inode 的當前長度設置為當前文件偏移量(也就是文件變長)
  •  O_APPEND  標志打開一個文件,則相應的標識會被設置到文件 file  狀態的標識中,每次對這種具有追加寫標識的文件執行 write 操作的時候,file 的當前文件偏移量首先會被設置成 inode 結構體中的文件長度,這就使得每次寫入的數據都追加到文件的當前尾端處(該操作對用戶態提供原子語義);
  •  若一個文件 seek 定位到文件當前的尾端,則 file 中的當前文件偏移量設置成 inode 的當前文件長度;
  •  seek 函數值修改 file 中的當前文件偏移量,不進行任何 I/O 操作;
  •  每個進程對有它自己的 file,其中包含了當前文件偏移,當多個進程寫同一個文件的時候,由于一個文件 IO 最終只會是落到全局的一個 inode 上,這種并發場景則可能產生用戶不可預期的結果;

總結

回到初心,理解 fd 的概念有什么用?

一切 IO 的行為到系統層面都是以 fd 的形式進行。無論是 C/C++,Go,Python,JAVA 都是一樣,任何語言都是一樣,這才是最本源的東西,理解了 fd 關聯的一系列結構,你才能對 IO 游刃有余。

簡要的總結:

  1.  從姿勢上來講,用戶 open 文件得到一個非負數句柄 fd,之后針對該文件的 IO 操作都是基于這個 fd ;
  2.  文件描述符 fd 本質上來講就是數組索引,fd 等于 5 ,那對應數組的第 5 個元素而已,該數組是進程打開的所有文件的數組,數組元素類型為 struct file;
  3.  結構體 task_struct 對應一個抽象的進程,files_struct 是這個進程管理該進程打開的文件數組管理器。fd 則對應了這個數組的編號,每一個打開的文件用 file 結構體表示,內含當前偏移等信息;
  4.  file 結構體可以為進程間共享,屬于系統級資源,同一個文件可能對應多個 file 結構體,file 內部有個 inode 指針,指向文件系統的 inode;
  5.  inode 是文件系統級別的概念,只由文件系統管理維護,不因進程改變( file 是進程出發創建的,進程 open 同一個文件會導致多個 file ,指向同一個 inode );

回顧一眼架構圖:

~完~

后記

內核把最復雜的活干了,只暴露給您最簡單的一個非負整數 fd 。所以,絕大部分場景會用fd 就行,倒不用想太多。當然如果能再深入看一眼知其所以然是最好不過。本文分享是基礎準備篇,希望能給你帶來不一樣的 IO 視角。 

 

責任編輯:龐桂玉 來源: 良許Linux
相關推薦

2025-01-10 15:13:38

2011-02-16 16:13:40

Debian

2019-05-27 15:30:44

Node.jsJavaScript前端

2015-09-29 09:47:14

2018-09-10 13:47:21

數據科學統計學決策

2011-08-04 13:24:28

IT運維

2012-05-28 22:49:50

PureView

2014-07-28 08:28:38

Windows

2014-08-07 10:32:02

Windows微軟

2009-07-30 14:43:30

認識BSM

2022-06-13 09:51:35

UWB超寬帶無線載波通信技術

2015-08-26 09:54:19

物聯網

2009-05-11 18:56:47

服務器虛擬化Vmware

2014-06-27 09:35:16

機器學習

2022-02-07 15:20:53

去中心化加密經濟學加密貨幣

2021-03-08 21:44:33

以太坊區塊鏈比特幣

2021-08-09 05:19:08

Provider 前端前端代碼

2020-07-08 08:09:08

邊緣計算邊緣云云平臺

2020-12-17 17:33:47

MLOps大數據數據

2025-06-25 14:18:36

LAMLAMsGUI
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 黄网址在线观看 | 国产精品永久久久久 | 国产精品色 | 国内av在线 | 亚洲一区二区久久 | 成在线人视频免费视频 | 国产99免费 | 国产一级特黄真人毛片 | 亚洲精品在线国产 | 亚洲导航深夜福利涩涩屋 | 一区二区av在线 | 99久久婷婷国产综合精品电影 | 国产精品99久久久久久久久 | 国产精品一级在线观看 | 四虎影院免费在线播放 | 伊人中文网| 国产精品久久av | 亚洲综合色丁香婷婷六月图片 | 久草成人网 | 中文字幕一区在线观看视频 | 在线播放中文字幕 | 国产精品极品美女在线观看免费 | 亚洲网站在线观看 | 亚洲精品中文在线观看 | 狠狠色网| 精品国产欧美在线 | 天天色天天射天天干 | 97偷拍视频| 国产精品麻 | 不卡的av电影 | 亚洲精品精品 | 亚洲精品久久久久久久久久久久久 | 日韩 国产 在线 | 一区二区三区在线播放 | 欧美一区二区久久 | 成人自拍av| 久久久久久成人 | 午夜欧美一区二区三区在线播放 | 欧美一区二区三区视频 | 国产黄色在线 | 插插宗合网 |