歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

Linux內存管理基礎教程

關於Linux的內存管理,本文分別從內核空間和用戶空間兩個視角來闡述

一、內核空間

1.1 頁

頁(page)是內核的內存管理基本單位。

==> linux/mm_types.h

struct page {
       page_flags_t flags;  頁標志符
       atomic_t _count;    頁引用計數
       atomic_t _mapcount;     頁映射計數
       unsigned long private;    私有數據指針
       struct address_space *mapping;    該頁所在地址空間描述結構指針,用於內容為文件的頁幀
       pgoff_t index;               該頁描述結構在地址空間radix樹page_tree中的對象索引號即頁號
       struct list_head lru;        最近最久未使用struct slab結構指針鏈表頭變量
       void *virtual;               頁虛擬地址
};
  • flags:頁標志包含是不是髒的,是否被鎖定等等,每一位單獨表示一種狀態,可同時表示出32種不同狀態,定義在<linux/page-flags.h>
  • _count:計數值為-1表示未被使用。
  • virtual:頁在虛擬內存中的地址,對於不能永久映射到內核空間的內存(比如高端內存),該值為NULL;需要事必須動態映射這些內存。

盡管處理器的最小可尋址單位通常為字或字節,但內存管理單元(MMU,把虛擬地址轉換為物理地址的硬件設備)通常以頁為單位處理。內核用struct page結構體表示每個物理頁,struct page結構體占40個字節,假定系統物理頁大小為4KB,對於4GB物理內存,1M個頁面,故所有的頁面page結構體共占有內存大小為40MB,相對系統4G,這個代價並不高。

1.2 區

內核把頁劃分在不同的區(zone)

總共3個區,具體如下:

區 描述 物理內存(MB) ZONE_DMA DMA使用的頁 <16 ZONE_NORMAL 可正常尋址的頁 16 ~896 ZONE_HIGHMEM 動態映射的頁 >896
  • 執行DMA操作的內存必須從ZONE_DMA區分配
  • 一般內存,既可從ZONE_DMA,也可從ZONE_NORMAL分配,但不能同時從兩個區分配;

1.3 頁分配與釋放

下面列舉所有的頁為單位進行連續物理內存分配,也稱為低級頁分配器:

頁分配函數 描述 alloc_pages(gfp_mask, order) 分配2^order個頁,返回指向第一頁的指針 alloc_pages(gfp_mask) 分配一頁,返回指向頁的指針 __get_free_pages(gfp_mask, order) 分配2^order個頁,返回指向其邏輯地址的指針 __get_free_pages(gfp_mask) 分配一頁,返回指向其邏輯地址的指針 get_zeroed_page(gfp_mask) 分配一頁,並填充內容為0,返回指向其邏輯地址的指針
  • get_zeroed_page:對於用戶空間,這個方法能保障系統敏感數據不會洩露
  • page_address: 把給定的頁轉換成邏輯地址
頁釋放函數 描述 __free_pages(page, order) 從page開始,釋放2^order個頁 free_pages(addr, order) 從地址addr開始,釋放2^order個頁 free_page(addr) 釋放addr所在的那一頁

1.4 字節分配與釋放

kmalloc,vmalloc分配都是以字節為單位

(1) kmalloc

void * kmalloc(size_t size, gfp_t flags)

該函數返回的是一個指向內存塊的指針,其內存塊大小至少為size,所分配的內存在物理內存中連續且保持原有的數據(不清零)

其中部分flags取值說明:

  • GFP_USER: 用於用戶空間的分配內存,可能休眠;
  • GFP_KERNEL:用於內核空間的內存分配,可能休眠;
  • GFP_ATOMIC:用於原子性的內存分配,不會休眠;典型原子性場景有中斷處理程序,軟中斷,tasklet等

kmalloc內存分配最終總是調用__get_free_pages 來進行實際的分配,故前綴都是GFP_開頭。 kmalloc分最多只能分配32個page大小的內存,每個page=4k,也就是128K大小,其中16個字節用來記錄頁描述結構。kmalloc分配的是常駐內存,不會被交換到文件中。最小分配單位是32或64字節。

kzalloc

kzalloc()等價於先用 kmalloc() 申請空間, 再用memset()來初始化,所有申請的元素都被初始化為0。

static inline void *kzalloc(size_t size, gfp_t flags)
{
    return kmalloc(size, flags | __GFP_ZERO); //通過或標志位__GFP_ZERO,初始化元素為0
}

(2) vmalloc

void * vmalloc(unsigned long size)

該函數返回的是一個指向內存塊的指針,其內存塊大小至少為size,所分配的內存是邏輯上連續的。

kmalloc不同,該函數乜有flags,默認是可以休眠的。

小結:

分配函數 區域 連續性 大小 釋放函數 優勢 kmalloc 內核空間 物理地址連續 最大值128K-16 kfree 性能更佳 vmalloc 內核空間 虛擬地址連續 更大 vfree 更易分配大內存 malloc 用戶空間 虛擬地址連續 更大 free  

1.5 slab層

slab分配器的作用:

  • 對於頻繁地分配和釋放的數據結構,會緩存它;
  • 頻繁分配和回收比如導致內存碎片,為了避免,空閒鏈表的緩存會連續的存放,已釋放的數據結構又會放回空閒鏈表,不會導致碎片;
  • 讓部分緩存專屬單個處理器,分配和釋放操作可以不加SMP鎖;

slab層把不同的對象劃分為高速緩存組,每個高速緩存組都存放不同類型的對象,每個對象類型對應一個高速緩存。kmalloc接口監理在slab層只是,使用一組通用高速緩存。

每個高速緩存都是用kmem_cache結構來表示

  • kmem_cache_crreate:創建高速緩存
  • kmem_cache_destroy: 撤銷高速緩存
  • kmem_cache_alloc: 從高速緩存中返回一個指向對象的指針
  • kmem_cache_free:釋放一個對象

實例分析: 內核初始化期間,/kernel/fork.c的fork_init()中會創建一個名叫task_struct的高速緩存; 每當進程調用fork()時,會通過dup_task_struct()創建一個新的進程描述符,並調用do_fork(),完成從高速緩存中獲取對象。

1.6 棧的靜態分配

當設置單頁內核棧,那麼每個進程的內核棧只有一頁大小,這取決於編譯時配置選項。 好處:

  • 可以減少每個進程內存的消耗;
  • 隨著機器運行時間的增加,尋找兩個未分配的、連續的頁越來越困難,物理內存碎片化不斷加重,那麼給每個新進程分配虛擬內存的壓力也增大;
  • 每個進程的調用鏈在自己的內核棧中,當單頁棧選項被激活時,中斷處理程序可獲得自己的棧;

任意函數必須盡量節省棧資源, 方法就是所有函數讓局部變量所占空間之和不要超過幾百字節。

1.7 高端內存的映射

高端內存中的頁不能永久地映射到內核地址空間。

  • kmap:把給定page結構映射到內核地址空間;
    • 當page位於低端內存,函數返回該頁的虛擬地址
    • 當page位於高端內存,建立一個永久映射,再返回地址
  • kunmap: 永久映射的數量有限,應通過kunmap及時解除映射
  • kmap_atomic: 臨時映射
  • kunmap_atomic: 解除臨時映射

1.8 每個CPU數據

  • alloc_percpu: 給系統的每個處理器分配一個指定類型對象的實例,以單字節對齊;
  • free_percpu: 釋放每個處理器的對象實例;
  • get_cpu_var: 返回一個執行當前處理器數據的特殊實例,同時會禁止內核搶占
  • put_cpu_var: 會重新激活內核搶占

使用每個CPU數據好處:

  • 減少了數據鎖定,每個CPU訪問自己CPU數據
  • 大大減少緩存失效,失效往往發生在一個處理器操作某個數據,而其他處理器緩存了該數據,那麼必須清理或刷新緩存。持續不斷的緩存失效稱為緩存抖動。

1.9 小結

分配函數選擇:

  1. 連續的物理頁,使用低級頁分配器 或kmalloc();
  2. 高端內存分配,使用alloc_pages(),返回page結構指針; 想獲取地址指針,應使用kmap(),把高端內存映射到內核的邏輯地址空間;
  3. 僅僅需要虛擬地址連續頁,使用vmalloc(),性能有所損失;
  4. 頻繁創建和撤銷大量數據結構,考慮建立slab高速緩存。

二、用戶空間

用戶空間中進程的內存,往往稱為進程地址空間。Linux采用虛擬內存技術

2.1 地址空間

每個進程都有一個32位或64位的地址空間,取決於體系結構。 一個進程的地址空間與另一個進程的地址空間即使有相同的內存地址,也彼此互不相干,對於這種共享地址空間的進程稱之為線程。一個進程可尋址4GB的虛擬內存(32位地址空間中),但不是所有虛擬地址都有權訪問。對於進程可訪問的地址空間稱為內存區域。每個內存區域都具有對相關進程的可讀、可寫、可執行屬性等相關權限設置。

內存區域可包含的對象:

  • 代碼段(text section): 可執行文件代碼
  • 數據段(data section): 可執行文件的已初始化全局變量(靜態分配的變量和全局變量)。
  • bss段:程序中未初始化的全局變量,零頁映射(頁面的信息全部為0值)。
  • 進程用戶空間棧的零頁映射(進程的內核棧獨立存在並由內核維護)
  • 每一個諸如C庫或動態連接程序等共享庫的代碼段、數據段和bss也會被載入進程的地址空間
  • 任何內存映射文件
  • 任何共享內存段
  • 任何匿名的內存映射(比如由malloc()分配的內存)

這些內存區域不能相互覆蓋,每一個進程都有不同的內存片段。

2.2 內存描述符

內存描述符由mm_struct結構體表示,

==> linux/sched.h

struct mm_struct
{
    struct vm_area_struct *mmap;
    rb_root_t mm_rb;
    ...
    atomic_t mm_users;
    atomic_t mm_count;

    struct list_head mmlist;
    ...
};
  • mm_users:代表正在使用該地址的進程數目,當該值為0時mm_count也變為0;
  • mm_count: 代表mm_struct的主引用計數,當該值為0說明沒有任何指向該mm_struct結構體的引用,結構體會被撤銷。
  • mmap和mm_rb:描述的對象都是相同的
    • mmap以鏈表形式存放, 利於高效地遍歷所有元素
    • mm_rb以紅黑樹形式存放,適合搜索指定元素
  • mmlist:所有的mm_struct結構體都通過mmlist連接在一個雙向鏈表中,該鏈表的首元素是init_mm內存描述符,它代表init進程的地址空間。

在進程的進程描述符(<linux/sched.h>中定義的task_struct結構體)中,mm域記錄該進程使用的內存描述符。故current->mm代表當前進程的內存描述符。

fork()函數 利用copy_mm函數復制父進程的內存描述符,子進程中的mm_struct結構體通過allcote_mm()從高速緩存中分配得到。通常,每個進程都有唯一的mm_struct結構體,即唯一的進程地址空間。

當子進程與父進程是共享地址空間,可調用clone(),那麼不再調用allcote_mm(),而是僅僅是將mm域指向父進程的mm,即 tsk->mm = current->mm。

相反地,撤銷內存是exit_mm()函數,該函數會進行常規的撤銷工作,更新一些統計量。

內核線程

  • 沒有進程地址空間,即內核線程對應的進程描述符中mm=NULL
  • 內核線程直接使用前一個進程的內存描述符,僅僅使用地址空間中和內核內存相關的信息
Copyright © Linux教程網 All Rights Reserved