Linux-3.14.12記憶體管理筆記【建立內核頁表(1)】

来源:https://www.cnblogs.com/linhaostudy/archive/2019/09/28/11603930.html
-Advertisement-
Play Games

前面已經分析過了Intel的記憶體映射和linux的基本使用情況,已知head_32.S僅是建立臨時頁表,內核還是要建立內核頁表,做到全面映射的。下麵就基於RAM大於896MB,而小於4GB ,切CONFIG_HIGHMEM配置了高端記憶體的環境情況進行分析。 建立內核頁表前奏,瞭解兩個很關鍵的變數: ...


前面已經分析過了Intel的記憶體映射和linux的基本使用情況,已知head_32.S僅是建立臨時頁表,內核還是要建立內核頁表,做到全面映射的。下麵就基於RAM大於896MB,而小於4GB ,切CONFIG_HIGHMEM配置了高端記憶體的環境情況進行分析。

建立內核頁表前奏,瞭解兩個很關鍵的變數:

  • max_pfn:最大物理記憶體頁面幀號;
  • max_low_pfn:低端記憶體區(直接映射空間區的記憶體)的最大可用頁幀號;

max_pfn 的值來自setup_arch()中,setup_arch()函數中有:

max_pfn = e820_end_of_ram_pfn();

那麼接下來看一下e820_end_of_ram_pfn()的實現:

804762

【file:/arch/x86/kernel/e820.c】
unsigned long __init e820_end_of_ram_pfn(void)
{
    return e820_end_pfn(MAX_ARCH_PFN, E820_RAM);
}

e820_end_of_ram_pfn()直接封裝調用e820_end_pfn(),而其入參為MAX_ARCH_PFN和E820_RAM,其中MAX_ARCH_PFN的定義(x86的32bit環境)為:

#  define MAX_ARCH_PFN              (1ULL<<(32-PAGE_SHIFT))

最終值為0x100000,它表示的是4G物理記憶體的最大頁面幀號;而E820_RAM為:

#define E820_RAM                 1

接下來看一下e820_end_pfn()函數實現:

【file:/arch/x86/kernel/e820.c】
/*
 * Find the highest page frame number we have available
 */
static unsigned long __init e820_end_pfn(unsigned long limit_pfn, unsigned type)
{
    int i;
    unsigned long last_pfn = 0;
    unsigned long max_arch_pfn = MAX_ARCH_PFN;
 
    for (i = 0; i < e820.nr_map; i++) {
        struct e820entry *ei = &e820.map[i];
        unsigned long start_pfn;
        unsigned long end_pfn;
 
        if (ei->type != type)
            continue;
 
        start_pfn = ei->addr >> PAGE_SHIFT;
        end_pfn = (ei->addr + ei->size) >> PAGE_SHIFT;
 
        if (start_pfn >= limit_pfn)
            continue;
        if (end_pfn > limit_pfn) {
            last_pfn = limit_pfn;
            break;
        }
        if (end_pfn > last_pfn)
            last_pfn = end_pfn;
    }
 
    if (last_pfn > max_arch_pfn)
        last_pfn = max_arch_pfn;
 
    printk(KERN_INFO "e820: last_pfn = %#lx max_arch_pfn = %#lx\n",
             last_pfn, max_arch_pfn);
    return last_pfn;
}

這個函數用來查找最大物理的頁面幀號,通過對e820圖的記憶體塊信息得到記憶體塊的起始地址,將起始地址右移PAGE_SHIFT,算出其起始地址對應的頁面幀號,如果足夠大,超出了limit_pfn則設置最大頁面幀號為limit_pfn,否則則設置為遍歷中找到的最大的last_pfn。

e820_end_of_ram_pfn()函數的調用位置:

start_kernel()                           #init/main.c

└─>setup_arch()                        #arch/x86/kernel/setup.c

├─>e820_end_of_ram_pfn()              #arch/x86/kernel/e820.c

└─>find_low_pfn_range()               #arch/x86/kernel/e820.c

其中find_low_pfn_range()用於查找低端記憶體的最大頁面數的 ,max_low_pfn則在這裡面初始化。

find_low_pfn_range()代碼實現:

【file:/arch/x86/mm/init_32.c】
/*
 * Determine low and high memory ranges:
 */
void __init find_low_pfn_range(void)
{
    /* it could update max_pfn */
 
    if (max_pfn <= MAXMEM_PFN)
        lowmem_pfn_init();
    else
        highmem_pfn_init();
}

函數實現很簡單,根據max_pfn是否大於MAXMEM_PFN,從而判斷是否初始化高端記憶體,也可以認為是啟用。那麼來看一下MAXMEM_PFN的巨集定義:

(file:/arch/x86/include/asm/setup.h)

#define MAXMEM_PFN               PFN_DOWN(MAXMEM)

其中PFN_DOWN(x)的定義為:

(file:/include/linux/pfn.h)

#define PFN_DOWN(x)              ((x) >> PAGE_SHIFT)

PFN_DOWN(x)是用來返回小於x的最後一個頁面號,對應的還有個PFN_UP(x)是用來返回大於x的第一個頁面號,此外有個PFN_PHYS(x)返回的是x的物理頁面號。接著看MAXMEM的定義:

(file:arch/x86/include/asm/pgtable_32_types.h)

#define MAXMEM                   (VMALLOC_END - PAGE_OFFSET - __VMALLOC_RESERVE)

那麼VMALLOC_END的定義則為:

(file:arch/x86/include/asm/pgtable_32_types.h)

#define VMALLOC_END              (PKMAP_BASE - 2 * PAGE_SIZE)

//永久記憶體映射
#define PKMAP_BASE ((FIXADDR_BOOT_START - PAGE_SIZE * (LAST_PKMAP + 1)) & PMD_MASK)

其中PKMAP_BASE是永久映射空間的起始地址,LAST_PKMAP則是永久映射空間的映射頁面數,定義為:

#define LAST_PKMAP 1024

另外PAGE_SHIFT和PAGE_SIZE的定義為:

#define PAGE_SHIFT               12
#define PAGE_SIZE                (_AC(1,UL) << PAGE_SHIFT)

而FIXADDR_BOOT_START是臨時固定映射空間起始地址,其的相關巨集定義:

臨時記憶體映射:

#define FIXADDR_BOOT_SIZE        (__end_of_fixed_addresses << PAGE_SHIFT)

#define FIXADDR_BOOT_START       (FIXADDR_TOP - FIXADDR_BOOT_SIZE)

unsigned long __FIXADDR_TOP = 0xfffff000;

extern unsigned long __FIXADDR_TOP;

#define FIXADDR_TOP              ((unsigned long)__FIXADDR_TOP)

這裡其中的__end_of_fixed_addresses是來自fixed_addresses枚舉值,是固定映射的一個標誌。此外這裡的FIXADDR_TOP是固定映射區末尾,而另外還有一個這裡未列出的FIXADDR_START,是固定映射區起始地址。

既然到此,順便介紹一下內核空間映射情況。

image

內核空間如上圖,分為直接記憶體映射區(低端記憶體,線性)和高端記憶體映射區。其中直接記憶體映射區是指3G到3G+896M的線性空間,直接對應物理地址就是0到896M(前提是有超過896M的物理記憶體),其中896M是high_memory值,使用kmalloc()/kfree()介面操作申請釋放;

而高端記憶體映射區則是至超多896M物理記憶體的空間,它又分為動態映射區、永久映射區和固定映射區。

  • 動態記憶體映射區,又稱之為vmalloc映射區或非連續映射區,是指VMALLOC_START到VMALLOC_END的地址空間,申請釋放操作介面是vmalloc()/vfree(),通常用於將非連續的物理記憶體映射為連續的線性地址記憶體空間;
  • 而永久映射區,又稱之為KMAP區或持久映射區,是指自PKMAP_BASE開始共LAST_PKMAP個頁面大小的空間,操作介面是kmap()/kunmap(),用於將高端記憶體長久映射到記憶體虛擬地址空間中;
  • 最後的固定映射區,也稱之為臨時內核映射區,是指FIXADDR_START到FIXADDR_TOP的地址空間,操作介面是kmap_atomic()/kummap_atomic(),用於解決持久映射不能用於中斷處理程式而增加的臨時內核映射。

下圖是根據個人的實驗環境繪製的一張關於內核空間映射情況。

image

PMD_MASK涉及的巨集定義:

(file:/include/asm-generic/pgtable-nopmd.h)

#define PMD_SHIFT                PUD_SHIFT

#define PMD_SIZE                 (1UL << PMD_SHIFT)

#define PMD_MASK                 (~(PMD_SIZE-1))

(file:/include/asm-generic/pgtable-nopud.h)

#define PUD_SHIFT                PGDIR_SHIFT

(file:arch/x86/include/asm/Pgtable-2level_types.h)

#define PGDIR_SHIFT              22

PMD_MASK計算結果是:0xFFC00000,其實是用於數據對齊而已。

已知PAGE_OFFSET預設的為0xC0000000,而__VMALLOC_RESERVE為:

unsigned int __VMALLOC_RESERVE = 128 << 20;

最後在個人的實驗環境上,得出MAXMEM_PFN的值為0x377fe。

Linux是一個支持多硬體平臺的操作系統,各種硬體晶元的分頁並非固定的2級(頁全局目錄和頁表),僅僅Intel處理器而言,就存在3級的情況(頁全局目錄、頁中間目錄和頁表),而到了64位系統的時候就成了4級分頁所以Linux為了保持良好的相容性和移植性,系統設計成了以下的4級分頁模型,根據平臺環境和配置的情況,通過將頁上級目錄和頁中間目錄的索引位設置為0,從而隱藏了頁三級目錄和頁中間目錄的存在。也就是為什麼存在PMD_SHIFT、PUD_SHIFT和PGDIR_SHIFT,還有pgtable-nopmd.h、pgtable-nopud.h和Pgtable-2level_types.h的原因了。

image

由此管中窺豹,看到了Linux記憶體分頁映射模型的存在和相關設計,暫且也就先瞭解這麼多。

分析巨集是一件很乏味的事情,不過以小見大卻是一件很有意思的事情。

【file:/arch/x86/mm/init_32.c】
/*

 * We have more RAM than fits into lowmem - we try to put it into
 * highmem, also taking the highmem=x boot parameter into account:
 */
static void __init highmem_pfn_init(void)
{
    max_low_pfn = MAXMEM_PFN;
 
    if (highmem_pages == -1)
        highmem_pages = max_pfn - MAXMEM_PFN;
 
    if (highmem_pages + MAXMEM_PFN < max_pfn)
        max_pfn = MAXMEM_PFN + highmem_pages;
 
    if (highmem_pages + MAXMEM_PFN > max_pfn) {
        printk(KERN_WARNING MSG_HIGHMEM_TOO_SMALL,
            pages_to_mb(max_pfn - MAXMEM_PFN),
            pages_to_mb(highmem_pages));
        highmem_pages = 0;
    }
#ifndef CONFIG_HIGHMEM
    /* Maximum memory usable is what is directly addressable */
    printk(KERN_WARNING "Warning only %ldMB will be used.\n", MAXMEM>>20);
    if (max_pfn > MAX_NONPAE_PFN)
        printk(KERN_WARNING "Use a HIGHMEM64G enabled kernel.\n");
    else
        printk(KERN_WARNING "Use a HIGHMEM enabled kernel.\n");
    max_pfn = MAXMEM_PFN;
#else /* !CONFIG_HIGHMEM */
#ifndef CONFIG_HIGHMEM64G
    if (max_pfn > MAX_NONPAE_PFN) {
        max_pfn = MAX_NONPAE_PFN;
        printk(KERN_WARNING MSG_HIGHMEM_TRIMMED);
    }
#endif /* !CONFIG_HIGHMEM64G */
#endif /* !CONFIG_HIGHMEM */
}

highmem_pfn_init()看起來很長,貌似很複雜,實際上僅僅是把max_low_pfn設置為MAXMEM_PFN,而highmem_pages設置為max_pfn - MAXMEM_PFN,至於後面的幾乎都是為了防止某些數據過大過小引起翻轉而做的保障性工作。需要說明的是這裡的max_low_pfn作為直接映射空間區的記憶體最大可用頁幀號,並不是896M大小記憶體的頁面數。896M只是定義高端記憶體的一個界限,至於直接映射記憶體大小隻定義了不超過896M而已。

此外還有一個準備操作,在setup_arch()函數中調用的頁表緩衝區申請操作:

early_alloc_pgt_buf():

【file:/arch/x86/mm/init.c】
void __init early_alloc_pgt_buf(void)
{
    unsigned long tables = INIT_PGT_BUF_SIZE;
    phys_addr_t base;
 
    base = __pa(extend_brk(tables, PAGE_SIZE));
 
    pgt_buf_start = base >> PAGE_SHIFT;
    pgt_buf_end = pgt_buf_start;
    pgt_buf_top = pgt_buf_start + (tables >> PAGE_SHIFT);
}

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

-Advertisement-
Play Games
更多相關文章
  • 最近遇到一個.NET連接Oracle的一個錯誤,其主要原因是換了一臺電腦,在新電腦上運行以前的項目出現了的一個錯誤,工作環境為vs2017+Oracle 64位,win10系統 這個錯誤頭疼了一天,找了好多博客去解決這個問題 在這主要是總結一下本人的解決思路與方法。 1.查看自己的Oracle客戶端 ...
  • 前言 OAuth 2.0預設四種授權模式(GrantType) 授權碼模式(authorization_code) 簡化模式(implicit) 密碼模式(resource owner password credentials) "客戶端模式(client_credentials)" 本章主要介紹客 ...
  • [toc] 前言 本來打算昨天都開始寫這篇,就因為要把小團隊的博客整理彙總,一看二哈的博客那麼多,一個個複製粘貼肯定麻煩(其實是我自己覺得複製麻煩),所以穿插著寫了個小爬蟲,後續寫差不多了就拿出來晾晾吧(py菜雞水平)。 之前開發的時候,忽略了記錄,等到想寫點兒啥跟後臺有關的東西的時候,還得一點點回 ...
  • 上一章講到了配置的用法及內部處理機制,對於配置,ASP.NET Core還提供了一種Options模式。(ASP.NET Core 系列目錄) 一、Options的使用 上一章有個配置的綁定的例子,可以將配置綁定到一個Theme實例中。也就是在使用對應配置的時候,需要進行一次綁定操作。而Option ...
  • STUN/TURN伺服器搭建 [toc] 編譯安裝 1. 編譯安裝 OpenSSL; 2. 編譯安裝 libevent 最新版; 3. coturn 可以選擇使用多種資料庫,這裡使用的是 SQLite ,使用命令 (or sqlite3) 和 (or sqlite3 dev) 安裝; 4. 編譯co ...
  • 一. 不同用戶下配置virtualenvwrapper的問題 問題描述: 安裝virtualnev和virtualnevwrapper之後,在.bashrc進行virtualenvwrapper的相關配置後重新載入.bashrc文件時報錯. 報錯內容大致如下: 也就是說系統檢測當前pip下的安裝軟體 ...
  • Centos 8阿裡雲下載地址https://mirrors.aliyun.com/centos/8.0.1905/isos/x86_64/ Centos8的一些變化 網路服務: 在/etc/sysconfig/network-scripts/目錄下只有一個當前網卡的配置文件 預設重啟服務不在使用/ ...
  • 在串口數據發送操作中,代碼一般是這樣寫的: 今天我們就來探討一下——while(USART_GetFlagStatus(USARTx, USART_FLAG_TXE) == RESET); 到底有什麼作用 首先看一個標準庫文件:stm32l1xx_usart.c中對這個函數的描述: 暫且不管函數的內 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...