版權(quán)聲明:本文為 cdeveloper 原創(chuàng)文章,可以隨意轉(zhuǎn)載,但必須在明確位置注明出處!
IO 概述
這篇文章主要介紹 Linux IO 的基本知識和學(xué)習(xí)方法,掌握這些再學(xué)習(xí) IO 操作會更加游刃有余,更加系統(tǒng)。
上層開發(fā)與 kenel 的關(guān)系
在學(xué)習(xí) Linux 的 IO 操作之前,我們先來了解下上層開發(fā)和 kernel 底層的關(guān)系,也就是說上層大體上是如何調(diào)用底層的。我們以在 Linux 上開發(fā)的 C 程序為例簡單介紹一下,因為這部分詳細(xì)介紹很復(fù)雜,而我們開發(fā)上層只需要了解基本的過程即可,對底層有興趣可以深入研究。
自頂向下
我們從上到下來看看一個 C 的 IO 程序是如何調(diào)用內(nèi)核方法的:
Linux C App -> glibc(C 庫)-> VFS(虛擬文件系統(tǒng))-> kernel function(內(nèi)核方法)
這只是以 C 程序為例,Linux 的 C 程序現(xiàn)在使用的是 GNU C Libary, glibc
,但是 Linux 也支持不同的語言,通過類比可以知道,每種語言也應(yīng)該都提供了相應(yīng)的類庫。
更加簡單的理解可以說成:操作系統(tǒng)內(nèi)核提供功能的實現(xiàn),上層類庫將這些實現(xiàn)封裝成 API 庫供上層調(diào)用,如果某個庫需要跨平臺,那么這個庫的接口就需要符合一定的規(guī)則。
例如標(biāo)準(zhǔn) C 庫 ANSI C
就是跨平臺的,它的接口標(biāo)準(zhǔn)由國際標(biāo)準(zhǔn)化組織(ISO)制定,你在 Linux 上用 ANSI C
寫的 C 程序在 Windows 上也能運行,因為 Windows 也支持 ANSI C
。
Linux IO 體系結(jié)構(gòu)
在學(xué)習(xí) IO 操作之前,我們也需要對 Linux 的 IO 系統(tǒng)有一個大致的了解,總體來說,Linux 的上層 IO 結(jié)構(gòu)有下面 3 個方面:
- 文件系統(tǒng) API:Linux 下有很多種文件系統(tǒng),但是為了統(tǒng)一接口,Linux 提供了 VFS,我們需要學(xué)會使用 VFS 的 API
- 驅(qū)動和總線:提供對硬件的操作接口,需要了解
- 設(shè)備類型:鍵盤,鼠標(biāo)等硬件 IO 設(shè)備,需要了解
Linux 下所有的設(shè)備都是文件,所以都可以使用文件系統(tǒng)的 API 來操作,一個基本的方式如下:
LinuxApp (open...) -> /dev/xxx -> VFS -> xFS -> 總線 -> 驅(qū)動 -> 硬件
這其中 VFS 提供的對多種不同的 FS 的統(tǒng)一接口非常重要,這使得上層 APP 只需調(diào)用統(tǒng)一的 API,而不用擔(dān)心當(dāng)前使用的是哪種文件系統(tǒng):
VFS(虛擬文件系統(tǒng)) 是抽象在計算機中的典型應(yīng)用
通用的 IO 操作
IO 操作即使是在不同的系統(tǒng)上也經(jīng)常提供下面這些功能(不是全部):
- 打開,關(guān)閉文件:open,close
- 讀,寫文件:read,write
- 控制文件:seek 移動文件指針等
這些基本上可以說是一個 IO 系統(tǒng)最基本的操作,其中打開,關(guān)閉,讀寫都是平常的必備操作。那么 Linux 的 IO 操作有沒有什么特別的地方呢?
Linux 的 IO 操作分類
Linux 的 IO 操作大致可以分為以下幾類:
- 標(biāo)準(zhǔn) IO:使用 ANSI C 提供的 API
- 底層 IO:使用 Posix C 提供的 API
- FS 文件系統(tǒng)接口:掌握訪問 FS 的 API
- 管道及 FIFO(先入先出隊列):用于進(jìn)程間通信
- Socket:比較特殊的 IO 操作,用于網(wǎng)絡(luò)訪問
- 底層終端接口(tty):字符終端也是一種 IO
在 IO 階段主要還是以標(biāo)準(zhǔn)和底層 IO 為主,其他的類別一般都在進(jìn)程,網(wǎng)絡(luò)中介紹。
Linux IO 數(shù)據(jù)結(jié)構(gòu)
開發(fā)上層 Linux IO 類型的程序,你首先需要理解下面這 3 個數(shù)據(jù)結(jié)構(gòu),它們非常重要,是一切操作的核心。
1. 文件描述符 FD
對于 Linux 內(nèi)核來說,一個打開的文件是一個文件描述符(File Description,F(xiàn)D)的引用,F(xiàn)D 是一個非負(fù)整數(shù)。當(dāng)打開一個現(xiàn)存的文件或者創(chuàng)建一個新文件時,內(nèi)核向進(jìn)程返回一個文件描述符,當(dāng)讀寫文件時,用 open 或 read 返回的文件描述符 fd 標(biāo)識該文件,將其作為參數(shù)傳送給 read 和 write 。
每個進(jìn)程都有默認(rèn)的 FD[0, 1, 2]
:
-
STDIN_FILENO
:標(biāo)準(zhǔn)輸入,F(xiàn)D = 0 -
STDOUT_FILENO
:標(biāo)準(zhǔn)輸出,F(xiàn)D = 1 -
STDERR_FILENO
:標(biāo)準(zhǔn)錯誤輸出,F(xiàn)D = 2
2. File 結(jié)構(gòu)
struct file
在內(nèi)核中其實就代表了一個實際的文件,我們需要了解其中比較重要的字段:
struct file {
// 文件鏈表指針
struct list_head f_list;
// 文件對應(yīng)目錄結(jié)構(gòu)
struct dentry *f_dentry;
// 虛擬文件系統(tǒng)掛載點
struct vfsmount *f_vfsmnt;
// 文件操作函數(shù)指針
struct file_operations *f_op;
...
// 文件模式
mode_t f_mode;
// 文件 offset
loff_t f_pos;
};
3. Files Structure
file_struct
保存了一個進(jìn)程打開的所有文件表的數(shù)據(jù):
struct file_struct {
// 自動曾量
atomic_t count;
...
// 最大文件句柄數(shù)目
int max_fds;
// 最大的 fd 集合容量
int max_fdset;
// 下一個空閑的 fs
int next_fd;
...
};
如何學(xué)習(xí) IO 操作?
給你 2 個最好的免費資源:
- glibc 官網(wǎng)
- Linux 自帶的 man 手冊,例如:
man 2 open
最好的方法是看 GNU 的官方文檔和系統(tǒng)自帶的 man 手冊,我們已經(jīng)知道 Linux C 使用的是 glibc 庫,那么我們可以去 GNU 官網(wǎng)去查找這個庫,發(fā)現(xiàn)它是開源的并且提供了非常好的學(xué)習(xí)文檔,而 man 是 Linux 系統(tǒng)自帶的,用起來也非常簡單,例如 man 2 open
即可查看 open 函數(shù)的用法,介紹非常詳細(xì)。但是市面上的那些培訓(xùn)機構(gòu)卻只會教你如何使用 API,而不教你如何查找這些 API 的學(xué)習(xí)資料,實在有些可惜。
一個函數(shù)名可能對應(yīng)一個 shell 命令,當(dāng)你用
man open
發(fā)現(xiàn)沒找到函數(shù)定義時,試試man 2 open
或者man [n] open
如果你養(yǎng)成學(xué)習(xí)一種技術(shù),首先到它的官網(wǎng)去查找學(xué)習(xí)資料的好習(xí)慣,那么你的進(jìn)步會非常的快,相信我。因為沒有比官網(wǎng)的資料更權(quán)威的了,那些寫博客的也只不過是翻譯并加上一些自己的理解,說實話當(dāng)你自己看懂了那些英文文檔,你就不需要看任何博客了,因為你已經(jīng)找到了最好的「博客」。
如果你喜歡看英文那么你完全可以不看我之后更新的 IO 的內(nèi)容,因為我的內(nèi)容也是根據(jù)官網(wǎng)的文檔自己總結(jié)的,你英文能力強,完全可以看原汁原味的資料,我更加希望你能不依賴別人而學(xué)習(xí),一個人的進(jìn)步 90 % 要靠自己,何況我自己的理解可能也不太準(zhǔn)確呢。但是如果你的英文不太好,那么我建議你可以對照我的博客和官方文檔來看,慢慢養(yǎng)成看英文的好習(xí)慣,受益終生。
結(jié)語
概述講的太多就沒有意義了,這篇文章主要讓你對 IO 有一個基本的了解,最重要的是你要理解上層 APP 大體的執(zhí)行過程和如何系統(tǒng)的學(xué)習(xí) IO 操作,養(yǎng)成看英文文檔的習(xí)慣,這才是這篇文章介紹的最重要的內(nèi)容,具體的 IO 操作的文章后面會有更新,敬請期待。
最后,感謝你的閱讀,我們下次再見 :)