從Linux看系統IO

2021-07-03 09:17:58 字數 4683 閱讀 4839

最近看《深入理解計算機系統》系統級io部分,突然想起之前和同學討論的關於檔案描述符和file*的差別,很不好意思的是,當時我說了乙個錯誤的答案,,

在繼續下文之前,先丟擲幾個問題:

1、可不可以對同一檔案open兩次?如果可以,關閉呢?

2、我們知道程序建立是父子程序複製,也即子程序繼承父程序開啟的檔案描述符,那父程序還是子程序關閉?

3、當open時,發生了什麼?檔案內容從硬碟傳入記憶體了嗎?

4、為什麼當不再操作檔案描述符時要close,不close不行嗎?或者說close時發生了什麼?

5、什麼叫帶緩衝區的讀?理論上,記憶體與硬碟互動資料大小是以頁為單位的(通常是4k),那緩衝區中就已經包含了文字檔案的資料,為什麼還需要應用級緩衝?

6、系統io與標準io庫是什麼關係?檔案描述符和file*是什麼關係?可不可以混用?作為程式設計師應該用哪種?

一、linux系統關於檔案的資料結構

既然題目是從linux看io,那當然得看真傢伙(linux 0.11)。每個程序的控制結構pcb(linux中用task_struct結構)中都有乙個檔案描述符表,通常說的檔案描述符就是檔案描述符表的索引。檔案描述符表的內容是指向檔案表(file table)的指標,其中檔案表是所有程序共用的,由核心來維護。檔案表也是乙個資料結構(struct file file_table),也即file_table指向乙個檔案的inode表,當然inode表也是由核心維護的,由所有程序共享的。

struct file file_table[nr_file];//檔案表,nr_file=64,每一項為struct file型別

struct file

//檔案的inode

struct m_inode

二、unix系統級io概述

我們知道linux/unix哲學是:一切皆檔案。這是乙個非常美妙的抽象,將各種不同的外設、檔案系統都歸為一類,而用簡單的檔案操作就可以操作。

unix系統級io函式只有如下幾個:

int open(const

char * filename,int flag,int mode);//開啟檔案

int close(int fd);//關閉檔案,fd為open返回的檔案描述符

int read(int fd,void *buf,int maxlen);//從當前位置讀取最大maxlen位元組到buf中

int write(int fd,const

void *buf,int maxlen);

int lseek(int fd,int offset,int origin);//移動當前檔案位置,origin:代表移動的模式,0為從頭開始定位offset,1表示從當前位置定位offset,2表示從檔案結尾定位offset,當然,offset可正可負。結果返回當前檔案位置,若定位檔案位置<0,返回負數。

int stat(const

char *filename,struct stat *buf);

int fstat(int fd,struct stat *buf);//讀取檔案的metadata(元資料這個翻譯太難受了)

三、呼叫open()時發生了什麼?

記得之前最頭痛的就是各種高階語言的open,雖然知道每次進行io操作時都要open,但是open之後發生了什麼?把檔案內容讀到了記憶體?

open函式是乙個系統呼叫,事實上所有的上面說的系統級io函式都是系統呼叫,每次呼叫都會陷入核心。

當呼叫open函式時,會發生如下步驟:

檢查是否可用獲得乙個檔案描述符,即在pcb(task_struct->flip)中找到乙個合適的位置,因為flip只有32項,所有每個程序最多開啟32-3項,為啥要減3,因為程序預設開啟了輸入(fd = 0),輸出(fd = 1),錯誤(fd = 2)。

檢查是否可用獲得乙個檔案表項,即在file_table中獲取乙個合適的 位置,因為file_table有64項,且為所有程序共享。所以所有程序最多可用同時開啟64個檔案。當然前3項是輸入、輸出、錯誤。這裡所謂的合適指struct file中的引用計數為0,即對應檔案沒有被使用的。

如果滿足上述條件,將載入對應檔案的inode(每個inode唯一對應乙個檔案,同理,每個檔案只有乙個inode),為了體現程序共享的思想,如果對應的inode已經在inode陣列裡,就將其返回,並將引用計數+1,如果沒有其他程序載入該inode,將從inode陣列裡分配乙個空的項,並從硬碟載入inode。

四、當呼叫read函式時發生了什麼?

這是個非常有趣的問題,當我們呼叫read時,要傳入三個引數,檔案描述符、指向緩衝區的指標、已經讀取位元組數。

首先,根據f_pos和count數判斷讀取檔案是否超過檔案大小,如果超過,將count截斷。如count為0,即已經到檔案結尾,返回0

根據f_pos/block_size,得到檔案資料塊號,經過bmap對映到硬碟中的邏輯塊號,而裝置號存在於inode中

當得到裝置號和塊號後,會先檢查該塊是否已經在緩衝區裡了,如果在,返回buffer_head。如果不存在才會讀盤。然後將從f_pos開始的count位元組的資料複製到應用程式的buf中。這裡是linux共享檔案思想的另乙個體現。

所以,我們得到如下結論:緩衝區與硬碟塊以block為單位進行資料交換;當我們讀取檔案時,並不會將全部檔案讀入到記憶體,特別地,當檔案特別大時,如果將檔案全部讀入到記憶體可能並不合適,所以可以顯式的調整f_pos和count來讀取資料;

所以,我們依次回答上述問題:

對同一檔案可以open多次嗎?

可以,如果程序(不管是否同乙個)中多次open同乙個檔案,那麼將會產生多個檔案描述符項和檔案表項。注意到檔案表項中struct file中存在著f_pos欄位,所以程序可以用不同檔案描述符從不同位置開始讀寫檔案。同理,如果關閉的話,開啟幾次就要關閉幾次。否則對應資源永遠不會**,這裡的資源主要指程序檔案描述符陣列資源、檔案表陣列的資源、inode陣列的資源。(此時,如上圖1,2線所示)

當父程序呼叫fork()函式建立子程序後呢?

當父程序建立子程序,根據父子程序複製的思想,子程序將繼承父程序的檔案描述符表。此時,父子程序各自一套檔案描述符表,只是內容完全一樣,也即,此時父子相同的檔案描述符指向同乙個檔案表項。當時該檔案表項的引用計數+1,此時,如上圖,1&11,2&12,3&13線所示。

所以這也就解釋了,為什麼當對檔案操作結束後,父子程序都要關閉檔案。因為如果某乙個沒關閉,檔案表中對應項的引用計數將為1,那麼將永遠佔據該項。

當然了,當程序退出時,將會自動關閉的。

當open時發生了什麼?檔案資料從硬碟傳遞到記憶體了嗎?

open只是將檔案的inode載入到記憶體中,但檔案的資料並沒有傳遞到記憶體。為啥要這樣設計?我們知道讀盤是非常耗費時間的,量級大概在10ms(如果cpu主頻是1ghz的話,那麼也已經執行了百萬條指令了),所以作業系統的設計思想是能不讀盤就不讀盤,一定要到非讀不可的時候才會選擇讀盤,而且讀的時候是以塊為單位讀的(典型為4k),即使唯讀乙個位元組。為啥要以塊為單位讀?因為磁碟讀資料的時間主要花費在了尋道上,也就是說讀第乙個位元組的時間,而順序讀的時間是很低的,所以如果,唯讀幾個位元組的話,整體效率太低了。

close(int fd)時發生了什麼?

首先,將檔案描述符對應項清空,將原對應的檔案表項的引用計數-1,如果此時該項引用計數為0,釋放inode。

為什麼有了記憶體緩衝區還要顯式的要新增應用級緩衝區呢?

核心緩衝區是由核心來維護的,是不對應用可見的。每次讀核心緩衝區時都要顯式的陷入核心狀態才可以。這樣會增大花銷。所以可以維護乙個應用級緩衝區。相當於是核心緩衝區的乙個子集。當應用級緩衝區不夠用時,顯式的呼叫read函式,如果此時核心緩衝區有需要的資料,直接返回,如果沒有,就從硬碟讀。這個過程是對程式設計師友好和透明的。

五、系統級io vs 標準io庫

幾乎每種高階語言都有自己的標準io庫,但是這些都是對系統級io的封裝。比如c語言的fopen,fclose,fseek,fread,fwrite等等等等。

一般來說,對普通檔案的操作,建議使用系統級io函式,因為對程式設計師更友好些。但是對於網路應用,比如socket檔案,標準io庫便不太好使了,這時候使用系統級io更好。

六、檔案描述符 vs file指標

文章開頭提到的檔案描述符和file指標的關係,現在是時候揭開面紗了。實際上,file資料結構中包含乙個檔案描述符,畢竟要通過該描述符進行讀寫資料。與此同時,內部還維護著乙個指向緩衝區的指標。注意,該緩衝區指的不是核心緩衝區,而是應用級緩衝區,這樣可以提高讀取速度。

那問題來了,那能不能標準c庫與系統級io函式混用呢?共同操作同一檔案描述符?答案是否定的。因為file結構裡面維護著乙個緩衝區。也就是說使用fread()時,讀取的個數實際上是緩衝區的個數,而不是程式指定的讀取位元組數。所以兩者不可混用,因為f_pos與程式設計師看到的可能已經不一致了。

這也就是為什麼對於網路應用而言,我們希望迅速將資料傳遞出去,而不是存放在緩衝區裡,此時,不應該使用標準c庫的io函式,而應該使用系統級io。

七、關於檔案結尾

我們如何標識乙個檔案在硬碟中的結尾呢?很多人以為硬碟中有個特殊的字元eof,當硬碟讀到這個字元時就截止。但這是錯誤的。硬碟裡並沒有什麼特別的東西標誌檔案的結束。作限制的是inode中的i_size,即檔案的大小。當讀取檔案的位置+讀取數》i_size時,這時候就要顯式的對讀取數做限制。當f_pos指向了檔案結尾,但還在讀時,將返回0,這也就是標誌eof的來歷了。

從檔案系統看系統架構

struct vop vector struct vop vector vop default 定義乙個預設的操作結構,注意型別也是vop vector int vop bypass struct vop generic args ap int vop open struct vop open ar...

linux看系統時間

一 檢視和修改linux的時區 1.檢視當前時區 命令 date r 2.修改設定linux伺服器時區 方法 a 命令 tzselect 方法 b 僅限於redhat linux 和 centos 命令 timeconfig 方法 c 適用於debian 命令 dpkg reconfigure tz...

Linux檢視系統IO

linux檢視io的工具主要有兩個,iostat 整合於sysstat包中 和iotop。前者經常用於檢視分割槽的io情況,後者常用來檢視每個程序的io占用。1 iostat yum install sysstat iostat x 5 每隔五秒顯示一次 2 iotop yum install io...