操作系統實現:記憶體管理

来源:https://www.cnblogs.com/thotf/archive/2022/06/07/16353031.html
-Advertisement-
Play Games

本文參考書:操作系統真像還原、電腦組成原理(微課版) 所謂記憶體管理包含: 物理記憶體 虛擬地址空間 以上就是記憶體管理中所要管理的資源。那麼記憶體管理的第一步就應該是整理出這兩種資源。 物理記憶體要分為兩部分: ①內核記憶體 ②用戶記憶體 在內核態下也經常會有一些記憶體申請,比如申請個pcb、頁表等等。內核態和 ...


本文參考書:操作系統真像還原、電腦組成原理(微課版)

 

所謂記憶體管理包含:

  物理記憶體

  虛擬地址空間

 

以上就是記憶體管理中所要管理的資源。那麼記憶體管理的第一步就應該是整理出這兩種資源。

 

物理記憶體要分為兩部分:

  ①內核記憶體

  ②用戶記憶體

 

在內核態下也經常會有一些記憶體申請,比如申請個pcb、頁表等等。內核態和用戶態所試用的記憶體要分隔開,用戶態不能直接訪問內核態的記憶體。

操作系統在進入保護模式前會通過

int 15h ax = E801h 獲取記憶體大小,最大支持4G

或者其他軟中斷獲取電腦記憶體大小 mm_bytes,並將mm_bytes放在一個記憶體地址我們這裡是放在 物理地址0xb00。

 

這裡可能有個疑問,我們將mm_bytes 放在0xb00處,進入保護模式後開啟了虛擬地址,那怎麼知道0xb00的虛擬地址是什麼?

答:在操作系統建立頁表時,低地址物理記憶體和虛擬地址設計為1對1映射,既 0xb00物理地址== 0xb00虛擬地址。

在本文章中操作系統實現中低1M記憶體是1對1映射的。

 

有了物理記憶體的大小,那下一步就應該建立結構以便對記憶體進行管理。

struct pool {
   struct bitmap pool_bitmap;     // 本記憶體池用到的點陣圖結構,用於管理物理記憶體
   uint32_t phy_addr_start;     // 本記憶體池所管理物理記憶體的起始地址
   uint32_t pool_size;         // 本記憶體池位元組容量
   struct lock lock;         // 申請記憶體時互斥
};

物理記憶體也需要點陣圖進行管理 bitmap 實現之前說過了,用戶點陣圖和內核點陣圖可以放在1M內找幾個連續頁存放。看個人設計。

物理記憶體起始地址 phy_addr_start 是需要計算的,我們要知道低段記憶體部分哪些空間是被占用了。

在本文章中內核放在低1M處,1M開始處又放了256個頁(頁表實現文章)。所以

phy_addr_start = 0x100000 + PG_SIZE * 256

 

pool_size    內核位元組池的容量,這裡分配多少按操作系統需求來設計,本文章直接將可用記憶體容量的一半分給內核。

free_mm = mm_bytes - phy_addr_start   //可用記憶體

 

可用記憶體就這些但是可以直接 ÷ 2 分配給內核嗎?

答:是不行的,我們知道記憶體只要通過點陣圖管理的,每個點陣圖指向4K大小的頁,所以我們要先計算出可用記憶體有多少頁,按總頁數一半的來給pool_size

 

all_free_pages = free_mm / PG_SIZE;

kernel_free_pages = all_free_pages / 2;

pool_size = kernel_free_pages * PG_SIZE;

 

類似地用戶記憶體池做一下減法就好了。

 

物理記憶體頁申請

 * 成功則返回頁框的物理地址,失敗則返回NULL */
static void* palloc(struct pool* m_pool) {
   /* 掃描或設置點陣圖要保證原子操作 */
   int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1);    // 找一個物理頁面
   if (bit_idx == -1 ) {
      return NULL;
   }
   bitmap_set(&m_pool->pool_bitmap, bit_idx, 1);    // 將此位bit_idx置1
   uint32_t page_phyaddr = ((bit_idx * PG_SIZE) + m_pool->phy_addr_start);
   return (void*)page_phyaddr;
}

 

bit_idx是返回的是頁起始下標, bit_idx * PG_SIZE是偏移地址 + 基地址 就是物理頁位置。

 

 

虛擬記憶體管理主要是處理的是虛擬地址和物理地址映射問題。

虛擬地址管理分為:

  內核多級頁表

  每個進程一個多級頁表

 

 

其中進程的多級頁表,是在進程創建時申請的記憶體頁建立的這裡只說內核頁表,進程的是類似的只是多了個申請頁的過程。

內核頁表是在進入保護模式前建好了,在之前的文章里寫過。

 

虛擬地址所使用的結構如下:

/* 用於虛擬地址管理 */
struct virtual_addr {
/* 虛擬地址用到的點陣圖結構,用於記錄哪些虛擬地址被占用了。以頁為單位。*/
   struct bitmap vaddr_bitmap;
/* 管理的虛擬地址 */
   uint32_t vaddr_start;
};

 

內核二級頁表

虛擬地址低1M的地址空間和物理地址是1-1映射關係。既0號頁目錄項指向第0個頁表,第0個頁表的0-254個頁表項指向物理地址 0-1M。 

在32位系統中,所有頁表的虛擬地址 3G以上空間都是內核虛擬地址對應關係,3G空間的起始部位為頁目錄項第768項。

所以我們只需要一直更新內核頁目錄第768項到1023項所指向的頁表,就可以保證其他進程頁表的內核虛擬地址空間同步變化。

內核頁目錄的第768-1023項也是指向 0-255 號頁表。這是在未開啟保護模式前就初始化寫好的。(詳細看頁表實現文章)

至於低1M的1-1映射,主要為了方便操作。

 

所以內核虛擬地址結構的 vaddr_start 應該初始化為0xc0100000 ,這裡就是768項跳過了低1M空間的虛擬地址。

 

 

如何建立物理地址與虛擬地址的映射關係?

先申請一個物理地址,再申請一個虛擬地址,拿到了2個地址後,我們要在虛擬地址對應的頁表項中填寫對應的物理地址(高24位)。

如何拿到虛擬地址對應的頁表項?

首先要找到頁目錄表,頁目錄項(虛擬地址高10位)存在則拿到頁表,要判斷頁表項(虛擬地址中間10位)未使用後填入物理地址(高24位)。

          頁目錄項不存在還要創建新頁表,再填入頁目錄項後再進行後續操作。

 

如何拿到頁表項物理地址?

 

#define PTE_IDX(addr) ((addr & 0x003ff000) >> 12)
uint32_t* pte_ptr(uint32_t vaddr) {
   /* 先訪問到頁表自己 + \
    * 再用頁目錄項pde(頁目錄內頁表的索引)做為pte的索引訪問到頁表 + \
    * 再用pte的索引做為頁內偏移*/
   uint32_t* pte = (uint32_t*)(0xffc00000 + \
     ((vaddr & 0xffc00000) >> 10) + \
     PTE_IDX(vaddr) * 4);
   return pte;
}

0xffc00000  代表選擇最後一個頁目錄項,前10位都是1,在頁表實現中,我們已經,提前設置好最後一個頁目錄項存放的是頁目錄物理基地址所以虛擬地址0xffc00000的含義是將頁目錄表當成頁表,返回的是當前頁目錄表的物理地址

(vaddr & 0xffc00000) >> 10是將vaddr虛擬地址的前10位移動到中間10位,和0xffc00000相加在這裡也可以理解為將頁目錄表當成頁表在頁目錄表中選擇頁目錄項,返回的是我們要的頁表物理地址。

PTE_IDX(vaddr) * 4  代表選擇頁表中的頁表項,也就是返回pte地址

 

如何拿到頁目錄項物理地址?

#define PDE_IDX(addr) ((addr & 0xffc00000) >> 22)

uint32_t* pde_ptr(uint32_t vaddr) { /* 0xfffff是用來訪問到頁表本身所在的地址 */ uint32_t* pde = (uint32_t*)((0xfffff000) + PDE_IDX(vaddr) * 4); return pde; }

 

提前設置好最後一個頁目錄項存放的是頁目錄物理基地址, 0xfffff000 前20位是 1111111111 1111111111 ,代表頁目錄表基地址

 

 

建立一一映射關係:


static void page_table_add(void* _vaddr, void* _page_phyaddr) {
uint32_t vaddr = (uint32_t)_vaddr, page_phyaddr = (uint32_t)_page_phyaddr;
uint32_t* pde = pde_ptr(vaddr);
uint32_t* pte = pte_ptr(vaddr);

/************************ 註意 *************************
* 執行*pte,會訪問到pde。所以確保pde創建完成後才能執行*pte,
* 否則會引發page_fault。因此在pde未創建時,
* *pte只能出現在下麵最外層else語句塊中的*pde後面。
* *********************************************************/
/* 先在頁目錄內判斷目錄項的P位,若為1,則表示該表已存在 */
if (*pde & 0x00000001) {
ASSERT(!(*pte & 0x00000001));

if (!(*pte & 0x00000001)) { // 只要是創建頁表,pte就應該不存在,多判斷一下放心
*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // US=1,RW=1,P=1
} else { // 調試模式下不會執行到此,上面的ASSERT會先執行.關閉調試時下麵的PANIC會起作用
PANIC("pte repeat");
}
} else { // 頁目錄項不存在,所以要先創建頁目錄項再創建頁表項.
/* 頁表中用到的頁框一律從內核空間分配 */
uint32_t pde_phyaddr = (uint32_t)palloc(&kernel_pool);
*pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1);

/******************* 必須將頁表所在的頁清0 *********************
* 必須把分配到的物理頁地址pde_phyaddr對應的物理記憶體清0,
* 避免裡面的陳舊數據變成了頁表中的頁表項,從而讓頁表混亂.
* pte的高20位會映射到pde所指向的頁表的物理起始地址.*/
memset((void*)((int)pte & 0xfffff000), 0, PG_SIZE);
/************************************************************/
ASSERT(!(*pte & 0x00000001));
*pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // US=1,RW=1,P=1
}
}
 

 

 

 

 

記憶體回收,將分配的頁表項的P位取消。

      內核或者用戶物理地址bitmap改為0

      虛擬地址bitmap 設為0

/* 將物理地址pg_phy_addr回收到物理記憶體池 */
void pfree(uint32_t pg_phy_addr) {
   struct pool* mem_pool;
   uint32_t bit_idx = 0;
   if (pg_phy_addr >= user_pool.phy_addr_start) {     // 用戶物理記憶體池
      mem_pool = &user_pool;
      bit_idx = (pg_phy_addr - user_pool.phy_addr_start) / PG_SIZE;
   } else {      // 內核物理記憶體池
      mem_pool = &kernel_pool;
      bit_idx = (pg_phy_addr - kernel_pool.phy_addr_start) / PG_SIZE;
   }
   bitmap_set(&mem_pool->pool_bitmap, bit_idx, 0);     // 將點陣圖中該位清0
}

/* 去掉頁表中虛擬地址vaddr的映射,只去掉vaddr對應的pte */
static void page_table_pte_remove(uint32_t vaddr) {
   uint32_t* pte = pte_ptr(vaddr);
   *pte &= ~PG_P_1;    // 將頁表項pte的P位置0
   asm volatile ("invlpg %0"::"m" (vaddr):"memory");    //更新tlb
}

/* 在虛擬地址池中釋放以_vaddr起始的連續pg_cnt個虛擬頁地址 */
static void vaddr_remove(enum pool_flags pf, void* _vaddr, uint32_t pg_cnt) {
   uint32_t bit_idx_start = 0, vaddr = (uint32_t)_vaddr, cnt = 0;

   if (pf == PF_KERNEL) {  // 內核虛擬記憶體池
      bit_idx_start = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
      while(cnt < pg_cnt) {
     bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 0);
      }
   } else {  // 用戶虛擬記憶體池
      struct task_struct* cur_thread = running_thread();
      bit_idx_start = (vaddr - cur_thread->userprog_vaddr.vaddr_start) / PG_SIZE;
      while(cnt < pg_cnt) {
     bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 0);
      }
   }
}

/* 釋放以虛擬地址vaddr為起始的cnt個物理頁框 */
void mfree_page(enum pool_flags pf, void* _vaddr, uint32_t pg_cnt) {
   uint32_t pg_phy_addr;
   uint32_t vaddr = (int32_t)_vaddr, page_cnt = 0;
   ASSERT(pg_cnt >=1 && vaddr % PG_SIZE == 0); 
   pg_phy_addr = addr_v2p(vaddr);  // 獲取虛擬地址vaddr對應的物理地址

/* 確保待釋放的物理記憶體在低端1M+1k大小的頁目錄+1k大小的頁表地址範圍外 */
   ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= 0x102000);
   
/* 判斷pg_phy_addr屬於用戶物理記憶體池還是內核物理記憶體池 */
   if (pg_phy_addr >= user_pool.phy_addr_start) {   // 位於user_pool記憶體池
      vaddr -= PG_SIZE;
      while (page_cnt < pg_cnt) {
     vaddr += PG_SIZE;
     pg_phy_addr = addr_v2p(vaddr);

     /* 確保物理地址屬於用戶物理記憶體池 */
     ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= user_pool.phy_addr_start);

     /* 先將對應的物理頁框歸還到記憶體池 */
     pfree(pg_phy_addr);

         /* 再從頁表中清除此虛擬地址所在的頁表項pte */
     page_table_pte_remove(vaddr);

     page_cnt++;
      }
   /* 清空虛擬地址的點陣圖中的相應位 */
      vaddr_remove(pf, _vaddr, pg_cnt);

   } else {         // 位於kernel_pool記憶體池
      vaddr -= PG_SIZE;          
      while (page_cnt < pg_cnt) {
     vaddr += PG_SIZE;
     pg_phy_addr = addr_v2p(vaddr);
      /* 確保待釋放的物理記憶體只屬於內核物理記憶體池 */
     ASSERT((pg_phy_addr % PG_SIZE) == 0 && \
           pg_phy_addr >= kernel_pool.phy_addr_start && \
           pg_phy_addr < user_pool.phy_addr_start);
    
     /* 先將對應的物理頁框歸還到記憶體池 */
     pfree(pg_phy_addr);

         /* 再從頁表中清除此虛擬地址所在的頁表項pte */
     page_table_pte_remove(vaddr);

     page_cnt++;
      }
   /* 清空虛擬地址的點陣圖中的相應位 */
      vaddr_remove(pf, _vaddr, pg_cnt);
   }
}

/* 回收記憶體ptr */
void sys_free(void* ptr) {
   ASSERT(ptr != NULL);
   if (ptr != NULL) {
      enum pool_flags PF;
      struct pool* mem_pool;

   /* 判斷是線程還是進程 */
      if (running_thread()->pgdir == NULL) {
     ASSERT((uint32_t)ptr >= K_HEAP_START);
     PF = PF_KERNEL; 
     mem_pool = &kernel_pool;
      } else {
     PF = PF_USER;
     mem_pool = &user_pool;
      }

      lock_acquire(&mem_pool->lock);   
      struct mem_block* b = ptr;
      struct arena* a = block2arena(b);         // 把mem_block轉換成arena,獲取元信息
      ASSERT(a->large == 0 || a->large == 1);
      if (a->desc == NULL && a->large == true) { // 大於1024的記憶體
     mfree_page(PF, a, a->cnt); 
      } else {                 // 小於等於1024的記憶體塊
     /* 先將記憶體塊回收到free_list */
     list_append(&a->desc->free_list, &b->free_elem);

     /* 再判斷此arena中的記憶體塊是否都是空閑,如果是就釋放arena */
     if (++a->cnt == a->desc->blocks_per_arena) {
        uint32_t block_idx;
        for (block_idx = 0; block_idx < a->desc->blocks_per_arena; block_idx++) {
           struct mem_block*  b = arena2block(a, block_idx);
           ASSERT(elem_find(&a->desc->free_list, &b->free_elem));
           list_remove(&b->free_elem);
        }
        mfree_page(PF, a, 1); 
     } 
      }   
      lock_release(&mem_pool->lock); 
   }
}

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • ​ 問題描述 我在Eclipse上運行項目的時候,在我用fileUpload上傳圖片,等到下一次Tomcat刷新的時候,這個照片就沒了,而且點擊查看那個文件夾也查看不到有圖片文件。 原因 我後來在網上查詢相關資料發現是Eclipse整 合Tomcat的問題,因為他們整合之後,你用相對路徑上傳的圖片會 ...
  • 用戶 在系統里,用戶是一個核心概念。它代表了一個人的唯一身份標識,除了與角色、團隊、組織架構等有關,甚至還會影響到在同一個界面不同的用戶操作流程與顯示內容都會發生變化,再複雜一點的話,或許在同一個系統內的一個用戶進入到不同產品後的身份也會變化 用戶與角色 用戶可以擁有一個或多個角色,讓角色作為許可權組 ...
  • 當服務端啟動後,但是telnet其監聽的埠卻失敗了。或者當服務端運行了一段時間後,突然其監聽的埠telnet不通了。當類似這樣情況出現時,要如何排查問題所在了? ...
  • 以下代碼實現自定義Button,繼承WinForm的Button,新增了邊框、圓角設置的相關屬性。 public class ZhmButton : Button { private int borderSize = 0; // 邊框 private Color borderColor = Colo ...
  • Linux軟體軟體安裝命令 sudo apt-get update//更新源,檢查更新 sudo apt-get upgrade; sudo apt-get dist-upgrade sudo apt-get install//從源中安裝軟體 sudo apt-get remove 刪除包 gnom ...
  • 為什麼要使用Vite 在瀏覽器中提供ES模塊之前,開發人員沒有以模塊化方式編寫JavaScript的本機機制。這就是為什麼我們都很熟悉“捆綁”的概念:使用工具來抓取、處理和連接源模塊到可以在瀏覽器中運行的文件中。 隨著時間的推移,我們看到了webpack、Rollup和Parcel等工具,它們極大地 ...
  • 英文原文:https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cpusets.html Copyright (C) 2004 BULL SA. Written by [email protected] Portions Cop ...
  • 一 、通過雲開發平臺快速創建初始化應用 1.創建相關應用模版請參考鏈接:基於Vue的極簡生成器 — Vuepress 2.完成創建後就可以在github中查看到新增的vuepress倉庫 二 、 本地編寫 Vue文檔風格的技術文檔/博客 1.將應用模版克隆到本地 首先假定你已經安裝了Git、node ...
一周排行
    -Advertisement-
    Play Games
  • C#TMS系統代碼-基礎頁面BaseCity學習 本人純新手,剛進公司跟領導報道,我說我是java全棧,他問我會不會C#,我說大學學過,他說這個TMS系統就給你來管了。外包已經把代碼給我了,這幾天先把增刪改查的代碼背一下,說不定後面就要趕鴨子上架了 Service頁面 //using => impo ...
  • 委托與事件 委托 委托的定義 委托是C#中的一種類型,用於存儲對方法的引用。它允許將方法作為參數傳遞給其他方法,實現回調、事件處理和動態調用等功能。通俗來講,就是委托包含方法的記憶體地址,方法匹配與委托相同的簽名,因此通過使用正確的參數類型來調用方法。 委托的特性 引用方法:委托允許存儲對方法的引用, ...
  • 前言 這幾天閑來沒事看看ABP vNext的文檔和源碼,關於關於依賴註入(屬性註入)這塊兒產生了興趣。 我們都知道。Volo.ABP 依賴註入容器使用了第三方組件Autofac實現的。有三種註入方式,構造函數註入和方法註入和屬性註入。 ABP的屬性註入原則參考如下: 這時候我就開始疑惑了,因為我知道 ...
  • C#TMS系統代碼-業務頁面ShippingNotice學習 學一個業務頁面,ok,領導開完會就被裁掉了,很突然啊,他收拾東西的時候我還以為他要旅游提前請假了,還在尋思為什麼回家連自己買的幾箱飲料都要叫跑腿帶走,怕被偷嗎?還好我在他開會之前拿了兩瓶芬達 感覺感覺前面的BaseCity差不太多,這邊的 ...
  • 概述:在C#中,通過`Expression`類、`AndAlso`和`OrElse`方法可組合兩個`Expression<Func<T, bool>>`,實現多條件動態查詢。通過創建表達式樹,可輕鬆構建複雜的查詢條件。 在C#中,可以使用AndAlso和OrElse方法組合兩個Expression< ...
  • 閑來無聊在我的Biwen.QuickApi中實現一下極簡的事件匯流排,其實代碼還是蠻簡單的,對於初學者可能有些幫助 就貼出來,有什麼不足的地方也歡迎板磚交流~ 首先定義一個事件約定的空介面 public interface IEvent{} 然後定義事件訂閱者介面 public interface I ...
  • 1. 案例 成某三甲醫預約系統, 該項目在2024年初進行上線測試,在正常運行了兩天後,業務系統報錯:The connection pool has been exhausted, either raise MaxPoolSize (currently 800) or Timeout (curren ...
  • 背景 我們有些工具在 Web 版中已經有了很好的實踐,而在 WPF 中重新開發也是一種費時費力的操作,那麼直接集成則是最省事省力的方法了。 思路解釋 為什麼要使用 WPF?莫問為什麼,老 C# 開發的堅持,另外因為 Windows 上已經裝了 Webview2/edge 整體打包比 electron ...
  • EDP是一套集組織架構,許可權框架【功能許可權,操作許可權,數據訪問許可權,WebApi許可權】,自動化日誌,動態Interface,WebApi管理等基礎功能於一體的,基於.net的企業應用開發框架。通過友好的編碼方式實現數據行、列許可權的管控。 ...
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...