Read 系統呼叫在使用者空間中的處理過程
Linux 系統呼叫(SCI,system call interface)的實現機制實際上是一個多路匯聚以及分解的過程,該匯聚點就是 0x80 中斷這個入口點(X86 系統結構)。也就是說,所有系統呼叫都從使用者空間中匯聚到 0x80 中斷點,同時儲存具體的系統呼叫號。當 0x80 中斷處理程式執行時,將根據系統呼叫號對不同的系統呼叫分別處理(呼叫不同的核心函式處理)。系統呼叫的更多內容,請參見參考資料。
Read 系統呼叫也不例外,當呼叫發生時,庫函式在儲存 read 系統呼叫號以及引數後,陷入 0x80 中斷。這時庫函式工作結束。Read 系統呼叫在使用者空間中的處理也就完成了。
Read 系統呼叫在核心空間中的處理過程
0x80 中斷處理程式接管執行後,先檢察其系統呼叫號,然後根據系統呼叫號查詢系統呼叫表,並從系統呼叫表中得到處理 read 系統呼叫的核心函式 sys_read ,最後傳遞引數並執行 sys_read 函式。至此,核心真正開始處理 read 系統呼叫(sys_read 是 read 系統呼叫的核心入口)。
在講解 read 系統呼叫在核心空間中的處理部分中,首先介紹了核心處理磁碟請求的層次模型,然後再按該層次模型從上到下的順序依次介紹磁碟讀請求在各層的處理過程。
Read 系統呼叫在核心空間中處理的層次模型
圖1顯示了 read 系統呼叫在核心空間中所要經歷的層次模型。從圖中看出:對於磁碟的一次讀請求,首先經過虛擬檔案系統層(vfs layer),其次是具體的檔案系統層(例如 ext2),接下來是 cache 層(page cache 層)、通用塊層(generic block layer)、IO 排程層(I/O scheduler layer)、塊裝置驅動層(block device driver layer),最後是物理塊裝置層(block device layer)
圖1 read 系統呼叫在核心空間中的處理層次
-
虛擬檔案系統層的作用:遮蔽下層具體檔案系統操作的差異,為上層的操作提供一個統一的介面。正是因為有了這個層次,所以可以把裝置抽象成檔案,使得操作裝置就像操作檔案一樣簡單。
-
在具體的檔案系統層中,不同的檔案系統(例如 ext2 和 NTFS)具體的操作過程也是不同的。每種檔案系統定義了自己的操作集合。關於檔案系統的更多內容,請參見參考資料。
-
引入 cache 層的目的是為了提高 linux 作業系統對磁碟訪問的效能。 Cache 層在記憶體中快取了磁碟上的部分資料。當資料的請求到達時,如果在 cache 中存在該資料且是最新的,則直接將資料傳遞給使用者程式,免除了對底層磁碟的操作,提高了效能。
-
通用塊層的主要工作是:接收上層發出的磁碟請求,並最終發出 IO 請求。該層隱藏了底層硬體塊裝置的特性,為塊裝置提供了一個通用的抽象檢視。
-
IO 排程層的功能:接收通用塊層發出的 IO 請求,快取請求並試圖合併相鄰的請求(如果這兩個請求的資料在磁碟上是相鄰的)。並根據設定好的排程演演算法,回呼驅動層提供的請求處理函式,以處理具體的 IO 請求。
-
驅動層中的驅動程式對應具體的物理塊裝置。它從上層中取出 IO 請求,並根據該 IO 請求中指定的資訊,透過向具體塊裝置的裝置控制器傳送命令的方式,來操縱裝置傳輸資料。
-
裝置層中都是具體的物理裝置。定義了操作具體裝置的規範。
相關的核心資料結構:
-
Dentry : 聯絡了檔案名和檔案的 i 節點
-
inode : 檔案 i 節點,儲存檔案標識、許可權和內容等資訊
-
file : 儲存檔案的相關資訊和各種操作檔案的函式指標集合
-
file_operations :操作檔案的函式介面集合
-
address_space :描述檔案的 page cache 結構以及相關資訊,並包含有操作 page cache 的函式指標集合
-
address_space_operations :操作 page cache 的函式介面集合
-
bio : IO 請求的描述
資料結構之間的關係:
圖2示意性地展示了上述各個資料結構(除了 bio)之間的關係。可以看出:由 dentry 物件可以找到 inode 物件,從 inode 物件中可以取出 address_space 物件,再由 address_space 物件找到 address_space_operations 物件。
File 物件可以根據當前行程描述符中提供的資訊取得,進而可以找到 dentry 物件、 address_space 物件和 file_operations 物件。
圖2 資料結構關係圖:
前提條件:
對於具體的一次 read 呼叫,核心中可能遇到的處理情況很多。這裡舉例其中的一種情況:
-
要讀取的檔案已經存在
-
檔案經過 page cache
-
要讀的是普通檔案
-
磁碟上檔案系統為 ext2 檔案系統,有關 ext2 檔案系統的相關內容,參見參考資料
準備:
註:所有清單中程式碼均來自 linux2.6.11 核心原始碼
讀資料之前,必須先開啟檔案。處理 open 系統呼叫的核心函式為 sys_open 。
所以我們先來看一下該函式都作了哪些事。清單1顯示了 sys_open 的程式碼(省略了部分內容,以後的程式清單同樣方式處理)
清單1 sys_open 函式程式碼
程式碼解釋:
-
get_unuesed_fd() :取回一個未被使用的檔案描述符(每次都會選取最小的未被使用的檔案描述符)。
-
filp_open() :呼叫 open_namei() 函式取出和該檔案相關的 dentry 和 inode (因為前提指明瞭檔案已經存在,所以 dentry 和 inode 能夠查詢到,不用建立),然後呼叫 dentry_open() 函式建立新的 file 物件,並用 dentry 和 inode 中的資訊初始化 file 物件(檔案當前的讀寫位置在 file 物件中儲存)。註意到 dentry_open() 中有一條陳述句:
f->f_op = fops_get(inode->i_fop);
這個賦值陳述句把和具體檔案系統相關的,操作檔案的函式指標集合賦給了 file 物件的 f _op 變數(這個指標集合是儲存在 inode 物件中的),在接下來的 sys_read 函式中將會呼叫 file->f_op 中的成員 read 。
-
fd_install() :以檔案描述符為索引,關聯當前行程描述符和上述的 file 物件,為之後的 read 和 write 等操作作準備。
-
函式最後傳回該檔案描述符。
圖3顯示了 sys_open 函式傳回後, file 物件和當前行程描述符之間的關聯關係,以及 file 物件中操作檔案的函式指標集合的來源(inode 物件中的成員 i_fop)。
圖3 file 物件和當前行程描述符之間的關係
到此為止,所有的準備工作已經全部結束了,下次介紹 read 系統呼叫在圖1所示的各個層次中的處理過程。
(未完)