Linux內存整理和學習

        本文僅是根據網絡上的資料結合自己對Linux內存的理解形成的一些文檔,主要是爲Android虛擬機以及Android系統相關的知識做準備,所以更多的是概念性結論性的東西,並沒有Liunx內核相關的代碼,對於專家來說也是很low的文章,可能還會有一些錯誤,歡迎大家來批評指正。

           Android是基於Linux內核的,所以在系統層面上更多的是Linux的架構和邏輯,比如對進程的管控,比如進程的內存架構和管理,個人覺得如果想要從系統層面上熟悉Android機制和Android虛擬機,對Linux的掌握是必要條件之一。

        在本文中我從以下幾個方面整理了Linux內存相關的知識:

  1. 每個進程中內存佔據的大小爲4G,如何理解這4G;
  2. 這4G內存的分佈情況,每一個類型各有什麼特點;
  3. Linux內存的管理的幾個方法,每個方法有什麼特點;
  4. mmap實現原理和機制。

        瞭解過Linux內存的人都聽過:每一個進程中都有4G的虛擬內存空間,這4G內存空間由2部分組成,0-3G(虛擬地址 0x00000000到0xBFFFFFFF)是用戶空間,3G-4G(虛擬地址0xC0000000到0xFFFFFFFF)是內核空間,這句話。其實更加準確的說cpu的尋址能力是2^32就是4G。現在很多的機器更多的是64位的,那這個時候的是否是2^64的地址空間呢?這個不是的,對於64位的虛擬地址爲2^48,即用48位來表示虛擬地址空間,用40位來表示物理地址。關於這個可以使用命令cat /proc/cpuinfo來查看,對應的address size 裏面可以看到物理和虛擬。

     關於Linux程序的用戶空間的虛擬內存分佈,網上有很多,說的粗一些可以依次低到高分爲代碼區、數據區、堆和棧。當然這裏要重點說下詳細的分區可以分爲:保留區、 rodata 段、text段、bss段、.data段、堆、mmap段、命令行參數和環境變量段、棧。

       我們先來看下不同的方法屬性對應的相關的內存分佈,隨便寫一個帶有各種屬性的,並且打印每一個變量的指針,然後cat proc/{pid}/maps對應的結果

     從打印的結果和cat proc/{pid}/maps的結果對應如下:

 

                                                                                              圖一

        在cat maps出來每一行列對應的意義就不多說,這裏主要想討論下對應的段和cat maps直接的關係。前面說過每一個應用都包含一些分區和段,那麼在這個程序裏面每個分區和段都是對應什麼呢,從這些打印的變量指針值大致能看出來,但很多時候在cat maps裏面並沒有區分出對應的段,如果我們要看可以在這些段在虛擬內存中的位置,可以使用readelf –a 去解析這個elf文件(這裏只是小試牛刀下,後面學習虛擬機的時候會重點學習elf文件),看每一個段對應的內容,每個段對應虛擬地址的內容如下表所示。

 

 

text:函數

0040000-0040100

rodata:define 常量

bss:方法中未初始化化的數據

00602000-00604000

data:static,

01b8f000-01bbf000

mmap

7f791d2d3000-7f791d2d4000

stack

7fff46e18000-7fff46e39000

環境變量

參數

         這裏要特別注意下mmap這個段,因爲mmap主要是去讀取結構體vm_area_struct,而網上說這個結構體存儲的是有名映射內存(此處請教過樸老師,樸老師的說有名匿名都會有,但在匿名的時候貌似不管怎麼操作都不會有,樸老師的說法應該是正確的但因爲我沒有驗證到所以先寫我的結論),而我將其修改爲有名映射在maps中也看不到,這裏要說的是如果我們僅是單純的做映射這樣Linux是還不會分配空間的,但如果我們對這塊內存做了一個操作,比如將裏面的指針加1,這樣才能看到,即延遲分配(此處感謝我飛飛兄的指點),如圖二。

                                                               圖二

 

           Linux分配內存主要有兩個比較基礎的算法夥伴算法和slab算法,夥伴算法主要用於分配一個頁即4k以上的大內存,好處是能減少內存碎片,slab算法主要用於分配小內存,這兩個算法是實現malloc的原理。

在linux系統中有很多分配內存的方法,這裏先整理一下關於brk()/sbrk()函數的原理和使用,先使用man 查看下該函數

          這個描述很好的解釋了brk()和sbrk()函數的原理,這個函數函數是通過改變程序斷點的位置,什麼是程序斷點呢,這個是程序數據段的尾端。前面提了一句虛擬內存粗略的可以分爲4個區,按照文檔翻譯過來是這個斷點指的就是數據區和堆的分割點,我們寫一個程序,另sbrk(0),會發現此時這個值是指向堆的最後的值如圖三。估計幫助文檔中所謂的date segment說的並不是Linux虛擬內存的date段。不過這個也很好理解,date區域時編譯完成的時候就分配好了,但堆上分配的動態分配的,每一次使用brk()/sbrk()對分配的時候都是通過移動這個點的位置,也正是因爲這點,如果使用這兩個釋放空間也是通過反向移動這個點的值,所以釋放的順序是高地址先釋放。

                                                                      圖三

在Linux裏面還有一個最爲常見的申請和釋放內存的方法是malloc()/free(),我們先看下它的描述

       我更多的是希望通過description來說下malloc的實現原理,但這裏只介紹了calloc()、realloc()這些calloc()的兄弟函數,並沒有介紹相關的原理。因爲malloc()的頭文件是在stdlib裏面,所以可以去/usr/include和http://ftp.gnu.org/gnu/glibc連接裏面去找對應的實現。具體的算法和源碼可以去參照下面相關的鏈接,我這裏總結一下:malloc核心的實現是使用block這個結構體,計算出要分配的內存空間,通過sbrk()這個方法來實現的。

還有就是relloc()和calloc()兩個方法,calloc()主要是申請完內存之後會把裏面的元素都默認置成0,而relloc()方法是指remalloc,在之前的基礎上重新malloc,如果重新分配的大小比之前的大小要小則可能會數據丟失,如果重新分配的大小比之前的要大,之前的那段空間的指針會變成野指針,注意要釋放該指針。relloc(void*ptr,size_t new_size)傳入的那個ptr必須爲NULL或者malloc的返回值。

關於mmap的實現原理其實從它的用途也可以基本上能猜到一二,它是虛擬內存的映射,而Linux主要是通過文件來實現的,那這個步驟基本上就很明確了,首先是通過虛擬內存找到對應的文件,在讓另一個進程的虛擬空間的地址也指向這個文件,當然Linux裏面有一個寫時給的機制(即上面說的爲什麼只映射不會分配內存的原因)。

用專業的話是下面幾步驟:

  1. 新建一個vm_area_struct的結構體,將這結構體插入到進程的虛擬地址區域或樹中;
  2. 通過fd找到對應的內核中該fd對應的文件結構體,並調用內核中的int mmap(struct file *filp,struct vm_area_struct *vma)通過虛擬文件中的inode模塊定位到文件磁盤的物理空間;
  3. 通過remap_pfn_range函數建立頁表,這樣文件地址和虛擬地址的映射關係就建立了;
  4. 到第三步映射已經建立完成,但還沒有真正的分配內存,如果有訪問這塊內存的操作,這個時候會引發缺頁異常;
  5. 判定缺頁異常並非非法操作時,這個時候內核會發起請求調頁的過程;
  6. 調頁完成之後,對應的頁將從磁盤寫到主存,由於用戶對這片主存進行寫操作,一定時間後系統會把髒頁會寫到磁盤。

   對我個人而言,這裏比較好奇的主要有這幾個:

   1. vm_area_struct的數據結構和用法;

   2.建立頁表的過程,即remap_pfn_range方法的原理;

   3.缺頁異常。

    先說說vm_area_struc,關於vm_area_struct數據結構和用法,本來是想寫一個程序使用task_struct找到mm_struct再找到vm_area_struct,遍歷每一個vm_area_struct把其vm_start和vm_end打印出來看和第一部分的maps裏面的值是不是一樣,但Linux內核這塊對我來說寫起來太耗時間,這就從網上貼上一張圖片來說明下這幾個結構體的關係吧。

                                                     

                                                                              圖四

    第二個remap_pfn_range方法也先放一下等有機會學內核的時候再來重新學習下。

    缺頁異常簡單的說下吧,給定一個線性地址,MMU 通過頁目錄表、頁表的轉換,找到對應的物理地址。在這個過程中,如果因某種原因導致無法訪問到最終的物理內存單元,CPU 會產生一次缺頁異常。所以這個缺頁異常在這裏並不是異常,只是當映射完成之後並沒有分配真正的物理地址空間,這個時候引發的缺頁異常,當然還有的時候是這個地址就是一個錯誤(或者沒有權限)的地址,這個時候就會發生段錯誤了,具體的處理過程可以參照do_page_fault()這個方法。

       

這篇主要是學習爲主,東西大部分是別人的,目的是爲了真正學習Android虛擬機和系統的,這裏的別人主要是下面的幾位同學

 

https://blog.csdn.net/weixin_41576955/article/details/84075908

https://blog.csdn.net/hustyangju/article/details/46330259

https://blog.csdn.net/huiguixian/article/details/6325383?utm_source=blogxgwz6

https://blog.csdn.net/wenqian1991/article/details/27968779

https://blog.csdn.net/sgbfblog/article/details/7772153

https://www.cnblogs.com/Commence/p/5785912.html

http://blog.jobbole.com/91887/

https://www.cnblogs.com/huxiao-tee/p/4660352.html

https://blog.csdn.net/jnu_simba/article/details/11757473

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章