inode
是文件系統中的一個重要概念,用于存儲文件的元數據。
inode 的結構和內容
- 文件權限:定義了文件所有者、所屬組以及其他用戶對文件的讀、寫、執行權限。
- 文件所有者和所屬組:記錄了文件的所有者和所屬的用戶組信息。
- 文件大小:以字節為單位表示文件的實際大小。
- 文件時間戳:包括文件的創建時間、最后修改時間和最后訪問時間。這些時間戳對于文件的管理和跟蹤非常重要。
- 鏈接計數:表示有多少個文件名指向該 inode。硬鏈接會增加鏈接計數,而刪除硬鏈接會減少鏈接計數。當鏈接計數為 0 時,文件系統可以回收該 inode 和其關聯的數據塊。
- 數據塊指針:這是 inode 結構中最重要的部分之一。它指向文件實際存儲數據的數據塊。由于文件大小可能不同,數據塊指針的數量和組織方式也會有所不同。對于較小的文件,可能只需要幾個直接數據塊指針就能指向其所有數據。而對于較大的文件,可能還需要間接數據塊指針,通過多層索引來訪問大量的數據塊。
inode 的管理方式
- inode 表:文件系統中維護著一個 inode 表,用于存儲所有 inode 的信息。inode 表是一個固定大小的數組,每個元素對應一個 inode。當創建一個新文件時,文件系統會在 inode 表中分配一個空閑的 inode,并初始化其各項屬性。
- 分配與回收:文件系統在創建文件時,會從 inode 表中查找一個空閑的 inode 分配給該文件。當文件被刪除時,對應的 inode 會被標記為空閑,以便重新分配給其他文件。
inode 的運作原理
- 文件訪問:當用戶或程序訪問一個文件時,文件系統首先根據文件名在目錄中查找對應的 inode 編號。然后,文件系統從 inode 表或 inode 緩存中讀取該 inode 的信息,以獲取文件的權限、大小、數據塊指針等關鍵信息。根據這些信息,文件系統可以確定是否允許用戶訪問文件,并找到文件數據所在的數據塊,從而進行讀取或寫入操作。
- 文件系統一致性:inode 在維護文件系統的一致性方面起著關鍵作用。在文件系統進行寫入操作時,會先更新 inode 中的相關信息,如文件大小、修改時間等,然后再將數據寫入數據塊。這樣可以確保在系統崩潰或其他異常情況下,文件系統能夠通過 inode 中的信息來恢復文件的一致性。
- 硬鏈接與軟鏈接:硬鏈接是通過在不同的目錄項中創建指向同一個 inode 的鏈接來實現的。因此,多個硬鏈接共享同一個 inode 和數據塊,它們具有相同的 inode 編號。而軟鏈接(符號鏈接)則是一個獨立的文件,其 inode 中存儲的是指向目標文件的路徑信息。當訪問軟鏈接時,文件系統會根據軟鏈接 inode 中的路徑信息來找到目標文件的 inode,進而訪問目標文件。
關于inode:
1,inode以分區為單位,一個分區對應一套inode
2,inode分配時,只要確定起始inode即可
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
inode 位圖
文件系統中用于管理 inode 資源的數據結構,它通過位圖的方式來記錄 inode 的使用狀態:
定義與結構
- inode 位圖是一個由二進制位組成的數組,數組中的每一位對應著文件系統中的一個 inode。
- 當某一位的值為 0 時,表示對應的 inode 是空閑的,可供分配使用;當某一位的值為 1 時,則表示該 inode 已被占用,正在被某個文件或目錄使用。
作用
- inode 分配與回收:在創建新文件或目錄時,文件系統需要為其分配一個空閑的 inode。此時,文件系統會遍歷 inode 位圖,尋找值為 0 的位,找到后將其置為 1,表示該 inode 已被分配,并將該 inode 的編號返回給用戶或程序,用于創建文件或目錄。
- 快速查詢:通過 inode 位圖,文件系統可以快速判斷某個 inode 是否被占用,無需遍歷整個 inode 表。
與文件系統的關系
- 文件系統在初始化時會創建 inode 位圖,并根據文件系統的大小和 inode 數量來確定位圖的大小。在文件系統的運行過程中,inode 位圖會隨著文件的創建、刪除和 inode 的分配、回收而動態更新,以準確反映 inode 的使用情況。
優缺點
- 優點:inode 位圖具有高效、簡潔的數據結構,能夠快速實現 inode 的分配和回收,并且占用的存儲空間相對較小。它可以方便地進行位操作,通過簡單的邏輯運算就能完成對 inode 狀態的查詢和修改,適合文件系統頻繁的 inode 管理操作。
- 缺點:隨著文件系統中 inode 數量的增加,inode 位圖的大小也會相應增大。在查找空閑 inode 時,雖然整體效率較高,但如果需要連續分配多個 inode,可能需要遍歷較長的位圖區域才能找到足夠數量的空閑 inode,這在一定程度上會影響分配效率。此外,inode 位圖本身需要被正確地維護和保存,如果位圖損壞,可能會導致文件系統無法正確識別 inode 的狀態,進而引發文件系統的一致性問題。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?
block塊號
在文件系統中,block 塊號是統一分配的,這有助于高效地管理和組織磁盤空間。
block與 inode 的關系
inode和block的索引關系:
1. 直接索引:inode直接存儲數據塊編號,訪問速度快,適合小文件。
2. 間接索引:inode指向索引塊,索引塊存儲數據塊編號,可支持較大文件,但增加一次磁盤I/O。
3. 雙重間接索引和三重間接索引:通過多層索引支持超大型文件,不過多次磁盤I/O導致訪問速度降低。
? ??
- inode 中記錄了文件所占用的 block 塊號信息。當文件被創建時,文件系統會為其分配一個 inode,并根據文件的大小和分配策略為其分配相應的 block 塊號,然后將這些 block 塊號記錄在 inode 中。當訪問文件時,系統通過 inode 中存儲的 block 塊號來定位和讀取文件數據。
- 在文件系統的管理中,inode 和 block 塊號的分配是相互配合的。inode 負責管理文件的元數據,而 block 塊號則具體指向文件數據在磁盤上的存儲位置,兩者共同構成了文件系統對文件的完整管理機制。
———————————————————————————————————————————
文件系統操作文件的流程
文件系統增刪查改操作流程:
————————————————————————————————
增加文件:分配inode -> 檢查空間并分配數據塊 -> 更新inode和目錄項 -> 寫入文件數據
刪除文件:查找inode -> 釋放數據塊 -> 釋放inode -> 刪除目錄項
查詢文件:路徑解析 -> 查找inode -> 讀取inode信息 -> 讀取文件數據(可選)
修改文件:查找inode -> 檢查權限 -> 確定修改位置和所需空間 -> 分配新數據塊(如果需要) -> 更新數據塊內容 -> 更新inode信息
———————————————————————————————
文件系統進行增、刪、查、改文件的操作細節:
增加文件
- 分配 inode:當要創建新文件時,文件系統首先會從 inode 表中查找一個空閑的 inode。一旦找到空閑 inode,就將其分配給新文件,并初始化 inode 中的元數據,如文件的所有者、權限、創建時間等。
- 檢查空間并分配數據塊:接著,文件系統會根據新文件預計的大小,在磁盤上尋找足夠的連續或非連續空閑數據塊(block)。如果采用連續分配策略,就需要找到一段連續的空閑塊;若使用鏈接分配或索引分配,則可以更靈活地分配分散的塊。文件系統會通過塊位圖(記錄哪些塊已使用、哪些塊空閑)等數據結構來管理和查找空閑塊。
- 更新 inode 和目錄項:將分配的數據塊的信息(如塊號)記錄到 inode 中。同時,在文件所在的目錄中添加一個新的目錄項,該目錄項包含文件名和對應的 inode 編號,以此建立文件名和 inode 之間的關聯。
- 寫入文件數據:用戶程序將文件內容寫入內存緩沖區,文件系統會在合適的時機將緩沖區中的數據寫入到之前分配的數據塊中。
刪除文件
- 查找 inode
- 釋放數據塊:通過 inode 中記錄的數據塊信息,將文件占用的所有數據塊標記為空閑。更新塊位圖等數據結構,表明這些塊現在可以被其他文件使用。
- 釋放 inode:將該文件對應的 inode 標記為空閑。
- 刪除目錄項:從文件所在的目錄中刪除該文件對應的目錄項。
查詢文件
- 路徑解析:用戶提供文件的路徑名,文件系統會從根目錄開始,逐步解析路徑中的各個目錄名,通過目錄的 inode 找到對應的目錄數據塊,在其中查找下一級目錄或文件的目錄項。
- 查找 inode:最終找到目標文件的目錄項后獲取文件的 inode 編號。
- 讀取 inode 信息:根據 inode 編號讀取對應的 inode,獲取文件的元數據,如文件大小、權限、創建時間等。
- 讀取文件數據(可選):如果需要讀取文件內容,根據 inode 中記錄的數據塊信息,依次讀取數據塊并將其內容返回給用戶。
修改文件
- 查找 inode:到對應的 inode。
- 檢查權限:檢查當前用戶是否有修改該文件的權限,若權限不足則拒絕操作。
- 確定修改位置和所需空間:根據用戶的修改操作(如追加內容、覆蓋部分內容等),確定需要修改的數據塊位置。如果修改后文件大小增加,需要檢查磁盤上是否有足夠的空閑塊可供分配。
- 分配新數據塊(如果需要):若文件增大,按照文件系統的分配策略分配新的空閑數據塊,并將新塊的信息記錄到 inode 中。
- 更新數據塊內容:將修改后的數據寫入到對應的磁盤數據塊中。可以是覆蓋原有數據,也可能涉及到在新分配的塊中寫入新數據。
- 更新 inode 信息
?——————————————————————————————————————————
struct dentry
?結構
struct dentry
?簡介:
struct dentry
?是目錄項對象,用于表示文件系統中的一個目錄項,它是文件系統路徑查找的關鍵結構。每個目錄項都對應著文件系統中的一個文件名或目錄名,通過?dentry
?可以快速定位到文件或目錄的相關信息。- 連接關系:
dentry
?結構中有一個成員?d_inode
,它是指向該目錄項所對應的?inode
?結構的指針。通過這個指針,dentry
?可以獲取到文件或目錄的詳細元數據,如文件的權限、大小、創建時間等,這些信息都存儲在?inode
?中。 - 路徑查找:在進行文件路徑查找時,文件系統從根目錄開始,根據路徑中的各個文件名或目錄名,通過?
dentry
?結構逐步解析路徑。每個?dentry
?都代表路徑中的一個節點,當找到目標文件或目錄的?dentry
?后,就可以通過其?d_inode
?指針獲取對應的?inode
?信息,從而完成路徑查找并獲取文件或目錄的詳細信息。 - 緩存機制:
dentry
?結構還參與了文件系統的緩存機制。為了提高文件系統的性能,系統會將經常訪問的?dentry
?和?inode
?結構緩存在內存中。 - 目錄項操作:對目錄項的一些操作,如創建、刪除、重命名等,都需要通過?
dentry
?找到對應的?inode
,然后在?inode
?上進行相應的操作。例如,創建一個新文件時,會創建一個新的?dentry
?并分配一個新的?inode
,然后將?dentry
?的?d_inode
?指針指向新分配的?inode
。?
———————————————————————————————————————————
?創建文件系統
在 Linux 系統中,mkfs
?是一個用于創建文件系統的工具集合,它可以在磁盤分區或其他存儲設備上初始化文件系統,同時也會構建相應的?inode
?結構。
mkfs
?命令的基本用法
mkfs
?實際上是多個文件系統創建工具的前端命令,它會根據指定的文件系統類型調用相應的工具:
mkfs [-t 文件系統類型] [-b 塊大小] [-i inode大小] 設備文件名
- 參數說明:
-t
:指定要創建的文件系統類型,常見的有?ext2
、ext3
、ext4
、xfs
、btrfs
?等。-b
:指定文件系統的塊大小,單位為字節。不同的文件系統支持不同的塊大小,例如?ext4
?支持 1024、2048 或 4096 字節。-i
:指定?inode
?的大小,單位為字節。不過,并不是所有的文件系統都支持該選項。- 設備文件名:指定要創建文件系統的設備,如?
/dev/sda1
?表示硬盤的第一個分區。
舉個例子:
創建?ext4
?文件系統
mkfs -t ext4 /dev/sda1
上述命令會在?/dev/sda1
?分區上創建一個?ext4
?文件系統。在執行該命令時,mkfs.ext4
?工具會完成以下主要操作:
- 初始化超級塊:超級塊包含了文件系統的整體信息,如文件系統的類型、塊大小、
inode
?數量等。 - 分配?
inode
?表:根據文件系統的大小和指定的參數,分配一定數量的?inode
?并初始化?inode
?表。 - 創建塊組:
ext4
?文件系統會將磁盤空間劃分為多個塊組,每個塊組都有自己的?inode
?表、數據塊位圖和數據塊。 - 初始化數據塊:將數據塊標記為空閑,以便后續文件存儲使用。
指定塊大小和?inode
?數量
mkfs -t ext4 -b 4096 -i 16384 /dev/sda1
這個命令在?/dev/sda1
?分區上創建一個?ext4
?文件系統,塊大小為 4096 字節,每個?inode
?占用 16384 字節的空間。通過調整這些參數,可以根據實際需求優化文件系統的性能和空間利用率。
注意事項
- 數據丟失風險:在使用?
mkfs
?命令時要格外小心,因為它會擦除指定設備上的所有數據。在執行命令之前,請務必備份重要數據。 - 權限要求:需要使用具有 root 權限的用戶來執行?
mkfs
?命令,因為創建文件系統是一個需要高權限的操作。
———————————————————————————————————————————
掛載 - - 分區如何轉化為路徑
在 Linux 系統里,分區轉化為路徑主要借助掛載操作達成,這樣分區才可以以路徑的形式被訪問。
分區概述
分區是對物理磁盤進行劃分得到的邏輯區域,每個分區都有自己獨立的文件系統,像ext4
、xfs
等。例如,新硬盤可以被劃分為多個分區,像/dev/sda1
、/dev/sda2
等。
掛載的概念
掛載是把分區和文件系統樹中的某個目錄關聯起來的操作,這個目錄被叫做掛載點。掛載之后,分區里的文件和目錄就能夠通過掛載點路徑進行訪問。
分區轉化為路徑的具體步驟
1. 識別分區
2. 創建掛載點
mkdir /mnt/data
3. 掛載分區
mount /dev/sda1 /mnt/data
4. 自動掛載設置
/dev/sda1 /mnt/data ext4 defaults 0 0
把/dev/sda1
分區掛載到/mnt/data
目錄,文件系統類型為ext4
,使用默認掛載選項。
5. 卸載分區
當不需要訪問分區時,可使用umount
命令卸載分區。例如,卸載/mnt/data
掛載點:
umount /mnt/data
——————————————————————————————————————————
硬鏈接和軟鏈接
1.如何理解軟硬連接?
- ? ?軟連接有獨立的inode,軟連接內容上,保存的是目標文件的婚徑,windows 快捷方式
- ? ?硬鏈接不是獨立的文件,沒有獨立的inode,硬髓擅本質就星一組文件名和已經存在的文件的映射關系!
2.為什么要有軟硬連接?各種應用場景軟連接:快捷方法
在 Linux 系統中,硬鏈接和軟鏈接(符號鏈接)是兩種不同的文件鏈接方式:
硬鏈接(Hard Link)
理解
- 硬鏈接本質上是一個指向同一個 inode(索引節點)的文件名。一個文件可以有多個硬鏈接,這些硬鏈接都指向同一個 inode,它們共享相同的文件數據。
- 當你創建一個硬鏈接時,實際上是在文件系統的目錄結構中增加了一個新的目錄項,該目錄項指向原文件的 inode。
作用
- 數據備份與共享:通過創建硬鏈接,可以在不同的目錄下訪問同一個文件,而無需復制文件內容。、因為所有硬鏈接都指向同一個 inode,對其中任何一個鏈接所做的修改都會反映到其他鏈接上。
- 防止誤刪除:只要文件的 inode 引用計數不為 0(即至少有一個硬鏈接存在),文件的數據就不會被真正刪除。即使刪除了原文件名,只要還有其他硬鏈接存在,文件數據仍然可以通過這些鏈接訪問。
意義
- 節省磁盤空間:避免了因復制文件而造成的磁盤空間浪費。
- 數據一致性:確保所有硬鏈接訪問的是同一文件數據,不會出現數據不一致的問題。
注意事項:
- 不能跨文件系統:由于硬鏈接是通過 inode 來實現的,而不同文件系統的 inode 編號是獨立的,所以硬鏈接不能跨越不同的文件系統創建
- 不能指向目錄(避免形成環狀鏈接):通常情況下,不允許對目錄創建硬鏈接。這是因為目錄的硬鏈接可能會破壞文件系統的目錄結構和鏈接關系,導致文件系統的不一致性和難以管理的問題。不過,在某些特殊情況下,如系統內部管理等,可能會有對目錄創建硬鏈接的需求,但這需要非常小心謹慎地操作。
- 原文件刪除問題:雖然硬鏈接可以防止文件被誤刪除,但如果原文件被刪除后,再創建一個同名的新文件,那么新文件與原來的硬鏈接將沒有任何關系。新文件會有自己獨立的 inode,而硬鏈接仍然指向原來文件的 inode。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~?
軟鏈接(符號鏈接,Symbolic Link)
理解
- 軟鏈接是一種特殊的文件,它包含了另一個文件或目錄的路徑名。當訪問軟鏈接時,系統會自動將其解析為所指向的目標文件或目錄。軟鏈接有自己獨立的 inode,它與目標文件的 inode 是不同的。
- 軟鏈接類似于 Windows 系統中的快捷方式,它只是一個指向目標文件的指針,而不包含實際的文件數據。
作用
- 跨文件系統鏈接:軟鏈接可以跨越不同的文件系統,即使目標文件位于不同的磁盤分區或掛載點上,也可以創建軟鏈接指向它。這在需要在不同文件系統之間建立聯系時非常有用。
- 簡化路徑:當文件或目錄的路徑很長或復雜時,可以創建一個軟鏈接來簡化訪問。
- 版本管理:在軟件版本管理中,軟鏈接可以用于指向不同版本的軟件。
意義
- 靈活性:軟鏈接提供了更高的靈活性,允許在不同文件系統和位置之間建立鏈接,方便文件和目錄的組織和管理。
- 兼容性:軟鏈接可以在不同的文件系統和操作系統之間保持兼容,使得文件和目錄的共享和訪問更加方便。
注意事項:
軟鏈接
- 目標文件移動或刪除:如果軟鏈接所指向的目標文件或目錄被移動、重命名或刪除,軟鏈接將失效。此時訪問軟鏈接會提示 “無法訪問,目標文件或目錄不存在” 等錯誤信息。所以在使用軟鏈接時,要確保目標文件的穩定性,避免隨意移動或刪除目標文件。
- 權限問題:軟鏈接本身有自己的權限設置,但它并不影響目標文件的權限。同時,修改軟鏈接的權限不會影響目標文件的權限,反之亦然。
- 相對路徑和絕對路徑:創建軟鏈接時,可以使用相對路徑或絕對路徑。使用相對路徑時,要注意軟鏈接所在目錄與目標文件之間的相對位置關系。如果軟鏈接所在目錄被移動,可能會導致軟鏈接失效。而絕對路徑則不受軟鏈接所在目錄位置的影響,但如果目標文件的絕對路徑發生變化,軟鏈接也會失效。
創建方法
以下是創建硬鏈接和軟鏈接的命令示例:
# 創建硬鏈接
ln /path/to/original_file /path/to/hard_link# 創建軟鏈接
ln -s /path/to/original_file /path/to/symbolic_link
——————————————————————————————————————————?
inode 引用計數
inode 引用計數,用于記錄指向同一個 inode 的硬鏈接數量。
基本概念
- 每個文件在文件系統中都有一個唯一的 inode 與之對應,inode 引用計數表示有多少個不同的文件名(硬鏈接)指向這個 inode。
工作原理
- 當創建一個新文件時,文件系統會為其分配一個 inode,并將 inode 引用計數初始化為 1。此時,該文件只有一個文件名指向它的 inode。
- 當創建一個指向該文件的硬鏈接時,文件系統會在目錄中創建一個新的目錄項,將其指向相同的 inode,并將 inode 引用計數加 1。這樣,就有兩個文件名指向同一個 inode,它們共享相同的文件數據和元數據。
- 當刪除一個硬鏈接(即刪除一個文件名)時,文件系統會將 inode 引用計數減 1。只要 inode 引用計數大于 0,文件的數據就會保留在磁盤上,不會被刪除。只有當 inode 引用計數減到 0 時,文件系統才會認為該文件不再被使用,從而釋放其占用的磁盤空間,包括 inode 和數據塊。
作用
- 判斷文件是否可刪除:inode 引用計數是文件系統判斷一個文件是否可以被刪除的重要依據。只要有至少一個硬鏈接存在,即 inode 引用計數大于 0,文件就不會被真正刪除,即使所有指向該文件的文件名都被刪除,只要 inode 引用計數不為 0,文件數據仍然可以通過其他硬鏈接訪問。
- 資源管理:通過 inode 引用計數,文件系統可以有效地管理磁盤空間和 inode 資源。當多個硬鏈接指向同一個文件時,它們共享相同的文件數據,避免了重復存儲,節省了磁盤空間。同時,文件系統可以根據 inode 引用計數來跟蹤文件的使用情況,以便進行優化和管理。
查看 inode 引用計數
- 可以使用
ls -li
命令來查看文件的 inode 信息,其中i
選項用于顯示 inode 編號,而文件權限后面的數字就是 inode 引用計數。
123456 -rw-r--r-- 2 user group 1024 Jan 1 00:00 file.txt
2
就是 inode 引用計數,表示有兩個硬鏈接指向這個文件。
———————————————————————————————————————————
動態庫和靜態庫
基本概念
- 靜態庫:靜態庫文件的擴展名通常為?
.a
(archive),它是一組目標文件(.o
?文件)的集合。在編譯時,鏈接器會將靜態庫中被程序使用的代碼直接復制到可執行文件中。 - 動態庫:動態庫文件的擴展名通常為?
.so
(shared object),也稱為共享庫。在程序運行時,動態鏈接器會將動態庫加載到內存中,多個程序可以共享同一個動態庫的副本。
作用
- 靜態庫
- 代碼復用:多個程序可以使用同一個靜態庫,避免了代碼的重復編寫,提高了開發效率。
- 獨立性:生成的可執行文件包含了所有需要的代碼,不依賴于外部的庫文件,方便在不同環境中部署。
- 動態庫
- 節省內存:多個程序可以共享同一個動態庫的副本,減少了內存的使用。
- 更新方便:如果動態庫進行了更新,只需要替換庫文件,而不需要重新編譯所有使用該庫的程序。
使用方法
# === 靜態庫操作 ===
# 編譯源文件生成目標文件,-c 選項表示只編譯不鏈接gcc -c add.c sub.c# 使用 ar 命令創建靜態庫
# r 表示將目標文件插入到靜態庫中,如果靜態庫已存在則更新
# c 表示創建靜態庫,如果靜態庫不存在則創建
# s 表示寫入一個目標文件索引到靜態庫中,加快鏈接速度ar rcs libmath.a add.o sub.o# 編譯并鏈接靜態庫
# -o main 表示生成名為 main 的可執行文件
# main.c 是主程序源文件
# -L. 表示在當前目錄下查找庫文件
# -lmath 表示鏈接名為 libmath.a 的靜態庫gcc -o main main.c -L. -lmath# 運行使用靜態庫鏈接生成的程序
./main
動態庫
# === 動態庫操作 ===
# 編譯源文件生成與位置無關的代碼(PIC),-fPIC 選項用于生成動態庫所需的代碼gcc -fPIC -c add.c sub.c# 使用 gcc 命令創建動態庫
# -shared 選項表示生成共享庫(動態庫)
# -o libmath.so 表示生成名為 libmath.so 的動態庫gcc -shared -o libmath.so add.o sub.o# 編譯并鏈接動態庫,參數含義與靜態庫鏈接時相同gcc -o main main.c -L. -lmath# 設置動態庫的搜索路徑,將當前目錄添加到 LD_LIBRARY_PATH 環境變量中
# 這樣動態鏈接器在運行時可以找到我們創建的動態庫export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.# 運行使用動態庫鏈接生成的程序
./main
在文件系統中的意義
- 靜態庫:靜態庫文件通常存放在系統的標準庫目錄(如?
/usr/lib
?或?/usr/local/lib
)中,或者項目的特定目錄下。靜態庫在編譯時被嵌入到可執行文件中,因此在文件系統中,可執行文件是獨立的,不依賴于靜態庫文件的存在。 - 動態庫:動態庫文件也通常存放在標準庫目錄中。多個程序可以共享同一個動態庫文件,這減少了文件系統中重復代碼的存儲,提高了磁盤空間的利用率。
注意事項
- 靜態庫
- 文件大小:由于靜態庫會將代碼復制到可執行文件中,因此可執行文件的大小會增加,特別是在使用多個大型靜態庫時。
- 更新困難:如果靜態庫進行了更新,需要重新編譯所有使用該庫的程序。
- 動態庫
- 依賴問題:程序運行時需要確保動態庫文件存在,并且動態鏈接器能夠找到它們。如果動態庫文件缺失或版本不兼容,程序可能無法正常運行。
- 版本管理:需要注意動態庫的版本管理,避免不同版本的動態庫之間的兼容性問題。
———————————————————————————————————————————
三條?gcc
?命令:用于編譯和鏈接 C 語言源文件
# 編譯 main.c,使用默認輸出文件名,添加 mystdio 為庫搜索路徑
-Lmystdio 表示將 mystdio 這個目錄添加到庫文件的搜索路徑里
gcc main.c -Lmystdio# 編譯 main.c,指定輸出文件名為 main,添加 mystdio 為庫搜索路徑
gcc main.c -o main -Lmystdio# 編譯 main.c,指定輸出文件名為 main,添加當前目錄和 mystdio 為庫搜索路徑
# gcc 在鏈接庫文件時,會先在當前目錄查找,接著在 mystdio 目錄查找
(-L. 表示將當前目錄添加到庫文件的搜索路徑)
gcc main.c -o main -L. -Lmystdio 輸出文件名:第一條命令使用默認的輸出文件名 a.out,而后兩條命令通過 -o main 選項將輸出文件名指定為 main。
庫搜索路徑:第一條命令僅將 mystdio 目錄添加到庫搜索路徑;第二條命令同樣只添加了 mystdio 目錄;第三條命令添加了兩個目錄,即當前目錄(.)和 mystdio 目錄。
———————————————————————————————————————————
生成動態庫/靜態庫
編寫?Makefile
?生成和發布庫
項目結構
假設項目結構如下:
project/
├── src/
│ ├── add.c
│ ├── sub.c
│ └── math.h
├── Makefile
└── output/ # 用于存放發布文件
靜態庫?Makefile
# 編譯器和編譯選項
CC = gcc
CFLAGS = -Wall -c# 源文件和目標文件
SRCS = src/add.c src/sub.c
OBJS = $(SRCS:.c=.o)# 靜態庫名稱
LIBRARY = libmath.a# 發布目錄
OUTPUT_DIR = output
LIB_DIR = $(OUTPUT_DIR)/lib
INCLUDE_DIR = $(OUTPUT_DIR)/include# 默認目標
all: $(LIBRARY) output# 創建靜態庫
$(LIBRARY): $(OBJS)ar rcs $(LIBRARY) $(OBJS)# 編譯源文件生成目標文件
%.o: %.c$(CC) $(CFLAGS) $< -o $@# 發布目標
output: $(LIBRARY)@mkdir -p $(LIB_DIR) $(INCLUDE_DIR)@cp $(LIBRARY) $(LIB_DIR)@cp src/math.h $(INCLUDE_DIR)# 清理生成的文件
clean:rm -f $(OBJS) $(LIBRARY)rm -rf $(OUTPUT_DIR)
動態庫?Makefile
makefile
# 編譯器和編譯選項
CC = gcc
CFLAGS = -Wall -fPIC -c
LDFLAGS = -shared# 源文件和目標文件
SRCS = src/add.c src/sub.c
OBJS = $(SRCS:.c=.o)# 動態庫名稱
LIBRARY = libmath.so# 發布目錄
OUTPUT_DIR = output
LIB_DIR = $(OUTPUT_DIR)/lib
INCLUDE_DIR = $(OUTPUT_DIR)/include# 默認目標
all: $(LIBRARY) output# 創建動態庫
$(LIBRARY): $(OBJS)$(CC) $(LDFLAGS) $^ -o $@# 編譯源文件生成目標文件
%.o: %.c$(CC) $(CFLAGS) $< -o $@# 發布目標
output: $(LIBRARY)@mkdir -p $(LIB_DIR) $(INCLUDE_DIR)@cp $(LIBRARY) $(LIB_DIR)@cp src/math.h $(INCLUDE_DIR)# 清理生成的文件
clean:rm -f $(OBJS) $(LIBRARY)rm -rf $(OUTPUT_DIR)
Makefile
?解釋
- 變量定義:
CC
:指定編譯器為?gcc
。CFLAGS
:編譯選項,靜態庫使用?-Wall -c
,動態庫額外添加?-fPIC
?生成位置無關代碼。LDFLAGS
:動態庫鏈接選項,使用?-shared
?生成共享庫。SRCS
:源文件列表。OBJS
:目標文件列表,通過替換?.c
?為?.o
?生成。LIBRARY
:庫的名稱。OUTPUT_DIR
:發布目錄。LIB_DIR
:存放庫文件的子目錄。INCLUDE_DIR
:存放頭文件的子目錄。
- 目標規則:
all
:默認目標,先構建庫,再執行發布操作。$(LIBRARY)
:創建靜態庫或動態庫。%.o: %.c
:將?.c
?源文件編譯成?.o
?目標文件。output
:創建發布目錄,將庫文件和頭文件復制到相應目錄。clean
:刪除生成的目標文件、庫文件和發布目錄。
后續發布流程
靜態庫發布
- 生成靜態庫和發布文件:在項目根目錄下執行?
make
?命令,Makefile
?會自動編譯源文件、生成靜態庫,并將庫文件和頭文件復制到?output
?目錄。 - 打包發布文件:將?
output
?目錄打包成一個壓縮文件,例如使用?tar
?命令:
tar -czvf libmath-static.tar.gz output
- 分發:將生成的?
libmath-static.tar.gz
?文件分發給其他開發者。其他開發者解壓該文件后,可在自己的項目中使用靜態庫。使用時,需要在編譯命令中指定庫文件路徑和頭文件路徑,例如:
gcc main.c -o main -I/path/to/output/include -L/path/to/output/lib -lmath
動態庫發布
- 生成動態庫和發布文件:同樣在項目根目錄下執行?
make
?命令,生成動態庫并完成發布操作。 - 打包發布文件:將?
output
?目錄打包成壓縮文件:
tar -czvf libmath-dynamic.tar.gz output
- 分發:將?
libmath-dynamic.tar.gz
?文件分發給其他開發者。其他開發者解壓后,在編譯時指定庫文件路徑和頭文件路徑:
gcc main.c -o main -I/path/to/output/include -L/path/to/output/lib -lmath
在運行程序時,需要設置動態庫的搜索路徑,例如:
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/output/lib
./main
?——————————————————————————————————————————
系統查找動態庫?
如何給系統指定路徑,查找我自己的動態庫
- 拷貝到系統默認路徑下,
- 在系統路徑,建立軟連接:? ? ?ln -s 源文件路徑 目標軟連接路徑
- Linux系統中,OS查找動態庫,環境變量,LD_LIBRAY_PATH? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? (export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/user/mylibs)
- ldconfig配置/etc/ld.so.conf.d/,ldconfig更新
注意:
如果同時提供動態庫,靜態庫,g++/gcc默認使用動態庫
如果強制靜態庫,必須提供相應的靜態庫
如果只提供靜態庫,但是連接方式是動態鏈接的的,g++/gcc只能針對你的.a局部性采用靜態鏈接。?
?——————————————————————————————————————————
動態庫加載過程
- 啟動加載:當可執行文件被運行時,操作系統的加載器會負責將可執行文件和它依賴的動態庫加載到內存中。加載器首先會讀取可執行文件的頭部信息,獲取其依賴的動態庫列表。
- 查找動態庫:根據動態庫的名稱,加載器會按照一定的路徑順序查找動態庫文件。通常,系統會先在默認的系統庫路徑(如
/lib
、/usr/lib
等)中查找,然后再檢查環境變量LD_LIBRARY_PATH
指定的路徑,如果還找不到,可能會在當前目錄下查找。 - 加載動態庫:找到動態庫后,加載器會將動態庫映射到進程的地址空間中。它會為動態庫分配一段內存區域,并將動態庫的代碼和數據加載到該區域。同時,加載器還會根據動態庫的依賴關系,遞歸地加載其依賴的其他動態庫。
- 符號解析:加載完成后,加載器會對動態庫中的符號進行解析。它會將可執行文件和動態庫中相互引用的符號地址進行修正,使得它們能夠正確地相互調用。這一過程涉及到重定位操作,即根據符號在內存中的實際地址,修改代碼中對該符號的引用地址。
?——————————————————————————————————————————
ELF(Executable and Linking Format)概述
ELF 即可執行與可鏈接格式,是一種在 Linux里廣泛使用的文件格式,主要用于可執行文件、目標文件、共享庫以及核心轉儲文件。其設計具有靈活性和可擴展性,為操作系統、編譯器、鏈接器和調試器等工具提供了統一接口,增強了軟件的可移植性和互操作性。
ELF 文件結構
- ELF 頭部(ELF Header):處在文件起始位置,包含了文件的基本信息,像文件類型(可執行文件、目標文件、共享庫等)、目標機器架構、程序入口地址等。
- 程序頭部表(Program Header Table):描述了如何把 ELF 文件映射到內存中,涵蓋了各個段(Segment)的信息,如段的類型、偏移量、大小、內存地址等。
- 節頭部表(Section Header Table):包含了各個節(Section)的信息,例如節的名稱、類型、大小、偏移量等。節是文件中具有特定用途的數據塊。
- 節(Section):存儲具體的數據和代碼。
- 符號表(Symbol Table)
- 重定位表(Relocation Table)
ELF 文件的作用
- 可執行文件
- 目標文件:鏈接器可以把多個目標文件和庫文件鏈接成一個可執行文件或共享庫。
- 共享庫(動態鏈接庫):多個程序在運行時可以共享同一個共享庫的代碼和數據,從而節省內存空間。
- 核心轉儲文件
——————————————————————————————————————————?
Section(節)介紹
基本概念
Section 是 ELF 文件里具有特定用途的數據塊,每個 section 都有自己的名字、大小、偏移量等屬性。編譯器、鏈接器和加載器會依據 section 的信息對 ELF 文件進行處理和操作。
常見 Section 類型
- 代碼段(
.text
):存儲程序的可執行代碼,也就是程序的機器指令。通常這部分內容是只讀的,防止程序在運行過程中意外修改自身代碼。 - 數據段(
.data
):存儲已初始化的全局變量和靜態變量。這些變量在程序啟動時就已經被賦予了初始值。
- 未初始化數據段(
.bss
):存儲未初始化的全局變量和靜態變量。這些變量在程序啟動時會被自動初始化為 0。因為它們初始值都為 0,所以在 ELF 文件中不需要為它們分配實際的存儲空間,只需要記錄它們的大小即可,這樣可以節省文件空間。例如: - 只讀數據段(
.rodata
):存儲只讀數據,如字符串常量。這些數據在程序運行過程中不能被修改。 - 符號表
- 重定位表
Section 的作用
- 模塊化組織:將不同類型的數據和代碼分開存儲在不同的 section 中,使 ELF 文件的結構更清晰,便于編譯器、鏈接器和加載器處理。
- 優化存儲:如?
.bss
?節只記錄未初始化變量的大小,不實際分配存儲空間,節省文件空間。 - 方便調試和分析:符號表和重定位表等 section 提供了程序的詳細信息,便于開發人員進行調試和分析。
查看 ELF 文件 Section 信息的工具
- readelf:使用?
readelf -S your_program.elf
?命令可列出 ELF 文件中所有 section 的詳細信息,包括名稱、類型、大小、偏移量等。
- objdump:使用?
objdump -h your_program.elf
?命令可顯示 ELF 文件的 section 頭部信息,包括 section 的編號、名稱、大小等。