🔥 本文專欄:Linux
🌸作者主頁:努力努力再努力wz
💪 今日博客勵志語錄:
人生中成功只是一時的,失敗卻是人生的主旋律,但是如何面對失敗卻把人分成了不同的樣子,有的人會被失敗擊垮,有的人會爬起來繼續向前,所以你會選擇成為什么樣的人呢?
那么在此前的一系列的文章,我主要圍繞展開講解了Linux的進程板塊與文件系統板塊,學習完了這兩個板塊的知識點之后,那么這兩個板塊的內容其實并不是獨立隔離開的而是有聯系的,那么有了文件系統板塊的知識點之后,其實我們進程板塊的很多知識點就可以在進一步的完善,那么本篇文章的核心就是關聯其我們進程板塊與我們的文件板塊,那么廢話不多說,就進入正文的學習
★★★ 本文前置知識:
文件系統收尾
文件系統
緩沖區
進程的替換
進程地址空間
引入:內存管理
想必進程的創建想必大家一定非常熟悉,那么在進程創建之前,我們需要將該進程對應的可執行文件給加載到內存中,那么我們知道內存和外部設備磁盤一樣,是一個物理結構,那么必然也要經過操作系統的管理,那么操作系統管理一個事物的方式是首先得為該事物建立一個邏輯映射來描述該事物,比如磁盤,那么操作系統便用一個一維的線性數組來描述磁盤這個物理結構,那么其中該一維的線性數組的每一個元素便是扇區,同理操作系統要描述內存,必然也得為內存建立一個邏輯結構
那么我們知道CPU在運行進程的代碼的時候,那么會從內存中獲取該進程的相關數據,而CPU要獲取該進程的相關數據,必然得告訴內存目標數據在內存中的物理位置,從而內存能夠定位到該數據然后交給CPU,而CPU與內存之間通過地址線相連,那么以32為機器為例,那么CPU與內存的地址線就是32根,那么每一根地址線的高低電頻信號用來表示二進制的0和1,那么內存就能夠獲取到這32根地址線組合得到一個二進制序列,那么該二進制序列便是地址,那么32根地址線總共就能夠表示出2的32次方個不停的地址,也就是從0000 0000到FFFF FFFF連續的大小為4GB的地址空間,那么該地址空間就可以用一個一維的數組來表示,其中該數組的每一個元素就是一個邏輯地址,那么每一個進程都有各自的獨立的一份虛擬地址空間,那么該虛擬地址空間就是內存的一個邏輯映射
那么這些地址是邏輯上的地址,就如同我們磁盤上一維線性數組中的邏輯塊的LAB地址一樣,那么他們就需要經過轉化映射得到實際的物理內存地址,所以便有了頁表這個數據結構,那么其中頁表就記錄的是虛擬地址到內存的物理地址的一個映射,那么CPU拿著虛擬地址經過其MMU也就是CPU的內存管理單元,然后MMU通過頁表就能實現虛擬地址到物理地址的一個轉化,從而交給內存定位到目標數據
而對于物理內存來說,我們操作系統將連續的物理內存空間其中按照4KB的數據為一個集合來劃分,其中這每一個集合就是一個頁框,那么為了管理整個物理內存就轉換為了管理這劃分的一個一個的頁框,那么管理的方式就是我們最為熟悉的先描述,再組織了,也就是說操作系統會為每一個頁框定義一個page結構體,那么該結構體存儲了該頁框的各種屬性,其中最關鍵的便是一個整形的flag作為標志位,那么該整形flag變量的每一個二進制位都有特定的含義,那么其中就包括了該頁框是否被使用等,那么其中page結構體還包括一個變量count也就是引用計數,記錄該頁框被多少進程所共享,那么在進程的視角下,物理內存是4GB,而頁框的大小是4KB,那么也就是說總共大概會有1048576個頁框
struct page {unsigned long flags; // 標志位(如PG_locked、PG_dirty)atomic_t _count; // 引用計數(共享次數)atomic_t _mapcount; // 頁表映射計數struct address_space *mapping; // 關聯的地址空間(文件映射時使用)pgoff_t index; // 頁框在文件或內存中的偏移struct list_head lru; // LRU鏈表(用于頁面置換算法)// 其他字段(如內核slab分配器相關)};
那么為了管理這么多的頁框,那么操作系統的內核中會維護一個全局的變量mem_map本質上也就是一個struct page數組,那么該數組的每一個元素就對應一個page實例,那么我們可以通過內存的邏輯地址也就是地址空間的虛擬地址來根據其頁表來映射轉換得到內存的物理地址,而虛擬地址的二進制序列是由頁目錄索引、頁表索引、頁內偏移所構成。
以虛擬地址 0x08048000
為例: 0x08048000
→ 分解為:頁目錄索引(0x20)、頁表索引(0x48)、頁內偏移(0x000)。
然后MMU通過頁目錄索引以及頁表索引在頁表中逐級查找得到頁框號PFN再結合頁內偏移最后在將兩者轉化得到物理內存地址
而物理地址的前12位便是該物理地址所對應的頁框的page結構體在mem_map的一個偏移量也就是PFN,那么我們就可以根據偏移量來定位到其對應的page結構體,這樣就完成了一個虛擬地址到物理地址再到對應的page結構體的一個映射
虛擬地址空間 (進程視角)
│
└── 頁表 (Page Table)
│
└── 物理地址 (硬件層)
│
└── 物理頁框 (4KB)
│
└── struct page (元數據)
│
└── mem_map[PFN] (全局管理)
而我們發現如果是對于EXT4文件系統來說,那么該文件系統下的邏輯塊的大小是4KB,而頁框的大小也為4kb,那么必然我們的內存與磁盤的邏輯塊就能夠達成一個1:1的映射關系,那么至于映射規則以及過程是什么的,那么又是說來話長,本文肯定是講不完的,所以便不再闡述了,讀者感興趣可以下去自己了解
完善進程的創建的全過程
那么我們現在我們要結合我們之前所學的進程板塊與文件板塊來完善進程創建的一個完整的過程的話,那么這個過程的起點就應該從系統調用說起,
我們知道了我們在Linux上創建的各種進程,本質上都是我們命令行解釋器也就是shell外殼程序的子進程,那么shell外殼程序會調用fork系統調用接口來創建一個子進程,那么此時就會復制拷貝一份父進程的task_struct結構體然后修改其中部分屬性比如PID以及PPID等得到子進程自己獨立的一份task_struct結構體,至于數據層面上,那么子進程是共享父進程的物理內存頁面的,但是為了進程的獨立性,操作系統會采取了寫時拷貝機制,也就是一旦進程對共享的數據進行寫入操作的時候,那么便會觸發寫時拷貝機制,那么此時操作系統會為寫入的數據在內存中為其開辟一份副本從而做到父子進程的數據的獨立,那么fork調用結束后,下一個環節便是進程的替換,那么此時會調用exec族函數,那么它會獲取到替換的目標進程的對應的可執行文件的文件名以及路徑,那么沒錯,現在這部分過程就開始與文件板塊串聯起來了,獲取文件名以及路徑是目的是要從該文件所處的目錄文件中的目錄項中獲取對應的文件名和其inode編號的映射關系從而獲取該可執行文件的inode編號,那么有了該文件的inode編號之后,那么便可以轉化為磁盤中的物理內存地址從而進行定位,而inode塊中有其關聯的保存文件內容的數據塊的索引,那么意味著此時我們便能同時定位到該可執行文件的inode塊以及對應的數據塊,從而將其加載到內存中,然后接著又切換到我們的進程板塊
一旦該進程對應的可執行文件加載到內存中之后,那么那么進程的替換不會為替換的新的進程創建一個task_struct結構體,而是修改該進程的頁表也就是映射關系,從而將該子進程的上下文替換為目標進程的上下文,那么子進程運行結束退出后接著父進程會利用waitpid接口獲取子進程的退出情況也就是退出碼,那么這就是結合了文件板塊之后,我們完善我們對于進程的創建的全過程的一個理解
完善用戶緩沖區寫入
那么我們有了文件系統以及進程的概念之后,我們便能夠理解以及完善用戶緩沖區寫入的一個全過程
那么我們知道當我們調用c語言提供的fwrite庫函數向目標文件做寫入時,那么寫入的數據不會直接到內核中而是先保存在c語言提供的一個用戶層面上的緩沖區,然后再根據特定的刷新策略比如行緩沖或者全緩沖調用write接口刷新到內核中去
而我們向目標文件寫入的數據前提肯定是該目標文件被打開了,那么既然該文件被打開,那么必然要調用fopen函數或者open接口,那么open系統接口會獲取該打開的目標文件的文件名以及所處路徑,然后會根據該路徑解析得到該目標文件所處的目錄,然后掃描其目錄項得到映射關系從而獲取該文件的inode編號從而定位到磁盤中的相應位置將該文件的inode塊以及其關聯的數據塊加載到內存當中,并且為其創建一個內核層面上的file結構體,以及定義一個inode結構體保存inode塊的數據,那么file結構體內部就有inode結構體的間接引用,而進程內部會有一個指針數組記錄其打開的文件,那么每一個元素指向一個該進程打開的文件的file結構體,那么創建完file結構體之后,就會從數組開始線性掃描該指針數組尋找空余位置然后指向該file結構體并返回該數組位置的下標
那么此時有了數組下標之后,我們便能獲取該文件的file結構體,那么其中file結構體中有一個address space字段,其中保存了一個指向基數樹的數據結構的指針,那么我們該打開的文件一定保存在特定位置的內存的頁框中,所以得通過基數樹來尋找定位到其page結構體然后寫入到頁緩存中去,最后再由操作系統刷新到磁盤中去,這也就是為什么要有用戶緩沖區的意義,因為每次寫入頁緩沖,那么其在基數樹的定位都需要時間開銷
結語
那么這就是本篇文章的全部內容,那么大家在學習Linux的各個板塊的時候,比如進程與文件系統,它們之間不是獨立沒有聯系的,而是可以結合這兩個板塊的知識,去完善我們很多過程,加深這兩個板塊之間的理解,這就是本文想要傳達的,那么我的下一篇文章是動靜態庫,那么我會持續更新,希望你能多多關注,如果本文有幫組到你的話,還請多多三連加關注哦,你的支持就是我創作的最大動力!