欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Linux深入理解進程和文件及內(nèi)存管理問題

 更新時間:2025年03月20日 10:07:50   作者:s_little_monster_  
這篇文章主要介紹了Linux深入理解進程和文件及內(nèi)存管理問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

一、重談Linux下一切皆文件

我們說了一切皆文件,對于操作系統(tǒng)來說,磁盤鍵盤顯示屏等等一系列的外設都是文件。

舉一個訪問外設的例子:

進程運行,從進程PCB中找到指針指向文件管理結(jié)構體,然后在這個結(jié)構體中我們可以找到struct file*類型的指針指向一個個的文件管理結(jié)構體struct file,在這些結(jié)構體中都有著一個專門放讀寫函數(shù)的結(jié)構體,調(diào)用這些讀寫函數(shù)可以訪問到外設存放讀寫函數(shù)的結(jié)構體,而雖然每個外設的讀寫方式不同,但它們僅把處理好的代碼封裝后將接口漏出,方便上方函數(shù)的統(tǒng)一調(diào)用,這樣雖然每個外設不同,但是我們通過一種求同存異的方法,將它們統(tǒng)一協(xié)調(diào)調(diào)度起來

類似于鍵盤一類的只有讀或者顯示屏一類的只有寫的外設,我們也有讀或?qū)懙慕涌?,只是接口不做處理,方便統(tǒng)一

二、操作系統(tǒng)對物理內(nèi)存的管理

1、物理內(nèi)存與磁盤的數(shù)據(jù)交互

在操作系統(tǒng)的運行機制里,物理內(nèi)存和磁盤之間的數(shù)據(jù)交換起著關鍵作用,這種交換一般就是以頁page為單位,常見的頁大小為 4KB,在物理內(nèi)存中,一個 4KB 大小的空間被稱作頁框,而從磁盤加載到這個頁框里的4KB數(shù)據(jù)塊則被叫做頁

采用這種以頁為單位進行數(shù)據(jù)交換的方式,具有顯著的優(yōu)勢,一方面,能有效減少IO操作的次數(shù),進而提升系統(tǒng)效率,舉例來說,如果需要讀取數(shù)據(jù),一次讀取 4KB 與分四次讀取每次 1KB 相比,前者的效率要高得多,對于硬盤來說,一次讀取 4KB 時,CPU 只需與磁盤進行一次交互,而分四次讀取 1KB 時,CPU 要與磁盤進行四次交互,且這四次操作很可能不連續(xù),這就意味著效率低下,另一方面,這種方式還遵循基于局部性原理的預加載機制,即便當前 CPU 僅需訪問 100 字節(jié)的內(nèi)容,操作系統(tǒng)和磁盤之間依舊會以 4KB 為單位將數(shù)據(jù)加載進來,這是因為根據(jù)經(jīng)驗,CPU 在訪問當前磁盤中的代碼和數(shù)據(jù)時,后續(xù)有較大概率會訪問附近空間的代碼和數(shù)據(jù),還有一方面,就是對齊,磁盤中的最小寫入單位是頁,因為計算機硬件的設計往往遵循一定的對齊規(guī)則,這樣可以提高數(shù)據(jù)訪問的效率內(nèi)存和磁盤控制器在設計時,通常會按照特定的字節(jié)邊界來組織和傳輸數(shù)據(jù),以頁為單位進行數(shù)據(jù)交換可以保證數(shù)據(jù)在內(nèi)存和磁盤之間的傳輸是按照硬件對齊要求進行的,減少硬件處理的復雜性

2、操作系統(tǒng)對物理內(nèi)存的管理

操作系統(tǒng)具備感知物理內(nèi)存的能力,其對物理內(nèi)存的管理遵循先描述再組織的原則,在內(nèi)核中,struct page 結(jié)構體承擔著描述物理內(nèi)存的重要職責,一個 struct page 對象對應著一個 4KB 的內(nèi)存頁框,該結(jié)構體中記錄了當前頁框的諸多屬性信息,像頁框的狀態(tài)、引用計數(shù)等

操作系統(tǒng)會把物理內(nèi)存劃分成一個個的struct page對象,再用數(shù)組的形式將它們組織起來,數(shù)組的下標即為對應的頁號,若要確定一個物理地址所在的頁號,只需將該物理地址除以 4096 b(4KB = 4 * 1024 = 4096 b),或者將該地址按位與上 0xFFFFF000(以 32 位系統(tǒng)為例),把低 12 位清零,得到的結(jié)果就是該地址所在的頁號

在進行內(nèi)存申請操作時,系統(tǒng)會訪問 page 數(shù)組,查看 struct page 里的 flags 屬性,通過這個屬性,系統(tǒng)能夠判斷當前頁框的狀態(tài),確定其是否已被使用,若未被使用,系統(tǒng)就會修改 flags 以表明該頁框已被申請,此外,flags 除了能表示頁框的使用狀態(tài)外,還能指示該頁框是只讀還是可讀寫等狀態(tài),

當然這里所介紹的只是操作系統(tǒng)對物理內(nèi)存管理的一個基礎模型,實際上真正的內(nèi)存管理系統(tǒng)要復雜得多

三、文件頁緩沖區(qū)

在操作系統(tǒng)內(nèi)核中,struct file 是一個重要的數(shù)據(jù)結(jié)構,用于描述一個已打開的文件,而 inode 這一概念是在介紹磁盤時引入的,磁盤上的每個文件都對應著一個inode,它存儲了該文件的屬性信息,struct fileinode 之間存在著緊密的聯(lián)系,struct file中僅記錄了文件的少量屬性,而struct inode結(jié)構體則專門用于記錄一個文件的所有屬性,在 struct file 中有一個指針字段,它指向該文件的struct inode對象

文件由內(nèi)容和屬性兩部分構成,在磁盤上,文件的屬性由 inode存儲,文件的內(nèi)容則由數(shù)據(jù)塊存儲,那么,在操作系統(tǒng)內(nèi)核中,文件的內(nèi)容(數(shù)據(jù))是如何表示的呢?答案就是通過文件頁緩沖區(qū),在 struct file 結(jié)構中有一個指向 struct address_space 結(jié)構體的指針,在 struct address_space 結(jié)構體中,有一個 struct radix_tree_root 結(jié)構體對象,它實際上是一種樹狀結(jié)構,即基數(shù)樹(也叫字典樹),樹中的每個節(jié)點都是 struct radix_tree_node 類型,該類型中有一個名為 slotsvoid* 類型數(shù)組,數(shù)組中存儲的其實就是 struct page 對象的地址,簡單來說,在 struct file 結(jié)構體中有指向物理內(nèi)存頁框的指針,我們把這些物理內(nèi)存區(qū)域稱為文件頁緩沖區(qū)

向文件寫入數(shù)據(jù)的過程

當使用C/C++庫函數(shù)向文件中寫入數(shù)據(jù)時,整個過程分為幾個階段,首先,數(shù)據(jù)有可能會被寫入到語言層面的用戶緩沖區(qū),然后,在合適的時機,這些數(shù)據(jù)會被從用戶緩沖區(qū)寫入到該文件對應的文件頁緩沖區(qū)中,最后,還是在合適的時機,數(shù)據(jù)會從文件頁緩沖區(qū)被寫入到磁盤

將物理內(nèi)存中的數(shù)據(jù)刷新到磁盤這一操作由IO子系統(tǒng)負責執(zhí)行,進程通常無需關注具體的執(zhí)行過程,在操作系統(tǒng)中,會存在大量的IO操作,可能有很多進程都需要將數(shù)據(jù)寫入磁盤,為了有效管理這些操作,操作系統(tǒng)會按照先描述再組織的方式對所有的IO操作進行管理,內(nèi)核中的struct request結(jié)構就是專門用來描述一個IO操作的

在Linux操作系統(tǒng)中,每個進程打開的每個文件都有自己的 struct inode 對象和對應的文件頁緩沖區(qū),也就是所謂的內(nèi)核緩沖區(qū),它們共同保障了文件操作的高效和穩(wěn)定

四、動態(tài)庫是如何被加載的

動態(tài)庫在進程運行時要被加載到內(nèi)存,一般我們常用的動態(tài)庫是要被所有的可執(zhí)行程序動態(tài)鏈接的,所以動態(tài)庫在系統(tǒng)中加載完成后,會被所有的進程所共享

在操作系統(tǒng)的進程管理與庫使用機制中,進程與動態(tài)庫的交互有著獨特的方式,一個進程在運行過程中,是可以同時鏈接多個動態(tài)庫的,不過,當系統(tǒng)中存在多個進程時,不能簡單地認為系統(tǒng)中必然存在多個不同的動態(tài)庫,多個進程可能會依賴相同的動態(tài)庫,操作系統(tǒng)對動態(tài)庫采用“先描述,再組織”的策略進行管理,它會為每個動態(tài)庫創(chuàng)建相應的數(shù)據(jù)結(jié)構來描述其屬性、位置等信息,然后將這些描述信息組織起來,以便高效地進行查找、加載和管理,憑借這種管理方式,操作系統(tǒng)對系統(tǒng)中所有動態(tài)庫的加載狀態(tài)了如指掌

a.exe 為例,它在編譯鏈接階段選擇使用動態(tài)庫,當 a.exe 運行成為 a 進程后,CPU 會按照程序的指令順序依次執(zhí)行代碼,假設在執(zhí)行過程中遇到了一個庫函數(shù)調(diào)用,此時,操作系統(tǒng)會檢查該函數(shù)所在的動態(tài)庫是否已經(jīng)被加載到內(nèi)存中,若尚未加載,操作系統(tǒng)會負責將該動態(tài)庫加載到內(nèi)存,這一加載過程本質(zhì)上與文件加載一致,因為動態(tài)庫本身也是以文件形式存在的,并且具有 inode 來標識其在文件系統(tǒng)中的元數(shù)據(jù)

動態(tài)庫加載完成后,操作系統(tǒng)會在 a 進程的頁表中建立該動態(tài)庫與 a 進程地址空間中共享區(qū)的映射關系,這樣,當 CPU 需要執(zhí)行上述函數(shù)時,就可以從代碼段跳轉(zhuǎn)到共享區(qū)去執(zhí)行動態(tài)庫中該函數(shù)的代碼,執(zhí)行完畢后,CPU 會跳轉(zhuǎn)回代碼段,繼續(xù)執(zhí)行后續(xù)的程序指令

b.exe 同樣在編譯鏈接時采用動態(tài)庫,之后被加載到內(nèi)存成為 b 進程,當 CPU 執(zhí)行 b 進程的代碼并遇到上面函數(shù)調(diào)用時,因為在 a 進程已經(jīng)將該所在的動態(tài)庫加載到了內(nèi)存,操作系統(tǒng)不會再次重復加載該動態(tài)庫,而是直接在 b 進程的頁表中建立該動態(tài)庫與 b 進程共享區(qū)的映射關系,通過這種方式,同一個動態(tài)庫可以被多個進程共享使用,所以動態(tài)庫又被稱為共享庫

關于動態(tài)庫中的全局變量

動態(tài)庫確實可以被多個進程共享,但對于動態(tài)庫中的全局變量(例如 errno),需要特殊的處理機制來保證各個進程之間的數(shù)據(jù)獨立性,errno 是 C 語言標準庫提供的一個全局變量,用于存儲最近一次庫函數(shù)調(diào)用失敗時的錯誤碼

如果簡單地讓所有進程共享 errno,會引發(fā)嚴重的問題,例如,當 a 進程調(diào)用庫函數(shù)失敗,errno 被設置為 1,此時如果 b 進程也使用這個共享的 errno,就會導致 b 進程錯誤地獲取到 a 進程的錯誤碼,這顯然不符合邏輯

實際上,操作系統(tǒng)采用了寫時拷貝技術來解決這個問題,當某個進程要修改 errno 時,操作系統(tǒng)會通過引用計數(shù)來判斷該動態(tài)庫是否被多個進程共享,如果該動態(tài)庫被多個進程共享,操作系統(tǒng)會為該進程復制一份動態(tài)庫中相關數(shù)據(jù)(包括 errno)的副本,而不是直接修改共享的數(shù)據(jù),這樣,每個進程都有自己獨立的 errno 副本,從而保證了各個進程之間的錯誤碼不會相互干擾,只有當進程對數(shù)據(jù)進行寫操作時才會發(fā)生拷貝,而在只讀的情況下,多個進程仍然可以共享同一份動態(tài)庫數(shù)據(jù),從而充分發(fā)揮了動態(tài)庫共享的優(yōu)勢

五、深入理解地址

1、程序地址

在一個程序編譯好后形成了可執(zhí)行文件,在它的內(nèi)部是有地址的概念的,這里程序內(nèi)部的地址我們稱為邏輯地址,我們計算機一般采用的是平坦模式編址,平坦模式編址是一種簡化的內(nèi)存編址模型,在這種模式下,整個內(nèi)存空間被視為一個連續(xù)的、線性的地址空間,程序可以直接訪問這個連續(xù)地址空間內(nèi)的任意內(nèi)存位置,而不需要像分段模式那樣進行復雜的段地址和偏移地址組合計算,在平坦模式中,內(nèi)存地址是一個單一的、連續(xù)的數(shù)值,從 0 開始一直到系統(tǒng)所支持的最大內(nèi)存地址

32位下的4GB內(nèi)存地址

2、進程地址

我們知道,可執(zhí)行程序內(nèi)部采用的是邏輯地址(也叫虛擬地址)進行編址,物理內(nèi)存本身有其固定的物理地址,無論可執(zhí)行程序是否加載,物理內(nèi)存的地址體系是一直存在的,當可執(zhí)行程序被加載到內(nèi)存后,程序中的每一條指令和數(shù)據(jù)都會對應一個物理地址,這是通過地址映射機制實現(xiàn)的

那么,CPU 是如何知道可執(zhí)行程序的第一條指令位置呢?在編譯生成可執(zhí)行程序時,除了生成代碼段、數(shù)據(jù)段等程序內(nèi)容外,還會生成一個文件頭,這個文件頭包含了諸多重要信息,其中就有可執(zhí)行程序的入口地址,此地址是邏輯地址(虛擬地址)

在 CPU 中有一個關鍵的寄存器,即程序計數(shù)器—PC 寄存器,它存儲著接下來要執(zhí)行指令的地址,實際上,在程序啟動階段,不會立刻把整個可執(zhí)行程序加載到內(nèi)存(可以想象我們打游戲的時候不是打開游戲就能玩的,需要等待加載)而是先將可執(zhí)行文件的頭部加載進來,操作系統(tǒng)讀取文件頭,從中獲取可執(zhí)行程序的入口地址,并將該地址設置到 PC 寄存器中

CPU 拿到這個虛擬地址后,會借助內(nèi)存管理單元去查詢頁表,頁表記錄了虛擬地址和物理地址的映射關系,若查詢發(fā)現(xiàn)該虛擬地址對應的頁表項無效,也就是此頁面尚未建立內(nèi)存映射,操作系統(tǒng)會觸發(fā)缺頁中斷,缺頁中斷發(fā)生后,操作系統(tǒng)暫停當前程序的執(zhí)行,從磁盤把對應的程序頁面加載到物理內(nèi)存的空閑頁框中,同時更新頁表,建立起虛擬地址到物理地址的映射,之后,恢復程序執(zhí)行,CPU 就能訪問到物理內(nèi)存中對應的指令了

CPU 憑借其內(nèi)置的指令集,能夠明確識別每條指令的長度,在正常運行狀態(tài)下,CPU 按照 PC 寄存器所存儲的地址順序執(zhí)行指令,每執(zhí)行完一條指令,PC 寄存器會自動更新為下一條指令的地址,當程序執(zhí)行過程中遇到函數(shù)調(diào)用指令或跳轉(zhuǎn)指令時,PC 寄存器的值會被修改為新的虛擬地址,CPU 會依據(jù)這個新的虛擬地址再次查詢頁表,若發(fā)現(xiàn)頁面未在內(nèi)存中,將再次觸發(fā)缺頁中斷

由此可見,CPU 正是通過將虛擬地址轉(zhuǎn)換為物理地址的方式,來執(zhí)行可執(zhí)行程序中的指令以及訪問可執(zhí)行程序中的變量的,這種基于虛擬內(nèi)存和地址映射的機制,為程序提供了獨立的地址空間,增強了內(nèi)存管理的靈活性和安全性

3、動態(tài)庫地址

在計算機系統(tǒng)的程序執(zhí)行機制中,可執(zhí)行程序內(nèi)部采用邏輯地址進行編址,CPU通過將虛擬地址轉(zhuǎn)換為物理地址的方式來執(zhí)行指令

靜態(tài)庫在編譯鏈接過程中會被整合到可執(zhí)行文件中,當可執(zhí)行程序啟動運行時,操作系統(tǒng)會將包含靜態(tài)庫代碼的可執(zhí)行文件加載到物理內(nèi)存,之后 CPU 方可執(zhí)行其中的指令,靜態(tài)庫中的函數(shù)采用絕對編址,這是因為它們已成為可執(zhí)行文件的組成部分,其地址在編譯鏈接階段就已經(jīng)確定下來

在可執(zhí)行程序的編譯階段,對動態(tài)庫內(nèi)函數(shù)的引用表現(xiàn)為未解析的符號,當程序進入運行狀態(tài)時,動態(tài)鏈接器承擔起解析這些符號的任務,由于動態(tài)庫在運行時能夠被加載到虛擬內(nèi)存共享區(qū)的任意位置,要將動態(tài)庫加載到固定位置存在較大困難,這是由于一個可執(zhí)行程序往往會同時使用多個動態(tài)庫,各個動態(tài)庫的大小不盡相同,并且每個動態(tài)庫都采用獨立的編址方式,不同動態(tài)庫中可能出現(xiàn)相同的編址情況,為了實現(xiàn)動態(tài)庫在虛擬內(nèi)存共享區(qū)任意位置的加載,動態(tài)庫內(nèi)部采用相對編址的方法,對于動態(tài)庫中的函數(shù),僅需明確其在庫中的偏移量即可鑒于庫中函數(shù)的偏移量是已知的(在編譯動態(tài)庫時,編譯器會按照一定的規(guī)則對庫中的代碼和數(shù)據(jù)進行布局,編譯器知道每個函數(shù)在代碼段中的起始位置以及函數(shù)內(nèi)部代碼的長度),動態(tài)庫便能夠被加載到虛擬內(nèi)存共享區(qū)的任意位置

在此機制下,操作系統(tǒng)只需記錄每個動態(tài)庫在虛擬內(nèi)存中的起始地址,當需要執(zhí)行某個動態(tài)庫函數(shù)時,通過將該函數(shù)所在動態(tài)庫的起始地址與該函數(shù)的偏移量相加,即可得到該函數(shù)在程序地址空間中的虛擬地址,隨后,依據(jù)此虛擬地址查詢頁表,找到該函數(shù)在物理內(nèi)存中的對應地址,進而執(zhí)行該庫函數(shù),GCC 編譯器中的 -fPIC 選項,其作用就是讓編譯器在生成動態(tài)庫文件時,直接使用偏移量對庫中的函數(shù)進行編址,從而實現(xiàn)代碼的位置無關性

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • linux系列之常用運維命令整理筆錄(小結(jié))

    linux系列之常用運維命令整理筆錄(小結(jié))

    這篇文章主要介紹了linux系列之常用運維命令整理筆錄(小結(jié)),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-01-01
  • Kloxo-MR VPS主機控制面板-安裝使用及中文設置方法

    Kloxo-MR VPS主機控制面板-安裝使用及中文設置方法

    這篇文章主要介紹了Kloxo-MR VPS主機控制面板-安裝使用及中文設置方法,需要的朋友可以參考下
    2017-07-07
  • 如何使用shell在多服務器上批量操作

    如何使用shell在多服務器上批量操作

    日常工作中,我們常需要同時在多臺服務器上執(zhí)行同樣的命令,如對比日志、檢查服務等。這就需要我們有服務器批量操作的能力,我們可以借用 ssh公鑰登陸的能力,方便地實現(xiàn)在多個服務器上批量執(zhí)行命令。
    2021-05-05
  • Centos rsync文件同步配置步驟分享

    Centos rsync文件同步配置步驟分享

    rsync是類unix系統(tǒng)下的數(shù)據(jù)鏡像備份工具,從軟件的命名上就可以看出來了——remote sync
    2012-09-09
  • 簡單談談Linux內(nèi)核定時器

    簡單談談Linux內(nèi)核定時器

    內(nèi)核定時器用于控制某個函數(shù)(定時器處理函數(shù))在未來的某個特定時間執(zhí)行.內(nèi)核定時器注冊的處理函數(shù)只執(zhí)行一次.處理過后即失效.
    2017-10-10
  • nmap掃描服務器端口(遠程桌面端口)

    nmap掃描服務器端口(遠程桌面端口)

    nmap是Linux下常用的端口掃描工具,它可以檢測主機是否在線,是否開啟了某個服務端口,使用了何種操作系統(tǒng)等,下面是安裝方法和使用方法
    2013-12-12
  • linux下open-vswitch安裝卸載操作

    linux下open-vswitch安裝卸載操作

    這篇文章主要為大家詳細介紹了linux下open-vswitch安裝卸載的相關代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • CentOS 6.2 下升級安裝為MySQL 5.5的方法

    CentOS 6.2 下升級安裝為MySQL 5.5的方法

    使用系統(tǒng)CentOS 6.2本來已經(jīng)系統(tǒng)自帶安裝了mysql 5.1,但是奈何5.1不支持utf8mb4字符集,只能想辦法將Mysql升級到5.5
    2014-11-11
  • Linux、CentOS下安裝zip與unzip指令功能(服務器)

    Linux、CentOS下安裝zip與unzip指令功能(服務器)

    這篇文章主要介紹了Linux、CentOS下安裝zip與unzip指令的操作方法,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友參考下吧
    2019-11-11
  • Linux在兩個服務器直接傳文件的操作方法

    Linux在兩個服務器直接傳文件的操作方法

    scp是?secure?copy?的簡寫,?是?linux?系統(tǒng)下基于?ssh?登陸進行安全的遠程文件拷貝命令,這篇文章主要介紹了Linux在兩個服務器直接傳文件的操作方法,需要的朋友可以參考下
    2022-08-08

最新評論