1. 程式人生 > >linux 記憶體探測和初始化

linux 記憶體探測和初始化

1、記憶體探測

    linux在被bootloader載入到記憶體後, cpu最初執行的核心程式碼是arch/x86/boot/header.S彙編檔案中的_start例程,設定好頭部header,其中包括大量的bootloader引數。接著是其中的start_of_setup例程,這個例程在做了一些準備工作後會通過call main跳轉到arch/x86/boot/main.c:main()函式處執行,這就是眾所周知的x86下的main函式,它們都工作在真實模式下。在這個main函式中我們可以第一次看到與記憶體管理相關的程式碼,這段程式碼呼叫detect_memory()函式檢測系統實體記憶體。如下:

  1. void main(void)  
  2. {  
  3.     /* First, copy the boot header into the "zeropage" */
  4.     copy_boot_params(); /* 把頭部各引數複製到boot_params變數中 */
  5.     /* End of heap check */
  6.     init_heap();  
  7.     /* Make sure we have all the proper CPU support */
  8.     if (validate_cpu()) {  
  9.         puts("Unable to boot - please use a kernel appropriate "
  10.              "for your CPU.\n");  
  11.         die();  
  12.     }  
  13.     /* Tell the BIOS what CPU mode we intend to run in. */
  14.     set_bios_mode();  
  15.     /* Detect memory layout */
  16.     detect_memory(); /* 記憶體探測函式 */
  17.     /* Set keyboard repeat rate (why?) */
  18.     keyboard_set_repeat();  
  19.     /* Query MCA information */
  20.     query_mca();  
  21.     /* Query Intel SpeedStep (IST) information */
  22.     query_ist();  
  23.     /* Query APM information */
  24. #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
  25.     query_apm_bios();  
  26. #endif
  27.     /* Query EDD information */
  28. #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
  29.     query_edd();  
  30. #endif
  31.     /* Set the video mode */
  32.     set_video();  
  33.     /* Parse command line for 'quiet' and pass it to decompressor. */
  34.     if (cmdline_find_option_bool("quiet"))  
  35.         boot_params.hdr.loadflags |= QUIET_FLAG;  
  36.     /* Do the last things and invoke protected mode */
  37.     go_to_protected_mode();  
  38. }  
    記憶體探測的實現在arch/x86/boot/memory.c中,如下:
  1. int detect_memory(void)  
  2. {  
  3.     int err = -1;  
  4.     if (detect_memory_e820() > 0)  
  5.         err = 0;  
  6.     if (!detect_memory_e801())  
  7.         err = 0;  
  8.     if (!detect_memory_88())  
  9.         err = 0;  
  10.     return err;  
  11. }  
    由上面的程式碼可知,linux核心會分別嘗試呼叫detect_memory_e820()、detcct_memory_e801()、detect_memory_88()獲得系統實體記憶體佈局,這3個函式都在memory.c中實現,它們內部其實都會以內聯彙編的形式呼叫bios中斷以取得記憶體資訊,該中斷呼叫形式為int 0x15,同時呼叫前分別把AX暫存器設定為0xe820h、0xe801h、0x88h,關於0x15號中斷有興趣的可以去查詢相關手冊。下面分析detect_memory_e820()的程式碼,其它程式碼基本一樣。
  1. #define SMAP    0x534d4150  /* ASCII "SMAP" */
  2. staticint detect_memory_e820(void)  
  3. {  
  4.     int count = 0; /* 用於記錄已檢測到的實體記憶體數目 */
  5.     struct biosregs ireg, oreg;  
  6.     struct e820entry *desc = boot_params.e820_map;  
  7.     staticstruct e820entry buf; /* static so it is zeroed */
  8.     initregs(&ireg); /* 初始化ireg中的相關暫存器 */
  9.     ireg.ax  = 0xe820;  
  10.     ireg.cx  = sizeof buf; /* e820entry資料結構大小 */
  11.     ireg.edx = SMAP; /* 標識 */
  12.     ireg.di  = (size_t)&buf; /* int15返回值的存放處 */
  13.     /* 
  14.      * Note: at least one BIOS is known which assumes that the 
  15.      * buffer pointed to by one e820 call is the same one as 
  16.      * the previous call, and only changes modified fields.  Therefore, 
  17.      * we use a temporary buffer and copy the results entry by entry. 
  18.      * 
  19.      * This routine deliberately does not try to account for 
  20.      * ACPI 3+ extended attributes.  This is because there are 
  21.      * BIOSes in the field which report zero for the valid bit for 
  22.      * all ranges, and we don't currently make any use of the 
  23.      * other attribute bits.  Revisit this if we see the extended 
  24.      * attribute bits deployed in a meaningful way in the future. 
  25.      */
  26.     do {  
  27.         /* 在執行這條內聯彙編語句時輸入的引數有:  
  28.         eax暫存器=0xe820  
  29.         dx暫存器=’SMAP’  
  30.         edi暫存器=desc  
  31.         ebx暫存器=next  
  32.         ecx暫存器=size  
  33.          返回給c語言程式碼的引數有:  
  34.         id=eax暫存器 
  35.         rr=edx暫存器  
  36.         ext=ebx暫存器 
  37.         size=ecx暫存器  
  38.         desc指向的記憶體地址在執行0x15中斷呼叫時被設定  
  39.         */
  40.         intcall(0x15, &ireg, &oreg);  
  41.         ireg.ebx = oreg.ebx; /* 選擇下一個 */
  42.         /* BIOSes which terminate the chain with CF = 1 as opposed 
  43.            to %ebx = 0 don't always report the SMAP signature on 
  44.            the final, failing, probe. */
  45.         if (oreg.eflags & X86_EFLAGS_CF)  
  46.             break;  
  47.         /* Some BIOSes stop returning SMAP in the middle of 
  48.            the search loop.  We don't know exactly how the BIOS 
  49.            screwed up the map at that point, we might have a 
  50.            partial map, the full map, or complete garbage, so 
  51.            just return failure. */
  52.         if (oreg.eax != SMAP) {  
  53.             count = 0;  
  54.             break;  
  55.         }  
  56.         *desc++ = buf; /* 將buf賦值給desc */
  57.         count++; /* 探測數加一 */
  58.     } while (ireg.ebx && count < ARRAY_SIZE(boot_params.e820_map));  
  59.     /* 將記憶體塊數保持到變數中 */
  60.     return boot_params.e820_entries = count;  
  61. }  
    由於歷史原因,一些I/O裝置也會佔據一部分記憶體實體地址空間,因此係統可以使用的實體記憶體空間是不連續的,系統記憶體被分成了很多段,每個段的屬性也是不一樣的。int 0x15查詢實體記憶體時每次返回一個記憶體段的資訊,因此要想返回系統中所有的實體記憶體,我們必須以迭代的方式去查詢。detect_memory_e820()函式把int 0x15放到一個do-while迴圈裡,每次得到的一個記憶體段放到struct e820entry裡,而struct e820entry的結構正是e820返回結果的結構。像其它啟動時獲得的結果一樣,最終都會被放到boot_params裡,探測到的各個記憶體段情況被放到了boot_params.e820_map。
    這裡存放中斷返回值的e820entry結構,以及表示記憶體圖的e820map結構均位於arch/x86/include/asm/e820.h中,如下:
  1. struct e820entry {  
  2.     __u64 addr; /* 記憶體段的開始 */
  3.     __u64 size; /* 記憶體段的大小 */
  4.     __u32 type; 

    相關推薦

    linux 記憶體探測初始

    1、記憶體探測     linux在被bootloader載入到記憶體後, cpu最初執行的核心程式碼是arch/x86/boot/header.S彙編檔案中的_start例程,設定好頭部header,其中包括大量的bootloader引數。接著是其中的start_

    Linux環境變數初始配置

    1.環境變數在 etc/profile 檔案中, PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/samba/bin 新增或者刪除 環境變數 也可以直接使用 PATH=$PATH:/usr/local

    啟動期間的記憶體管理之初始過程概述----Linux記憶體管理(九)

    在記憶體管理的上下文中, 初始化(initialization)可以有多種含義. 在許多CPU上, 必須顯式設定適用於Linux核心的記憶體模型. 例如在x86_32上需要切換到保護模式, 然後核心才能檢測到可用記憶體和暫存器. 而我們今天要講的boot階段

    linux C 結構體struct的定義初始

    struct 定義: struct test { int a; long b; float c; char d; char e[]; }tt1,tt2;//物件宣告列表緊跟struct定義 struct test tt3,tt4;//單獨宣告物件列表 //宣告一個沒結構體名

    C++學習之記憶體的分配初始

    C++定義了2個運算子來分配和釋放動態記憶體。new分配記憶體,delete釋放記憶體。 1. 使用new動態分配和初始化物件 在自由空間分配的記憶體是無名的,new返回一個指向分配的物件的指標。 int *pi = new int; // pi指向一個動態分

    linux動態庫的初始清理

       a. Windows 中有 DllMain 入口函式, 而 Linux 中則沒有。    b. Linux 中有特殊函式 _init 和 _fini, 主要是分別用來初始化動態庫和關閉的時候       做一些必要的處理, 我們可以把自己認為需要的程式碼放到這兩個

    C#中對於變量的聲明初始

    最好 編譯 return 它的 con code 數據 類型安全 狀態   C#變量初始化是C#強調安全性的另一個例子。簡單地說,C#編譯器需要用某個初始值對變量進行初始化,之後才能在操作中引用該變量。大多數現代編譯器把沒有初始化標記為警告,但C#編譯器把它當作錯誤來看待。

    c++中成員函數指針數組定義初始方法

    fun all turn bsp ati const 成員函數指針 溢出 cat 實際項目中經常遇到很多類似操作,比如命令碼對應執行函數等,對於此類操作,比較好的方式是使用const數組,將命令碼和操作函數綁定在一起,通過查表方式找到操作函數,並執行操作函數。這樣可以簡化代

    有關變量的聲明初始的問題

    stat 對象實例 實例 [] ima als void img 比較 1.馬上就要考java了,於是刷幾道題,題目不難但是比較基礎,比較細節,其中一道題目如下: 為了弄明白那些變量需要提前聲明,我做了如下幾個小測試: 測試一: 1 package priv.xiaom

    JAVA中對象創建初始過程

    2.3 人的 cin 類型變量 認識 handle product window blank 1.Java中的數據類型   Java中有3個數據類型:基本數據類型(在Java中,boolean、byte、short、int、long、char、float、double這八種

    nsq源碼閱讀筆記之nsqd(一)——nsqd的配置解析初始

    con views pos 直接 rgba 函數調用 程序 spa 重命名 配置解析nsqd的主函數位於apps/nsqd.go中的main函數首先main函數調用nsqFlagset和Parse進行命令行參數集初始化, 然後判斷version參數是否存在,若存在,則打印版

    金甲防線服務項目筆記:菜單初始

    serial 16px http private ftdi fig user ping -i 1、首先數據庫jdbc配置:config路徑下,配置jdbc,數據庫名:jjfxxin,賬戶:root,密碼:123456 2、菜單初始化設置: //3.5.4服務中心--&

    Java數組的創建初始

    java 類 操作符 我們說到數組,可能有的人就會比較害怕了,其實,數組只是把對象序列(很多個對象)或者基本類型序列(很多個基本類型)放在一起而已。數組是通過方括號下標操作符[]來定義和使用的。如果要定義,創建一個數組,只需在類型名後加上一對方括號[]即可。如果要定義二維數組,那麽,就要加兩個方括

    SpringMVC源碼解析-DispatcherServlet啟動流程初始

    instant custom delegate bean 自己的 erro -- true long 在使用springmvc框架,會在web.xml文件配置一個DispatcherServlet,這正是web容器開始初始化,同時會在建立自己的上下文來持有Spring

    hibernate_基本配置初始步驟

    導入jar包 導入 eclips ips conf 數據 hiberna 持久化類 步驟 1.hibernate使用步驟: 1)創建hibernate配置文件 2)創建持久化類 3)創建對象-關系映射 4)通過hibernate api編寫訪問數據庫的代碼 2.ecli

    linux文件系統 - 初始(一)

    成員函數 cat 文章 記錄 inf htm 源代碼 設備驅動模型 proxy 術語表: struct task:進程 struct mnt_namespace:命名空間 struct mount:掛載點 struct vfsmount:掛載項 struct file:文件

    linux文件系統 - 初始(三)

    視圖 div 目錄遷移 oca script 方式 不能 輸出 str 一、目的 內核加載完initrd文件後,為掛載磁盤文件系統做好了必要的準備工作,包括掛載了sysfs、proc文件系統,加載了磁盤驅動程序驅動程序等。接下來,內核跳轉到用戶空間的init程序,

    linux文件系統 - 初始(二)

    軟鏈接 復制代碼 模式 文件的 操作 pop 臨時 console mini 一、目的 本文主要講述linux3.10文件系統初始化過程的第二階段:加載initrd。 initrd是一個臨時文件系統,由bootload負責加載到內存中,裏面包含了基本的可執

    SQLAlchemy數據庫連接初始數據庫

    數據 即使 conf 執行 int rop windows use VR 查看版本 >>> import sqlalchemy >>> sqlalchemy.__version__ ‘1.0.9‘ 創建連接 from sqlclach

    JVM基礎學習之類的加載、鏈接初始

    條件 希望 運行 ring get 準備 AR return 復雜   本文我們一起討論Java類的加載、鏈接和初始化。 Java字節代碼的表現形式是字節數組(byte[]),而Java類在JVM中的表現形式是 java.lang.Class類 的對象。一個Java類從字節