文件系統:隱匿在 Linux 背後的機制


點擊藍色「Java建設者 」關注我喲java

加個「星標」,及時閱讀最新技術文章node




這是Java建設者的第 115 篇原創文章


在 Linux 中,最直觀、最可見的部分就是 文件系統(file system)。下面咱們就來一塊兒探討一下關於 Linux 中國的文件系統,系統調用以及文件系統實現背後的原理和思想。這些思想中有一些來源於 MULTICS,如今已經被 Windows 等其餘操做系統使用。Linux 的設計理念就是 小的就是好的(Small is Beautiful) 。雖然 Linux 只是使用了最簡單的機制和少許的系統調用,可是 Linux 卻提供了強大而優雅的文件系統。
web

Linux 文件系統基本概念

Linux 在最初的設計是 MINIX1 文件系統,它只支持 14 字節的文件名,它的最大文件只支持到 64 MB。在 MINIX 1 以後的文件系統是 ext 文件系統。ext 系統相較於 MINIX 1 來講,在支持字節大小和文件大小上均有很大提高,可是 ext 的速度仍沒有 MINIX 1 快,因而,ext 2 被開發出來,它可以支持長文件名和大文件,並且具備比 MINIX 1 更好的性能。這使他成爲 Linux 的主要文件系統。只不過 Linux 會使用 VFS 曾支持多種文件系統。在 Linux 連接時,用戶能夠動態的將不一樣的文件系統掛載倒 VFS 上。shell

Linux 中的文件是一個任意長度的字節序列,Linux 中的文件能夠包含任意信息,好比 ASCII 碼、二進制文件和其餘類型的文件是不加區分的。數據庫

爲了方便起見,文件能夠被組織在一個目錄中,目錄存儲成文件的形式在很大程度上能夠做爲文件處理。目錄能夠有子目錄,這樣造成有層次的文件系統,Linux 系統下面的根目錄是 / ,它一般包含了多個子目錄。字符 / 還用於對目錄名進行區分,例如 /usr/cxuan 表示的就是根目錄下面的 usr 目錄,其中有一個叫作 cxuan 的子目錄。數組

下面咱們介紹一下 Linux 系統根目錄下面的目錄名緩存

  • /bin,它是重要的二進制應用程序,包含二進制文件,系統的全部用戶使用的命令都在這裏
  • /boot,啓動包含引導加載程序的相關文件
  • /dev,包含設備文件,終端文件,USB 或者鏈接到系統的任何設備
  • /etc,配置文件,啓動腳本等,包含全部程序所須要的配置文件,也包含了啓動/中止單個應用程序的啓動和關閉 shell 腳本
  • /home,本地主要路徑,全部用戶用 home 目錄存儲我的信息
  • /lib,系統庫文件,包含支持位於 /bin 和 /sbin 下的二進制庫文件
  • /lost+found,在根目錄下提供一個遺失+查找系統,必須在 root 用戶下才能查看當前目錄下的內容
  • /media,掛載可移動介質
  • /mnt,掛載文件系統
  • /opt,提供一個可選的應用程序安裝目錄
  • /proc,特殊的動態目錄,用於維護系統信息和狀態,包括當前運行中進程信息
  • /root,root 用戶的主要目錄文件夾
  • /sbin,重要的二進制系統文件
  • /tmp, 系統和用戶建立的臨時文件,系統重啓時,這個目錄下的文件都會被刪除
  • /usr,包含絕大多數用戶都能訪問的應用程序和文件
  • /var,常常變化的文件,諸如日誌文件或數據庫等

在 Linux 中,有兩種路徑,一種是 絕對路徑(absolute path) ,絕對路徑告訴你從根目錄下查找文件,絕對路徑的缺點是太長並且不太方便。還有一種是 相對路徑(relative path) ,相對路徑所在的目錄也叫作工做目錄(working directory)微信

若是 /usr/local/books 是工做目錄,那麼 shell 命令app

cp books books-replica 

就表示的是相對路徑,而編輯器

cp /usr/local/books/books /usr/local/books/books-replica

則表示的是絕對路徑。

在 Linux 中常常出現一個用戶使用另外一個用戶的文件或者使用文件樹結構中的文件。兩個用戶共享同一個文件,這個文件位於某個用戶的目錄結構中,另外一個用戶須要使用這個文件時,必須經過絕對路徑才能引用到他。若是絕對路徑很長,那麼每次輸入起來會變的很是麻煩,因此 Linux 提供了一種 連接(link) 機制。

舉個例子,下面是一個使用連接以前的圖

以上所示,好比有兩個工做帳戶 jianshe 和 cxuan,jianshe 想要使用 cxuan 帳戶下的 A 目錄,那麼它可能會輸入 /usr/cxuan/A ,這是一種未使用連接以後的圖。

使用連接後的示意以下

如今,jianshe 能夠建立一個連接來使用 cxuan 下面的目錄了。‘

當一個目錄被建立出來後,有兩個目錄項也同時被建立出來,它們就是 ... ,前者表明工做目錄自身,後者表明該目錄的父目錄,也就是該目錄所在的目錄。這樣一來,在 /usr/jianshe 中訪問 cxuan 中的目錄就是 ../cxuan/xxx

Linux 文件系統不區分磁盤的,這是什麼意思呢?通常來講,一個磁盤中的文件系統相互之間保持獨立,若是一個文件系統目錄想要訪問另外一個磁盤中的文件系統,在 Windows 中你能夠像下面這樣。

兩個文件系統分別在不一樣的磁盤中,彼此保持獨立。

而在 Linux 中,是支持掛載的,它容許一個磁盤掛在到另一個磁盤上,那麼上面的關係會變成下面這樣

掛在以後,兩個文件系統就再也不須要關心文件系統在哪一個磁盤上了,兩個文件系統彼此可見。

Linux 文件系統的另一個特性是支持 加鎖(locking)。在一些應用中會出現兩個或者更多的進程同時使用同一個文件的狀況,這樣極可能會致使競爭條件(race condition)。一種解決方法是對其進行加不一樣粒度的鎖,就是爲了防止某一個進程只修改某一行記錄從而致使整個文件都不能使用的狀況。

POSIX 提供了一種靈活的、不一樣粒度級別的鎖機制,容許一個進程使用一個不可分割的操做對一個字節或者整個文件進行加鎖。加鎖機制要求嘗試加鎖的進程指定其 要加鎖的文件,開始位置以及要加鎖的字節

Linux 系統提供了兩種鎖:共享鎖和互斥鎖。若是文件的一部分已經加上了共享鎖,那麼再加排他鎖是不會成功的;若是文件系統的一部分已經被加了互斥鎖,那麼在互斥鎖解除以前的任何加鎖都不會成功。爲了成功加鎖、請求加鎖的部分的全部字節都必須是可用的。

在加鎖階段,進程須要設計好加鎖失敗後的狀況,也就是判斷加鎖失敗後是否選擇阻塞,若是選擇阻塞式,那麼當已經加鎖的進程中的鎖被刪除時,這個進程會解除阻塞並替換鎖。若是進程選擇非阻塞式的,那麼就不會替換這個鎖,會馬上從系統調用中返回,標記狀態碼錶示是否加鎖成功,而後進程會選擇下一個時間再次嘗試。

加鎖區域是能夠重疊的。下面咱們演示了三種不一樣條件的加鎖區域。

如上圖所示,A 的共享鎖在第四字節到第八字節進行加鎖

如上圖所示,進程在 A 和 B 上同時加了共享鎖,其中 6 - 8 字節是重疊鎖

如上圖所示,進程 A 和 B 和 C 同時加了共享鎖,那麼第六字節和第七字節是共享鎖。

若是此時一個進程嘗試在第 6 個字節處加鎖,此時會設置失敗並阻塞,因爲該區域被 A B C 同時加鎖,那麼只有等到 A B C 都釋放鎖後,進程才能加鎖成功。

Linux 文件系統調用

許多系統調用都會和文件與文件系統有關。咱們首先先看一下對單個文件的系統調用,而後再來看一下對整個目錄和文件的系統調用。

爲了建立一個新的文件,會使用到 creat 方法,注意沒有 e

這裏說一個小插曲,曾經有人問 UNIX 創始人 Ken Thompson,若是有機會從新寫 UNIX ,你會怎麼辦,他回答本身要把 creat 改爲 create ,哈哈哈哈。

這個系統調用的兩個參數是文件名和保護模式

fd = creat("aaa",mode);

這段命令會建立一個名爲 aaa 的文件,並根據 mode 設置文件的保護位。這些位決定了哪一個用戶可能訪問文件、如何訪問。

creat 系統調用不只僅建立了一個名爲 aaa 的文件,還會打開這個文件。爲了容許後續的系統調用訪問這個文件,這個 creat 系統調用會返回一個 非負整數, 這個就叫作 文件描述符(file descriptor),也就是上面的 fd。

若是在已經存在的文件上調用了 creat 系統調用,那麼該文件中的內容會被清除,從 0 開始。經過設置合適的參數,open 系統調用也可以建立文件。

下面讓咱們看一看主要的系統調用,以下表所示

系統調用 描述
fd = creat(name,mode) 一種建立一個新文件的方式
fd = open(file, ...) 打開文件讀、寫或者讀寫
s = close(fd) 關閉一個打開的文件
n = read(fd, buffer, nbytes) 從文件中向緩存中讀入數據
n = write(fd, buffer, nbytes) 從緩存中向文件中寫入數據
position = lseek(fd, offset, whence) 移動文件指針
s = stat(name, &buf) 獲取文件信息
s = fstat(fd, &buf) 獲取文件信息
s = pipe(&fd[0]) 建立一個管道
s = fcntl(fd,...) 文件加鎖等其餘操做

爲了對一個文件進行讀寫的前提是先須要打開文件,必須使用 creat 或者 open 打開,參數是打開文件的方式,是隻讀、可讀寫仍是隻寫。open 系統調用也會返回文件描述符。打開文件後,須要使用 close 系統調用進行關閉。close 和 open 返回的 fd 老是未被使用的最小數量。

什麼是文件描述符?文件描述符就是一個數字,這個數字標示了計算機操做系統中打開的文件。它描述了數據資源,以及訪問資源的方式。

當程序要求打開一個文件時,內核會進行以下操做

  • 授予訪問權限
  • 全局文件表(global file table)中建立一個 條目(entry)
  • 向軟件提供條目的位置

文件描述符由惟一的非負整數組成,系統上每一個打開的文件至少存在一個文件描述符。文件描述符最初在 Unix 中使用,而且被包括 Linux,macOS 和 BSD 在內的現代操做系統所使用。

當一個進程成功訪問一個打開的文件時,內核會返回一個文件描述符,這個文件描述符指向全局文件表的 entry 項。這個文件表項包含文件的 inode 信息,字節位移,訪問限制等。例以下圖所示

默認狀況下,前三個文件描述符爲 STDIN(標準輸入)STDOUT(標準輸出)STDERR(標準錯誤)

標準輸入的文件描述符是 0 ,在終端中,默認爲用戶的鍵盤輸入

標準輸出的文件描述符是 1 ,在終端中,默認爲用戶的屏幕

與錯誤有關的默認數據流是 2,在終端中,默認爲用戶的屏幕。

在簡單聊了一下文件描述符後,咱們繼續回到文件系統調用的探討。

在文件系統調用中,開銷最大的就是 read 和 write 了。read 和 write 都有三個參數

  • 文件描述符:告訴須要對哪個打開文件進行讀取和寫入
  • 緩衝區地址:告訴數據須要從哪裏讀取和寫入哪裏
  • 統計:告訴須要傳輸多少字節

這就是全部的參數了,這個設計很是簡單輕巧。

雖然幾乎全部程序都按順序讀取和寫入文件,可是某些程序須要可以隨機訪問文件的任何部分。與每一個文件相關聯的是一個指針,該指針指示文件中的當前位置。順序讀取(或寫入)時,它一般指向要讀取(寫入)的下一個字節。若是指針在讀取 1024 個字節以前位於 4096 的位置,則它將在成功讀取系統調用後自動移至 5120 的位置。

Lseek 系統調用會更改指針位置的值,以便後續對 read 或 write 的調用能夠在文件中的任何位置開始,甚至能夠超出文件末尾。

lseek = Lseek ,段首大寫。

lseek 避免叫作 seek 的緣由就是 seek 已經在以前 16 位的計算機上用於搜素功能了。

Lseek 有三個參數:第一個是文件的文件描述符,第二個是文件的位置;第三個告訴文件位置是相對於文件的開頭,當前位置仍是文件的結尾

lseek(int fildes, off_t offset, int whence);

lseek 的返回值是更改文件指針後文件中的絕對位置。lseek 是惟一歷來不會形成真正磁盤查找的系統調用,它只是更新當前的文件位置,這個文件位置就是內存中的數字。

對於每一個文件,Linux 都會跟蹤文件模式(常規,目錄,特殊文件),大小,最後修改時間以及其餘信息。程序可以經過 stat 系統調用看到這些信息。第一個參數就是文件名,第二個是指向要放置請求信息結構的指針。這些結構的屬性以下圖所示。

fstat 調用和 stat 相同,只有一點區別,fstat 能夠對打開文件進行操做,而 stat 只能對路徑進行操做。

pipe 文件系統調用被用來建立 shell 管道。它會建立一系列的僞文件,來緩衝和管道組件之間的數據,而且返回讀取或者寫入緩衝區的文件描述符。在管道中,像是以下操做

sort <in | head –40

sort 進程將會輸出到文件描述符1,也就是標準輸出,寫入管道中,而 head 進程將從管道中讀入。在這種方式中,sort 只是從文件描述符 0 中讀取並寫入到文件描述符 1 (管道)中,甚至不知道它們已經被重定向了。若是沒有重定向的話,sort 會自動的從鍵盤讀入並輸出到屏幕中。

最後一個系統調用是 fcntl,它用來鎖定和解鎖文件,應用共享鎖和互斥鎖,或者是執行一些文件相關的其餘操做。

如今咱們來關心一下和總體目錄和文件系統相關的系統調用,而不是把精力放在單個的文件上,下面列出了這些系統調用,咱們一塊兒來看一下。

系統調用 描述
s = mkdir(path,mode) 建立一個新的目錄
s = rmdir(path) 移除一個目錄
s = link(oldpath,newpath) 建立指向已有文件的連接
s = unlink(path) 取消文件的連接
s = chdir(path) 改變工做目錄
dir = opendir(path) 打開一個目錄讀取
s = closedir(dir) 關閉一個目錄
dirent = readdir(dir) 讀取一個目錄項
rewinddir(dir) 迴轉目錄使其在此使用

可使用 mkdir 和 rmdir 建立和刪除目錄。可是須要注意,只有目錄爲空時才能夠刪除。

建立一個指向已有文件的連接時會建立一個目錄項(directory entry)。系統調用 link 來建立連接,oldpath 表明已有的路徑,newpath 表明須要連接的路徑,使用 unlink 能夠刪除目錄項。當文件的最後一個連接被刪除時,這個文件會被自動刪除。

使用 chdir 系統調用能夠改變工做目錄。

最後四個系統調用是用於讀取目錄的。和普通文件相似,他們能夠被打開、關閉和讀取。每次調用 readdir 都會以固定的格式返回一個目錄項。用戶不能對目錄執行寫操做,可是可使用 creat 或者 link 在文件夾中建立一個目錄,或使用 unlink 刪除一個目錄。用戶不能在目錄中查找某個特定文件,可是可使用 rewindir 做用於一個打開的目錄,使他能在此從頭開始讀取。




目錄





漲姿式了!用了這些建議後系統性能足足提高了 10 倍!!!

給女同事講完代理後,女同事說:你好棒哦


太喜歡啦!學 SQL 就看這些了!


本文分享自微信公衆號 - Java建設者(javajianshe)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索