[ linux-系統 ] 軟硬鏈接與動靜態庫

軟硬鏈接

介紹

軟鏈接

通過下圖可以看出軟鏈接和原始文件是兩個獨立的文件,因為軟鏈接有著自己的inode編號:

具有獨立的 inode ,也有獨立的數據塊,它的數據塊里面保存的是指向的文件的路徑,公用 inode

硬鏈接

通過下圖可以看出硬鏈接和原始文件是同一個文件,因為二者的inode編號是相同的,并且創建完硬鏈接后改變了原始文件的引用計數

觀察 inode 編號可以發現,軟硬鏈接的區別:是否具有獨立的Inode。

軟鏈接具有獨立的Inode:可以被當作獨立的文件看待。

如果想刪除一個軟鏈接或者硬鏈接,可以使用刪除命令rm,也可以使用unlink命令,例如刪除上面的硬鏈接:

軟硬鏈接的使用場景?

如果對一個文件既創建了軟鏈接,也創建了一個硬鏈接,那么刪除原文件時,軟鏈接將失效,但是硬鏈接不會:

此時再訪問軟鏈接指向的文件中的內容就會無效,但不會影響硬鏈接

因為對于存在硬鏈接的文件來說,刪除原文件就是減少其引用計數,只要引用計數不為0,那么該文件就不會被認為失效,而創建硬鏈接會增加原文件的引用計數,所以此時刪除原文件就只是讓原文件的引用計數從2變為1,從而保證文件還在硬盤上存在

從上面刪除文件的例子可以看出,使用硬鏈接可以做到對原文件的備份

我們新建一個目錄,引用計數是2,新建一個普通文件,引用計數為1

empty目錄里面有兩個隱藏文件,其中一個是 .? 表示當前路徑,文件名不同,inode相同

在empty里面再新建一個目錄dir,引用計數變為3,新建目錄dir里面有隱藏文件 ..?表示上級路徑

和empty的inode相同,相當于硬鏈接

linux不允許對目錄新建硬鏈接(會形成環狀路徑)

下面看一下軟鏈接的應用場景:

軟鏈接就像windows下的快捷方式,路徑直接跳轉

當前目錄下有一個test.cc文件,g++編譯形成可執行程序,直接運行可以看到運行結果

但是上面的運行需要帶./限定才能正常運行a.out文件,在linux命令行與環境變量提到之所以需要./作為限定是因為Linux默認查找可執行文件的路徑是/bin路徑下,而當前a.out文件并不在該目錄。當時解決這個問題的辦法就是將a.out移動到/bin路徑下

實際上,/bin也是一個軟鏈接,該鏈接指向的原文件是/usr/bin

在軟鏈接部分,就可以通過為a.out創建軟鏈接,再將軟鏈接移動到/bin路徑下即可執行a.out文件:此時的軟鏈接指向的原文件需要使用絕對路徑

這時候直接寫a.out不用帶./就可以運行test.txt程序了

通過上面的例子可以看出,軟鏈接的作用主要是相當于一個快捷方式?,軟鏈接里面保存的是與文件所處路徑的映射關系

動靜態庫

創建靜態庫與使用

我們自己封裝了兩個簡單的庫,stdio,string庫,為了將我們的庫給別人使用,并且不暴露源碼,我們可以將這兩個庫制作成靜態庫

制作靜態庫的兩種方式

方式一:將靜態庫放到/usr/lib64目錄下,將頭文件放到/usr/include目錄下 (安裝到系統里)

制作步驟如下:

1.將需要打包為靜態庫的.c文件編譯生成.o文件


2.使用ar -rc lib庫名.a 指定的.o文件生成靜態庫,注意靜態庫的名稱一定要以lib開頭,后綴為.a


3.將頭文件使用cp命令拷貝到/usr/include目錄下使用cp命令將靜態庫拷貝到/usr/lib64目錄下。


這一步僅僅只是完成靜態庫的安裝,如果直接編譯要生成可執行程序的文件會出現鏈接報錯

4.編譯要生成可執行程序的文件時使用gcc 文件名 -l+庫名稱(去掉lib和.a)

方法二:?通過編譯器選項指定靜態庫路徑,并且使用當前目錄下的頭文件

默認情況下,gcc不會在當前目錄查找需要的靜態庫,也不會在指定目錄中自動查找需要的靜態庫,所以需要指定靜態庫路徑和靜態庫名稱

當前目錄下存在靜態庫、頭文件和用于生成可執行程序的源文件:?

使用下面的指令指定靜態庫所在位置:

gcc 文件名 -L路徑 -l靜態庫(去掉lib和.a)

?方式3:通過編譯器選項指定頭文件和靜態庫文件的位置

使用Makefile自動化生成靜態庫和.o文件

使用下面的指令指定頭文件所在路徑和靜態庫所在路徑

gcc 文件名 -I.h所在位置 -L靜態庫路徑 -l靜態庫名稱(去掉lib和.a)

運行結果如下:?

創建動態庫與使用?

創建動態庫的命令不再是ar而是直接使用gcc,但是在生成動態庫前必須保證.o文件具有絕對地址,即對.o文件的生成方式需要改變:使用-fPIC選項生成帶有與位置無關碼的.o文件,即:

運行結果如下

動態庫前兩種創建方式與靜態庫一樣

第三種創建方式與靜態庫不同

?gcc編譯動態庫時,并沒有把代碼加載到程序里,靜態庫加載到程序里面了,直接運行即可。

動態庫沒有加載到程序里,運行時找不到 libmystdio ,系統找動態庫默認從lib64找

如何給系統指定路徑,查找自己的動態庫:

1.拷貝到系統默認路徑下(與靜態庫使用第一種方法相同)

2.在系統路徑,建立軟鏈接

3.linux系統中,OS查找動態庫,環境變量,LD_LIBRARY_PATH

4.?ldconfig?案:配置/ etc/ld.so.conf.d/ ,ldconfig更新? (系統級別)

動靜態庫同時使用的細節

1.同時存在動靜態庫時,gcc/g++默認使用動態庫

如果想使用靜態庫,編譯時應該帶上 -static?

2.如果強制靜態鏈接,必須提供對應的靜態庫

3.如果只提供靜態庫,但是連接方式是動態鏈接的,gcc/g++只能針對.a局部性采用靜態鏈接

動態庫的加載

先探討?下編譯和鏈接的整個過程,來更好的理解動靜態庫的使用原理

ELF的形成與加載

編譯的過程其實就是將我們程序的源代碼翻譯成CPU能夠直接運行的機器代碼。在編譯之后會?成兩個擴展名為 .o 的文件,它們被稱作目標文件

目標文件是?個?進制的文件,文件的格式是 ELF ,是對?進制代碼的?種封裝

ELF文件

以下四種都是ELF文件:

1.可重定位文件(xxx.o文件) 2.可執行程序? 3.共享目標文件(.so文件)? 4.內核轉儲(core dumps)

ELF文件由以下四部分組成:

ELF頭(ELF header) :描述?件的主要特性。其位于?件的開始位置,它的主要?的是定位文件的其他部分。
? 程序頭表(Program header table) :列舉了所有有效的段(segments)和他們的屬性。表?
記著每個段的開始的位置和位移(offset)、?度,畢竟這些段,都是緊密的放在?進制?件中,需要段表的描述信息,才能把他們每個段分割開。
? 節頭表(Section header table) :包含對節(sections)的描述。
? 節(Section ):ELF?件中的基本組成單位,包含了特定類型的數據。ELF?件的各種信息和數據都存儲在不同的節中,如代碼節存儲了可執?代碼,數據節存儲了全局變量和靜態數據等

最常見的節:

代碼節(.text) : 用于保存機器指令,是程序的主要執行部分

數據節(.data) :保存已初始化的全局變量和局部靜態變量

鏈接就是將一個一個的相同屬性的section合并

對任何一個文件,文件的內容就是一個巨大的“一維數組”,標識文件任何一個區域,用偏移量+大小的方式

動態庫的加載

使用動態庫的可執行程序在調用動態庫中的方法時需要知道動態庫的地址,動態庫還未加載到內存之前,先使用一些內容進行占位,等到執行到指定的動態庫代碼再加載動態庫,此時就形成了動態庫的虛擬地址和物理地址映射關系,根據這個虛擬地址替換掉進程中調用動態庫代碼的占位內容即可。這個過程也被稱為地址重定位

看似上面的思路好像沒問題,實際上,虛擬地址空間的代碼區是不可寫的,也就是說,如果進程的代碼加載到虛擬地址空間就不無法再更改其中的內容,那么此時又是如何做到使用動態庫加載到內存之后的虛擬地址替換進程調用動態庫代碼的位置的內容

其實,進程調用動態庫代碼的位置的內容并不是直接寫動態庫的地址,而是寫入一個GOT表的地址,這個表中存儲的就是指定動態庫和對應的虛擬地址的映射關系,進程在調用動態庫代碼的位置此時只需要寫上調用的是GOT表中的哪一個動態庫的下標即可,剩下的就交給GOT表來進行,即當動態庫加載到內存后,虛擬地址填充到GOT表指定動態庫對應下標即可。這也就是所謂的「生成與位置無關碼」

所以,一個動態庫之所以可以只加載一次而可以被任何進程所調用,本質就是因為這個GOT表,只需要知道這個GOT表的地址和對應庫的下標,即可調用對應動態庫中的內容

重談地址空間--可執行程序,加載問題

可執行程序是有地址的

CPU要執行進程中的代碼,就需要知道對應代碼的地址,所以在磁盤的可執行程序中,盡管其未加載到內存,但是在編譯鏈接時就已經形成了地址,使用下面的指令對main程序進行反匯編可以看到每一個步驟對應的虛擬地址

注意,不是物理地址,因為此時可執行程序還沒有被加載到內存,只有被加載到內存后,才有物理地址。程序在加載到內存之前只有虛擬地址邏輯地址,只有在加載到物理內存后才會被分配對應的物理地址

objdump -S指令顯示目標文件的詳細信息

平坦模式:邏輯地址=起始地址+偏移量?

平坦模式(Flat Mode)是指在計算機系統中的一種內存管理模式,其中整個地址空間被看作是單一的、連續的線性空間。在這種模式下,所有代碼和數據都位于一個大的、平坦的地址范圍內,沒有分段或分區的概念。這種模式簡化了編程模型,使得編譯器和程序員不需要處理復雜的段選擇符或偏移量計算

ELF在沒有加載到內存的時候就已經按照[000...000,FFF...FFF](虛擬地址)進行編址了?

?結論:編譯器編譯,就已經形成虛擬地址了

當可執行程序加載到內存之后,其ELF中的LOAD部分的內容就會分別被加載到指定的區域,例如.text的內容被加載到代碼區.data的內容被加載到數據區等,這個過程就完成了虛擬地址空間的初始化

但是只有初始化還不夠,為了保證物理地址和可執行程序的虛擬地址可以匹配,此時就需要頁表進行對應的映射

上面整個過程完成,一個可執行程序就從硬盤加載到內存,變為了一個可以被CPU調度的進程

接著,CPU要執行這個進程,PC寄存器就需要找到第一條指令的地址(即找到入口地址),這個地址在ELF頭中可以看到Entry point address字段:

但是這個地址依舊是虛擬地址,所以依舊需要使用頁表進行映射,對應著的就是反匯編代碼中的<_start>地址(此處<_start>相當于關于C語言函數棧幀:main函數被其他函數調用的__tmainCRTStartup())

所以,不論是進程還是CPU的PC寄存器,二者訪問到的都是虛擬地址,但是這個虛擬地址要和物理地址在頁表中建立映射關系。同時CPU內部還有一個寄存器,稱為CR3寄存器,其中存儲的就是頁表本身的物理地址,這個寄存器是操作系統本身使用的。有了CR3寄存器后,就需要一個硬件配合其完成查表的工作,這個硬件稱為MMU,也是在CPU內部。還有一個寄存器EIP,將pc指針中的虛擬地址,通過MMU查表轉化為物理地址

虛擬空間是操作系統,CPU,編譯器協作下的產物,通過上面的過程,再次思考為什么需要有虛擬地址和虛擬地址空間:編譯器在編譯代碼時不再需要考慮物理內存,完成操作系統和編譯器進行解耦合。

理解虛擬地址空間的區域劃分

前面提到,虛擬地址空間初始化時會由ELF文件中的內容對指定區域進行初始化,但是并沒有看到ELF文件中存在對棧、堆和共享區進行初始化的部分,這些部分如何進行的初始化就是下面需要討論的問題

實際上,虛擬地址空間中還存在一個結構,稱為vm_area_struct,即虛擬區域結構,其對應的部分源碼如下:

struct vm_area_struct {struct mm_struct * vm_mm;    /* The address space we belong to. */unsigned long vm_start;        /* Our start address within vm_mm. */unsigned long vm_end;        /* The first byte after our end addresswithin vm_mm. *//* linked list of VM areas per task, sorted by address */struct vm_area_struct *vm_next;
};

真正的棧、堆和共享區都是vm_area_struct結構對象,有著自己的vm_startvm_end用于標記區域的開始和結束,每一個vm_area_struct結構對象通過鏈表進行鏈接。所以,CPU在訪問棧、堆和共享區時實際上訪問的也是對應的vm_area_struct對象的虛擬地址,在頁表中也存在著這些虛擬地址和物理地址的映射

有了上面這種思想,當一個可執行程序有很多內容時,操作系統可以考慮先加載一部分的Section形成vm_area_struct對象,再根據需要加載后面的Section,這也就實現了Section的懶加載

所以,如果有多個動態庫需要加載,本質上就是創建一個vm_area_struct結構對象鏈接到指定的區域

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/912761.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/912761.shtml
英文地址,請注明出處:http://en.pswp.cn/news/912761.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

3D 商品展示與 AR 試戴能為珠寶行業帶來一些便利?

對于珠寶行業而言&#xff0c;長久以來&#xff0c;如何讓消費者在做出購買決策之前&#xff0c;便能真切且直觀地領略到珠寶獨一無二的魅力&#xff0c;始終是橫亙在行業發展道路上的一道棘手難題。而 3D 互動營銷的橫空出世&#xff0c;恰似一道曙光&#xff0c;完美且精準地…

電子電氣架構 --- SOVD功能簡單介紹

我是穿拖鞋的漢子,魔都中堅持長期主義的汽車電子工程師。 老規矩,分享一段喜歡的文字,避免自己成為高知識低文化的工程師: 簡單,單純,喜歡獨處,獨來獨往,不易合同頻過著接地氣的生活,除了生存溫飽問題之外,沒有什么過多的欲望,表面看起來很高冷,內心熱情,如果你身…

【Java編程動手學】 Java中的運算符全解析

文章目錄 一、引言二、算術運算符1、基本概念2、具體運算符及示例 三、關系運算符1、基本概念2、具體運算符及示例 四、自增減運算符1、基本概念2、具體運算符及示例 五、邏輯運算符1、基本概念2、具體運算符及示例 六、位運算符1、基本概念2、具體運算符及示例 七、移位運算符…

【前端】1 小時實現 React 簡歷項目

近期更新完畢。僅包括核心代碼 目錄結構 yarn.lock保證開發者每次能下載到同版本依賴&#xff0c;一般不需要特別留意 package.json 是 Node.js 項目、前端項目、npm/yarn的配置文件。 Dockerfile 是用來 定義 Docker 鏡像構建過程的文本文件。它是一份腳本&#xff0c;告訴 …

python中的pydantic是什么?

Pydantic 是 Python 中一個用于數據驗證和設置管理的庫&#xff0c;主要通過 Python 類型注解&#xff08;Type Hints&#xff09;來定義數據結構&#xff0c;并自動驗證輸入數據的合法性。它廣泛應用于 API 開發&#xff08;如 FastAPI&#xff09;、配置管理、數據序列化等場…

騰訊云市場目前飽和度

首先我需要理解市場飽和度的概念。市場飽和度通常指一個產品或服務在潛在市場中的滲透程度&#xff0c;高飽和度意味著市場增長空間有限&#xff0c;低飽和度則表明還有較大發展潛力。 從搜索結果看&#xff0c;騰訊云目前在中國云服務市場排名第三&#xff0c;市場份額約為15%…

EDR、NDR、XDR工作原理和架構及區別

大家讀完覺得有幫助記得關注和點贊&#xff01;&#xff01;&#xff01; EDR、NDR、XDR是網絡安全中關鍵的檢測與響應技術&#xff0c;它們在覆蓋范圍、數據源和響應機制上有顯著差異。以下是它們的工作原理和架構詳解&#xff1a; --- ### &#x1f50d; 一、EDR&#xff0…

vue3 + luckysheet 實現在線編輯Excel

效果圖奉上&#xff1a; 引入的依賴&#xff1a; "dependencies": {"types/jquery": "^3.5.32","types/xlsx": "^0.0.36","jquery": "^3.7.1","xlsx": "^0.18.5",}在index.html中…

Linux下MinIO分布式安裝部署

文章目錄 一、MinIO簡單說明二、MinIO分布式安裝部署1、關閉SELINUX2、開啟防火墻2.1、關閉firewall&#xff1a;2.2、安裝iptables防火墻 3、安裝MinIO4、添加MinIO集群控制腳本4.1添加啟動腳本4.2添加關閉腳本 5、MinIO控制臺使用 一、MinIO簡單說明 1、MinIO是一個輕量的對…

Codeforces Round 980 (Div. 2)

ABC 略 D 這個過程一定是由1向后跳的過程中穿插有幾次向前一步一步走。直到跳到一個位置后再把前面所有沒有走過的位置倒序走一遍。總分就等于最大位置的前綴和-前面所有起跳位置和。前綴和固定我們只需要求到每個位置的最小起跳和即可。對于這個向后跳和向前走的過程我們可以…

Langchain實現rag功能

RAG&#xff08;檢索增強生成&#xff09;的核心是通過外部知識庫增強大模型回答的準確性和針對性&#xff0c;其工作流程與優化策略如下&#xff1a; 一、RAG 核心流程 ?知識庫構建? ?文檔加載與分割?&#xff1a;將非結構化文檔&#xff08;PDF、Markdown等&#xff09;…

算法筆記上機訓練實戰指南刷題

算法筆記上機訓練實戰指南刷題記錄 文章目錄 算法筆記上機訓練實戰指南刷題記錄模擬B1001 害死人不償命的(3n1)猜想B1011 AB 和 CB1016 部分ABB1026 程序運行時間B1046劃拳B1008數組元素循環右移問題B1012 數字分類B1018 錘子剪刀布A1042 Shuffling Machine 每天兩題&#xff0…

MYSQL基礎內容

一、介紹 1.不用數據庫&#xff1a;使用IO流對數據進行管理 2.使用數據庫&#xff1a;使用SQL語句對開發的數據進行管理&#xff0c;能儲存上億條數據 3.MYSQL&#xff1a; 是流行的關系型數據庫管理系統之一&#xff0c;將數據保存在不同的數據表中&#xff0c;通過表與表之…

音視頻會議服務搭建(設計方案)-01

前言 最近在做音視頻會議系統服務搭建的工作任務&#xff0c;因為內容過多&#xff0c;我會逐篇分享相關的設計方案、開發思路、編程語言、使用的組件集合等等。如果你也有大型音視頻會議系統搭建架構的需求&#xff0c;希望這些可以對你有所幫助。 EchoMeet 音視頻會議系統架構…

刷leetcode hot100/準備機試--圖

圖的基礎知識【這部分建議使用acm模式】 圖論理論基礎 | 代碼隨想錄 存儲&#xff1a; 一般有鄰接表【適合稀疏圖】【數組 鏈表 】和鄰接矩陣【適合稠密圖】存儲方式 注意鄰接表 和 鄰接矩陣的寫法都要掌握&#xff01; 鄰接矩陣 n個節點&#xff0c;申請n*n或者&#xf…

無代碼自動化測試工具介紹

無代碼自動化測試工具允許用戶無需編寫代碼即可創建和運行測試,通過拖拽式界面或錄制回放等可視化界面進行操作。 這些工具利用圖形用戶界面和預定義命令來創建測試,使非編程人員也能進行自動化測試。 無代碼自動化測試工具使團隊能夠: 使用直觀的拖拽界面開發和執行自動化…

python學習打卡day58

DAY 58 經典時序預測模型2 知識點回顧&#xff1a; 時序建模的流程時序任務經典單變量數據集ARIMA&#xff08;p&#xff0c;d&#xff0c;q&#xff09;模型實戰SARIMA摘要圖的理解處理不平穩的2種差分 n階差分---處理趨勢季節性差分---處理季節性 建立一個ARIMA模型&#xf…

分布式鎖的實現方式:使用 Redisson 實現分布式鎖( Spring Boot )

Redisson提供了分布式和可擴展的Java數據結構&#xff0c;包括分布式鎖的實現。 1. 添加依賴 在pom.xml中添加Redisson依賴&#xff1a; <dependency><groupId>org.redisson</groupId><artifactId>redisson-spring-boot-starter</artifactId>…

Web基礎關鍵_004_CSS(二)

目 錄 一、樣式 1.行內樣式 2.內部樣式 3.外部樣式 二、選擇器優先級 1.非關系選擇器 2.關系選擇器 三、屬性 四、盒子模型 五、元素 1.塊級元素 2.行內元素 3.行內塊級元素 4.元素類型轉換 六、浮動 七、定位 1.靜態定位 2.相對定位 3.絕對定位 4.固定定位 …

數據使用權與所有權分離:能否誕生“數據租賃”市場

——首席數據官高鵬律師數字經濟團隊創作&#xff0c;AI輔助 數據如礦藏&#xff0c;開采需“契約” 想象一座蘊藏著無盡資源的數字礦山&#xff1a;企業或個人擁有數據的“所有權”&#xff0c;如同手握礦脈的產權&#xff0c;但若無法高效挖掘其價值&#xff0c;礦石終將沉…