天天看點

Linux核心設計與實作(12)---記憶體管理

記憶體管理,個人感覺應該是核心裡最複雜的一部分了,目前還沒做這方面相關的工作,是以沒打算深究,隻學點皮毛,搞懂點基本原理,以便更好了解OS的其他部分吧。

1.頁

核心把實體頁作為記憶體管理的基本機關(MMU是以頁為機關處理),體系結構不同,支援的頁大小也不盡相同,大多數32位體系結構是4KB頁,64位體系結構8KB頁。

核心用struct page結構表示系統中的每個實體頁(幾個重要域)

此處)折疊或打開

  1. struct page {
  2.     unsigned long flags;
  3.     atomic_t _count;
  4.     atomic_t _mapcount;
  5.     unsigned long private;
  6.     struct address_space *mapping;
  7.     pgoff_t index;
  8.     struct list_head lru;
  9.     void *virtual;
  10. };

flags:存放頁的狀态,比如頁是不是髒的,是否被鎖定在記憶體中等。

_count:存放頁的引用計數,當計數為-1時表示目前核心并沒有引用該頁。檢查引用計數用page_count()函數,當傳回0時表示頁空閑。

virtual:是頁的虛拟位址,有些記憶體(比如高端記憶體)并不永久映射到核心位址空間,這時域值為NULL,需要的時候,必須動态地映射這些頁。

page結構與實體頁相關,該結構對頁的描述隻是短暫的,核心僅僅用這個資料結構來描述目前時刻相關實體頁中存放的東西,目的在于描述實體記憶體本身,而不是其中資源。因為由于交換等原因,虛拟頁可能不再和同一個page結構相關聯。核心用這一結構來管理系統中所有的頁,每個實體頁都要配置設定一個這樣的結構。

2. 區

由于硬體限制,有些頁位于記憶體中特定的實體位址上,不能用于一些特定的任務,是以核心把頁劃分為不同的區(ZONE).

Linux必須處理如下兩種記憶體缺陷引起的記憶體尋址問題。

a.一些硬體隻能用在某些特定的記憶體位址來執行DMA。

b.一些體系結構的記憶體的實體尋址範圍比虛拟尋址範圍大得多,這樣就有一些記憶體不能永久地映射在核心空間上。

因為存在這些制約條件,Linux主要使用了四種區:

ZONE_DMA:這個區包含的頁能用來執行DMA操作。

ZONE_DMA32:和ZONE_DMA類似,該區包含的頁面可用來執行DMA操作,不同之處在于這些頁面隻能被32位裝置通路。在某些體系結構,該區比ZONE_DMA更大。

ZONE_NORMAL:這個區包含的都是能正常映射的頁。

ZONE_HIGHEM: 高端記憶體,其中的頁并不能永久地映射到核心位址空間。

區的實際使用是和體系結構相關的,某些體系結構記憶體在任何位址空間執行DMA都沒有問題。在X86-32每個區所占頁的清單如下

每個區都用struct zone表示,在

定義

此處)折疊或打開

  1. struct zone {
  2. unsigned long watermark[NR_WMARK];
  3. unsigned long lowmem_reserve[MAX_NR_ZONES];
  4. struct per_cpu_pageset pageset[NR_CPUS];
  5. spinlock_t lock;
  6.     struct free_area free_area[MAX_ORDER]
  7. spinlock_t lru_lock;
  8. struct zone_lru {
  9. struct list_head list;
  10. unsigned long nr_saved_scan;
  11. } lru[NR_LRU_LISTS];
  12. struct zone_reclaim_stat reclaim_stat;
  13. unsigned long pages_scanned;
  14. unsigned long flags;
  15. atomic_long_t vm_stat[NR_VM_ZONE_STAT_ITEMS];
  16. int prev_priority;
  17. unsigned int inactive_ratio;
  18. wait_queue_head_t *wait_table;
  19. unsigned long wait_table_hash_nr_entries;
  20. unsigned long wait_table_bits;
  21. struct pglist_data *zone_pgdat;
  22. unsigned long zone_start_pfn;
  23. unsigned long spanned_pages;
  24. unsigned long present_pages;
  25. const char *name;
  26. };

這個結構體很大,但是系統中隻有三個區,是以,也隻有三個這樣的結構。幾個重要域如下:

Lock域是一個自旋鎖,它防止結構被并發通路,這個域隻保護結構,而不保護駐留在這個區中的所有頁。

watermark 數組持有該區最小值,最低和最高水位值,核心使用水位為每個記憶體區設定合理的記憶體消耗基準。該水位随空閑記憶體的多少而變化。

name域是一個以NULL結束的字元串表示這個區的名字,核心啟動期間初始化這個值,三個區名字分别是”DMA”,”Normal” 和”HighMem”。

3. 獲得頁

核心提供的請求記憶體的接口,都是以頁為機關,定義于

(1)

此處)折疊或打開

  1. struct page * alloc_pages(gfp_t gfp_mask, unsigned int order)
  2. 配置設定2order(1<<order)個連續的實體頁,傳回一個指針,該指針指向第一個page結構體,出錯傳回NULL。
  3. void *page_address(struct page *page); //傳回指定實體頁目前所在的邏輯位址.
  4. 還可以直接調用
  5. unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order);
  6. 如果隻需要申請一個頁,可以用
  7. struct page *alloc_page(gfp_t gfp_mask);
  8. unsigned long __get_free_page(gfp_t gfp_mask);//這兩個函數功能與上面相同,隻是order值為0.

(2)如果需要讓傳回的頁内容全為0,用這個函數

unsigned long get_zeroed_page(gfp_t gfp_mask) ;//配置設定傳回一個頁,邏輯位址,并且内容全清零

底層配置設定頁方法清單:

(3)釋放頁

釋放頁記憶體函數:

此處)折疊或打開

  1. void __free_pages(struct page *page, unsigned int order);
  2. void free_pages(unsigned long addr, unsigned int order);
  3. void free_page(unsigned long addr);

分别與上述配置設定函數配套使用,釋放頁要謹慎,隻能釋放屬于你的頁。錯誤釋放,容易導緻系統崩潰。

配置設定記憶體應該做錯誤檢查,若失敗,應該做相應處理;在程式開始就先進行記憶體配置設定是有意義的。

4. kmalloc()

用來獲得以位元組為機關的一塊記憶體,所配置設定記憶體在實體上是連續的,在

定義

此處)折疊或打開

  1. void *kmalloc(size_t size, gfp_t flags);

gfp_mask标志,可分為三類:行為修飾符、區修飾符、類型

(1).行為修飾符

表示核心應當如何配置設定所需記憶體,某些特定情況下,隻能使用某些特定的方法配置設定記憶體。比如中斷處理程式中要求配置設定記憶體函數不能睡眠。

定義,

中包含這個頭檔案。

可以同時指定這些配置設定标志,比如

ptr = kmalloc(size, __GFP_WAIT|__GFP_IO|__GFP_FS);

(最終調用alloc_pages())在配置設定時可以阻塞,可以執行I/O,還可以執行檔案系統操作。

大多數配置設定都會指定這些修飾符,但一般不是直接指定,而是采用類型标志。

(2).區修飾符

表示從哪兒配置設定記憶體,核心把實體記憶體分為多個區,每個區用于不同目的。

ZONE_NORMAL開始,這樣確定其他區在需要時有足夠的空閑頁。

不能給_get_free_pages()或kmalloc()指定ZONE_HIGHMEM,因為這兩個函數傳回都是邏輯位址,而不是page結構,高端記憶體可能還沒有映射到核心的虛拟位址空間,是以根本沒有邏輯位址。隻有alloc_pages()才能配置設定高端記憶體。大多數情況下ZONE_NORMAL足矣。

 (3).類型

組合行為修飾符和區修飾符,将各種可能用到的組合歸納為不同類型,簡化了修飾符的使用。

核心趨向于使用正确的類型标志。

GFP_KERNEL:核心中最常用标志,可能引起睡眠,普通優先級,隻用在可以重新安全排程程序上下文中(沒有持有鎖),這個标志對核心如何擷取請求的記憶體沒有任何限制,可以讓調用者睡眠、交換、重新整理一些頁到硬碟等,是以配置設定成功可能性很高。

GFP_ATOMIC: 不能睡眠,配置設定成功相對機會較小(特别記憶體短缺時)。

GFP_NOIO和GFPNOFS:可能引起阻塞,用在某些低級塊I/O或檔案系統中,核心使用較少。

GFP_DMA:表示配置設定器必須滿足從ZONE_DMA進行配置設定,一般與GFP_ATOMIC和GFP_KERNEL結合使用。

标志常用情形清單

kfree():在

定義

void kfree(const void *ptr);

配對使用,釋放kmalloc()配置設定的記憶體。釋放屬于核心其他部分的記憶體,可能導緻嚴重後果,但kfree(NULL)是安全的。

5.vmalloc()

vmalloc()與kmalloc()工作方式類似,不同點:

vmalloc()配置設定的記憶體:虛拟位址連續,實體位址不一定連續。

kmalloc()配置設定的記憶體:虛拟位址連續,實體位址也連續。

盡管隻有某些情況下才需要實體上連續的記憶體塊,但核心多用kmalloc()來配置設定記憶體,主要是基于性能的考慮:vmalloc()函數為了把實體上不連續的頁轉換為虛拟位址空間上連續的頁,必須專門建立頁表項,vmalloc()獲得的頁必須一個一個進行映射,這會導緻比直接記憶體映射大得多的TLB抖動。

vmalloc()在

聲明

void *vmalloc(unsigned long size);

該函數傳回一個指針,指向邏輯上連續的一塊記憶體區,大小至少為size;發生錯誤時函數傳回NULL。

函數可能睡眠,是以不能在中斷上下文 ,也不能在不允許阻塞的情況下進行調用。

釋放vmalloc()配置設定的頁

void vfree(const void *addr);

該函數也可以睡眠,沒有傳回值。

6.alsb層

為了便于資料的頻繁配置設定和回收,常常會用到空閑連結清單,空閑連結清單包含可供使用的、已經配置設定好的資料結構塊。當需要一個新的資料結構執行個體時,從空閑連結清單抓取一個,而不需要配置設定記憶體,當不再使用這個資料結構執行個體時,把它放回空閑連結清單,而不是釋放它。實際上,空閑連結清單就相當于對象高速緩存。

而核心是不知道任何空閑連結清單存在的,是以提供了一個slab配置設定器(slab層)來扮演通用資料結構緩存層的角色。

slab配置設定器基本設計思想:

①頻繁使用的資料結構也會頻繁配置設定和釋放,應當緩存它們;

②頻繁配置設定和回收會導緻記憶體碎片,空閑連結清單的緩存會連續存放,避免碎片;

③回收的對象可以立即投入下一次使用,是以對于頻繁配置設定和釋放,空閑連結清單能夠提高其性能;

④如果配置設定器知道對象大小、頁大小和總的高速緩存的大小這些概念,它會做出更明智的決策;

⑤如果讓部分緩存專屬于單個處理器(系統中每個處理器獨立),那麼配置設定和釋放就可以在不加SMP鎖的情況下進行;

⑥如果配置設定器是與NUMA相關的,它就可以從相同的記憶體節點為請求者進行配置設定;

⑦對存放的對象進行着色,以防止多個對象映射到相同的高速緩存行;

6.1 slab層的設計

slab層把不同的對象劃分為所謂高速緩存組,其中每個高速緩存組都存放不同類型的對象,每種對象類型對應一個高速緩存。比如一個高速緩存存放程序描述符(task_struct),另一個高速緩存存放索引節點對象(struct inode)。kmalloc()接口建立在slab層之上,使用了一組通用高速緩存。

這些高速緩存又被劃分為slab,slab由一個或多個實體上連續的頁組成。一般情況下,一個slab僅僅由一頁組成。每個高速緩存可以有多個slab。

每個slab都處于三種狀态之一:滿、部分滿或空;當核心需要一個新對象時,先從部分滿的slab進行配置設定,如果沒有部分滿的slab,就從空slab配置設定,如果沒有空的slab,就要建立一個slab,這種政策能減少碎片。

看一個執行個體,inode結構(磁盤索引節點在記憶體中的展現)會頻繁地建立和釋放,是以,用slab配置設定器來管理很合适。struct inode由inode_cachep高速緩存進行配置設定。

這種高速緩存由一個或多個slab組成,每個slab包含盡可能多的struct inode對象,當核心請求配置設定一個新的inode結構時,核心從部分滿或空的slab傳回一個指向已配置設定但未使用的inode結構的指針。當核心用完inode對象後,slab配置設定器就把該對象标記為空閑,高速緩存、slab及對象的關系:

每個高速緩存都使用kmem_cache結構表示,其包含一個kmem_list3結構,這個結構包含三個連結清單:slabs_full, slabs_partial, slabs_empty.這些連結清單包含高速緩存中所有的slab。

而slab用struct slab來描述:

此處)折疊或打開

  1. struct slab {
  2.     struct list_head list; //滿、部分滿或空連結清單
  3.     unsigned long colouroff; //slab着色的偏移量
  4.     void *s_mem;        / //slab的第一個對象
  5.     unsigned int inuse;     //slab中已配置設定的對象數
  6.     kmem_bufctl_t free; //第一個空閑對象,如果有的話
  7.     unsigned short nodeid;
  8. };

slab描述符在slab之外配置設定,若空間足夠也可以把描述符放在slab裡面。

slab配置設定器可以建立新的slab,通過__get_free_pages()低級核心頁配置設定器進行的

忽略與NUMA相關的代碼,一個簡單的kmem_getpages()函數

此處)折疊或打開

  1. static inline void * kmem_getpages(struct kmem_cache *cachep, gfp_t flags)
  2. {
  3.     void *addr;
  4.     flags |= cachep->gfpflags;
  5.     addr = (void*) __get_free_pages(flags, cachep->gfporder);
  6.     return addr;
  7. }

接着調用kmem_freepages()釋放記憶體;

slab配置設定器的關鍵就是避免頻繁配置設定和釋放頁,隻有當給定高速緩存中沒有空的slab時,才會調用頁配置設定函數;隻有當記憶體變得緊缺,系統試圖釋放出更多記憶體以供使用,或者當高速緩存顯式地被撤銷時才會釋放記憶體。

slab層的管理,就是在每個高速緩存的基礎上,通過一組接口來建立和撤銷新的高速緩存,并且在緩存中配置設定和釋放對象,高速緩存及其内的slab複雜管理完全由slab層的内部機制來處理。

6.2 slab配置設定器的接口

(1)建立新的高速緩存

此處)折疊或打開

  1. struct kmem_cache *
  2. kmem_cache_create (const char *name, size_t size, size_t align,
  3.     unsigned long flags, void (*ctor)(void *))

name:高速緩存名字, 在/proc/slabinfo可以看到;

size:高速緩存中每個元素的大小;

align: slab内第一個對象的偏移,確定業内對齊,一般用0即可;

flags: SLAB标志,可選參數,控制高速緩存行為,0表示沒有特殊行為,可以一個或多個标志進行或運作;

SLAB_HWCACHE_ALIGN :該标志指令slab層把一個slab内的所有對象按高速緩存行對齊,這可以提高性能,但以增加記憶體開銷為代價,防止“錯誤的共享”。

SLAB_POISON :是slab層用已知的值(a5a5a5a5)填充slab,就是所謂“中毒”,有利于對位初始化記憶體的通路。

SLAB_RED_ZONE :在已配置設定的記憶體周圍插入“紅色警戒區”以探測緩沖越界。

SLAB_PANIC:标志當配置設定失敗時提醒slab層。這在要求配置設定隻能成功的時候非常有用,比如在系統初啟動時配置設定一個VMA結構的高速緩存。

SLAB_CACHE_DMA:這個标志配置設定的slab層使用可以執行DMA的記憶體給每個slab配置設定空間。隻有配置設定對象用于DMA,并且必須駐留在ZONE_DMA區時才用此标志。

ctor:高速緩存的構造函數,隻有再新的頁追加到高速緩存時,構造函數才被調用。實際上Linux齧合的高速緩存不使用構造函數,指派NULL即可。

kmem_cache_create()在成功時傳回一個指向所建立高速緩存的指針,否則傳回NULL;

這個函數可能會睡眠,是以不能在中斷上下文中使用。

要撤銷一個高速緩存,調用

int kmem_cache_destroy(struct  kmem_cache *cachep);

這個函數通常在子產品登出時調用,它也會睡眠,成功傳回0,失敗傳回非零。調用該函數之前,必須確定:

①高速緩存中的所有slab都必須為空

②在調用kmem_cache_destroy()過程中,之後都不能再通路這個高速緩存。

(2)從緩存中擷取對象:

建立高速緩存之後,可以通過下列函數擷取對象

void * kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);

從給定的高速緩存cachep中,傳回一個指向對象的指針,如果高速緩存的所有slab都沒有空閑的對象,那麼slab層必須通過kmem_getpages()擷取新的頁,flags的值傳遞給_get_free_pages()。一般用到的是GFP_KERNEL或GFP_ATOMIC。

最後,釋放一個對象,把它傳回給原先的slab,可以用:

void kmem_cache_free(struct kmem_cache *cachep, void *objp);

這樣就能把cachep中的對象objp标記為空閑。

(3)slab配置設定器的使用執行個體—task_strcut

首先,核心用一個全局變量存放指向task_struct高速緩存的指針:

static struct kmem_cache *task_struct_cachep;

核心初始化期間,在定義與kernel/fork.c的fork_init()中會建立高速緩存:

此處)折疊或打開

  1. /* create a slab on which task_structs can be allocated */
  2.     task_struct_cachep =
  3.         kmem_cache_create("task_struct", sizeof(struct task_struct),
  4.             ARCH_MIN_TASKALIGN, SLAB_PANIC | SLAB_NOTRACK, NULL);

建立一個名為task_struct的高速緩存,其中存放的就是類型為struct task_struct的對象,該對象被建立後存放在slab中偏移量為ARCH_MIN_TASKALIGN位元組的地方,ARCH_MIN_TASKALIGN的值與體系結構相關,通常定義為L1高速緩存的位元組大小。沒有構造函數,不用檢查傳回值,因為設定了SLAB_PANIC标志,如果配置設定失敗,slab配置設定器就調用panic()函數。

每當程序調用fork()時,一定會建立一個新的程序描述符,在dup_task_struct()中完成:

此處)折疊或打開

  1. # define alloc_task_struct()    kmem_cache_alloc(task_struct_cachep, GFP_KERNEL)
  2. struct task_struct *tsk;
  3. tsk = alloc_task_struct();

程序執行完後,如果沒有子程序在等待的話,它的程序描述符就會被釋放,并傳回給task_struct_cachep slab高速緩存,這在free_task_struct()中執行,tsk是現有程序:

kmem_cache_free(task_struct_cachep, tsk);

由于程序描述符是核心的核心組成部分,時刻都要用到,是以task_struct_cachep高速緩存絕對不會被撤銷掉。

slab層負責記憶體緊缺情況下所有底層的對齊、着色、配置設定、釋放和回收等。如果要頻繁建立很多相同類型的對象,那麼就應該考慮使用slab高速緩存,也就是說,不要自己去實作空閑連結清單。

7. 在棧上靜态配置設定

在使用者空間,以前讨論的那些配置設定的例子,有不少可以在棧上發生,因為使用者空間能夠奢侈地負擔起非常大的棧,而且棧空間可以動态增長,但是核心,卻不能這麼奢侈—核心棧小且固定。

每個程序的核心棧大小既依賴體系結構,也與編譯時的選項有關。

(1) 單頁核心棧

每個程序的整個調用鍊必須放在自己的核心棧中,中斷處理程式也曾經使用它們所中斷的程序的核心棧,這樣中斷處理也放在核心棧中,這就要求更加嚴格的限制。

2.6核心引入一個選項設定單頁核心棧,當激活這個選項時,中斷程式不再使用程序核心棧,而是實作了一個中斷棧。

核心棧是1頁或者2頁,取決于編譯時配置,在任何情況下,無限制的遞歸和alloc()是不允許的。

(2) 在棧上光明正大的工作

在任意一個函數中,都必須盡量節省棧資源,局部變量所占之和不要超過幾百位元組,在棧上進行大量的靜态配置設定(比如大型數組或大型結構體)都是很危險的。

棧溢出時悄無聲息,會覆寫掉thread_info結構和鄰堆棧末端的東西。

最好的情況是引起崩潰,最壞的情況悄無聲息的破壞資料。

是以大塊記憶體的配置設定最好是的動态配置設定。

8. 高端記憶體的映射

在高端記憶體中的頁不能永久地映射到核心位址空間上,是以通過alloc_pages()以__GFP_HIGHMEM标志獲得的頁不可能有邏輯位址。在X86體系結構,高端記憶體中的頁被映射到3GB~4GB。

(1) 永久映射

一個給定的page結構映射到核心虛拟位址空間,使用在

定義的函數:

void *kmap(struct  page *page);

這個函數在高端記憶體低端記憶體上都能用,如果page結構對應的是低端記憶體中的一頁,函數傳回該頁的虛拟位址,如果頁位于高端記憶體,則會建立一個永久映射,再傳回位址,這個函數可以睡眠,是以隻能用在程序上下文中。

因為允許永久映射的數量是有限的,當不再需要高端記憶體時,應該解除映射

void kunmap(struct page *page);

(2) 臨時映射

當必須建立一個映射而目前的上下文又不能睡眠時,核心提供了臨時映射,也就是所謂的原子映射。

此處)折疊或打開

  1. void *kmap_atomic(struct page *page, enum km_type type);
  2. type是下列枚舉類型之一,在<asm/kmap_types.h>定義
  3. enum km_type {
  4.     KM_BOUNCE_READ,
  5.     KM_SKB_SUNRPC_DATA,
  6.     KM_SKB_DATA_SOFTIRQ,
  7.     KM_USER0,
  8.     KM_USER1,
  9.     KM_BIO_SRC_IRQ,
  10.     KM_BIO_DST_IRQ,
  11.     KM_PTE0,
  12.     KM_PTE1,
  13.     KM_IRQ0,
  14.     KM_IRQ1,
  15.     KM_SOFTIRQ0,
  16.     KM_SOFTIRQ1,
  17.     KM_L1_CACHE,
  18.     KM_L2_CACHE,
  19.     KM_TYPE_NR
  20. };

這個函數不會阻塞,是以可以用在中斷上下文和其他不能重新排程的地方。它也禁止搶占,這是有必要的,因為映射對每個處理器都是唯一的。

通過下列函數取消映射:

void kunmap_atomic(void *kvaadr, enum km_type  type);//這個函數也不會阻塞。

9.每個CPU的配置設定

支援SMP的現代作業系統使用每個CPU上的資料,對于給定的處理器其資料是唯一的。一般來說,每個CPU的資料存放在一個數組中,數組的每一項對應系統中一個處理器。比如可以聲明如下資料:

unsigned int my_percpu[NR_CPUS];

通路方式如下:

此處)折疊或打開

  1. int cpu;
  2. cpu = get_cpu(); //擷取目前處理器,并且禁止核心搶占
  3. my_percpu[cpu]++;
  4. printk(“my_percpu on cpu=%d is %lu\n”,cpu,my_percpu[cpu]);
  5. put_cpu(); //激活核心搶占

由于資料對目前處理器是唯一的,對于多處理器不存在并發問題,不需要加鎖;另,由于get_cpu()禁止核心搶占,也不存在競争條件。是以,這個資料操作是安全的。

10.新的每個CPU接口

2.6核心為了友善建立和操作CPU資料,引進了新的操作接口,稱作percpu。該接口歸納了CPU資料操作行為,簡化了建立和操作每個CPU的資料。

聲明

10.1 編譯時的每個CPU資料

DEFINE_PER_CPU(type,  name);

這個宏為系統中的每個處理器都建立一個類型為type,名字為name的變量執行個體。如果在别處申明,以防編譯時警告,可以用

此處)折疊或打開

  1. DECLARE_PER_CPU(type, name);
  2. 可用get_cpu_var()和put_cpu_var()操作變量。
  3. get_cpu_var(name)++; //get_cpu_var()傳回目前處理器上的指定變量,同時禁止搶占
  4. put_cpu_var(name); //操作完成,重新激活核心搶占
  5. 還可以獲得别的處理器上的每個CPU資料;
  6. per_cpu(name, cpu)++;//該函數不會禁止核心搶占,也不會提供任意形式的鎖保護。

這些編譯時每個CPU資料的例子并不能在子產品内使用,因為連結程式實際上将它們建立在一個唯一的可執行段中(.data.percpu)。

10.2 運作時的每個CPU資料

運作時為每個處理器建立所需記憶體的執行個體,原型在

此處)折疊或打開

  1. void *alloc_percpu(type); //該宏為每個處理器配置設定一個type型資料,按單位元組對齊
  2. void * __alloc_percpu(size_t size, size_t align);//為每個處理器配置設定一個size大小對象,按align對齊
  3. void free_percpu(const void *); //釋放所有處理器上指定的每個CPU資料
  4. alloc_percpu()實際上是對__alloc_percpu()的一個封裝宏,比如:
  5. struct rabid_cheetah = alloc_percpu(struct rabid_cheetah);
  6. 等價于
  7. struct rabid_cheetah = __alloc_percpu(sizeof(struct rabid_cheetah), __alignof__(struct rabid_cheetah));
  8. __alignof__()是gcc的一個功能,傳回指定類型或lvalue所需的對齊位元組數。
  9. alloc_percpu()傳回一個指針,它用來間接引用動态建立的每個CPU資料,核心提供兩個宏來利用指針擷取每個CPU資料:
  10. get_cpu_var(ptr); //傳回一個void類型指針,該指針指向處理器的ptr的拷貝,禁止核心搶占
  11. put_cpu_var(ptr); //重新激活核心搶占
  12. 一個完整的例子:
  13. void *percpu_ptr;
  14. unsigned long *foo;
  15. percpu_ptr = alloc_percpu(unsigned long);
  16. if (!ptr)
  17.     …//出錯處理
  18. foo = get_cpu_var(percpu_ptr);
  19. /*操作foo…*/
  20. put_cpu_var(percpu_ptr);

11. 使用每個CPU資料的原因

使用每個CPU資料的好處:

①減少了資料鎖定,你需要確定本地處理器隻會通路它自己的唯一資料,系統本身并不存在任何措施禁止你從事欺騙活動;

②可以大大減少緩存失效;

每個CPU資料在中斷上下文或程序上下文中使用都很安全,但注意,不能在通路每個CPU資料過程中睡眠,否則,醒來後可能已經到了其他處理器上。

12.配置設定函數的選擇

如果需要連續的實體頁,可以使用某個低級頁配置設定器或kmalloc()。這是核心常用配置設定方式,兩個最常用的标志是GFP_ATOMIC和GFP_KERNEL。

如果想從高端記憶體進行配置設定,就是要alloc_pages(),它傳回一個指向struct page結構的指針,而不是一個指向某個邏輯位址的指針。因為高端記憶體可能沒有被映射,通路它唯一方法是通過相應的struct page結構,為了獲得真正的指針,應該調用kmap(),吧高端記憶體映射到核心的邏輯位址空間。

如果不需要實體上連續的頁,僅需要虛拟位址上連續的頁,那就是用vmalloc(),但vmalloc()相對kmalloc()來說,有一定性能損失。

繼續閱讀