国产精品久久久aaaa,日日干夜夜操天天插,亚洲乱熟女香蕉一区二区三区少妇,99精品国产高清一区二区三区,国产成人精品一区二区色戒,久久久国产精品成人免费,亚洲精品毛片久久久久,99久久婷婷国产综合精品电影,国产一区二区三区任你鲁

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Linux內核伙伴系統內存申請函數詳解:從原理到實戰

jf_44130326 ? 來源:Linux1024 ? 2026-02-10 16:58 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

Linux內核中,內存管理是整個系統穩定運行的基石,而伙伴系統(Buddy System作為內核物理內存分配的核心機制,更是驅動開發、內核模塊開發的必備知識點。它通過"2的冪次分配粒度"巧妙解決了外碎片問題,而我們申請內核內存的所有操作,最終都要通過伙伴系統提供的核心函數來完成。

今天這篇文章,我們就來全面拆解伙伴系統的內存申請函數:從底層核心到上層封裝,從參數解析到實戰示例,再到可視化流程,幫你徹底搞懂"內核內存怎么申請"

一、前言:為什么要關注伙伴系統?

內核內存和用戶態內存完全是兩套管理體系:用戶態有malloc/free,但內核態不能直接用——內核需要更高效、更安全的內存分配方式,而伙伴系統就是為此而生。

?解決外碎片:傳統連續分配會產生大量"無法利用的小空閑塊",伙伴系統通過固定2^order的分配粒度,讓空閑塊可拆分、可合并,從根源減少外碎片;

?支撐內核核心功能:進程棧、內核模塊、設備緩沖區等所有內核態內存需求,都依賴伙伴系統分配;

?開發必備技能:驅動或內核模塊中,只要涉及內存操作,就必須掌握伙伴系統的申請/釋放函數。

在講函數之前,先快速回顧下伙伴系統的核心原理,幫你建立認知基礎。

二、伙伴系統核心原理速覽

伙伴系統的設計思想非常簡潔,核心圍繞3個關鍵點:

1.分配粒度:物理內存被劃分為"頁塊",塊大小必須是2^order個物理頁(order稱為"分配階")。比如order=0對應1頁,order=1對應2頁,order=3對應8頁,最大orderMAX_ORDER定義(默認11,即最大2048= 8MB);

2.伙伴塊定義:兩個大小相同、物理地址連續、且來自同一父塊的頁塊,互為"伙伴"。比如order=1的塊(2頁)拆分后,會生成兩個order=0的伙伴塊;

3.分配/釋放邏輯

?分配:先找對應order的空閑塊,找到直接分配;找不到就拆分更高order的空閑塊,直到得到目標大小;

?釋放:釋放的塊會檢查是否有空閑伙伴,若有則合并為更高order的塊,逐步歸還到空閑鏈表。

理解了這3點,再看后續的函數就會豁然開朗——所有申請函數的本質,都是向伙伴系統請求"指定order的連續物理頁塊"

三、伙伴系統核心申請函數詳解

伙伴系統提供了一套"底層核心+上層封裝"的函數體系,不同函數適用于不同場景。我們從底層到上層逐一拆解:

3.1底層核心:__alloc_pages ()

__alloc_pages()是伙伴系統最底層的內存申請函數,所有其他申請函數最終都會調用它。可以說,它是"內核內存分配的入口"

函數原型

structpage*__alloc_pages(gfp_tgfp_mask,unsignedintorder);

核心作用

直接向伙伴系統申請2^order個連續物理頁,返回對應物理頁的struct page結構體指針(注意:返回的是物理頁描述符,不是虛擬地址)。

參數解析

參數名

作用說明

gfp_mask

分配策略標志(核心參數!),告訴內核"怎么分配內存"(能否睡眠、用哪種內存域等)

order

分配階(0≤order),表示申請2^order個連續物理頁

關鍵補充:gfp_mask常用取值

gfp_mask是內核內存分配的"策略開關",不同場景必須選對,否則會導致系統異常:

?GFP_KERNEL:最常用,允許睡眠(可觸發頁回收),適用于進程上下文(比如驅動的probe函數、內核線程);

?GFP_ATOMIC:不允許睡眠、不允許觸發頁回收,適用于中斷上下文(比如中斷處理函數);

?GFP_DMA:僅從DMA內存域分配(適用于需要DMA傳輸的設備緩沖區);

?GFP_HIGHUSER:允許從高端內存分配(適用于大內存場景)。

返回值

?成功:返回第一個物理頁的struct page指針;

?失敗:返回NULL(表示沒有找到滿足條件的連續物理頁)。

特點

?底層裸函數,沒有參數合法性檢查(比如order超過MAX_ORDER也會嘗試分配);

?不建議直接調用(風險高),僅內核核心代碼使用;

?返回的是page結構體,需要手動轉換為虛擬地址才能訪問(用page_to_virt())。

3.2常用封裝:alloc_pages ()

alloc_pages()是對__alloc_pages()的上層封裝,也是驅動開發中最常用的"page級分配函數"

函數原型

structpage*alloc_pages(gfp_tgfp_mask,unsignedintorder);

核心作用

__alloc_pages()功能一致,但增加了參數合法性檢查,更安全。

__alloc_pages ()的區別

特性

__alloc_pages()

alloc_pages()

參數檢查

有(比如檢查order范圍)

適用場景

內核核心代碼

驅動/內核模塊開發

安全性

適用場景

需要直接操作struct page結構體的場景:

?設置頁屬性(比如標記為只讀、可緩存);

?映射高端內存(高端內存無法直接訪問,需要通過page結構體建立映射);

?管理物理頁的引用計數。

3.3虛擬地址直達:__get_free_pages ()

如果不需要操作page結構體,只想直接獲取可訪問的虛擬地址,__get_free_pages()是最優選擇——它幫我們完成了"申請page +轉換虛擬地址"的全過程。

函數原型

unsignedlong__get_free_pages(gfp_tgfp_mask,unsignedintorder);

核心作用

申請2^order個連續物理頁,并返回對應的內核虛擬地址(直接可讀寫)。

內部邏輯

// 偽代碼:__get_free_pages()的實現邏輯unsignedlong__get_free_pages(gfp_tgfp_mask,unsignedintorder) { structpage*page =alloc_pages(gfp_mask, order); // 調用alloc_pages() if(!page)   return0; // 失敗返回0(內核虛擬地址不會是0) return(unsignedlong)page_to_virt(page); // 轉換為虛擬地址}

參數與返回值

?參數和alloc_pages()完全一致;

?返回值:成功返回內核虛擬地址(非0),失敗返回0(注意:不是NULL,因為返回值是unsigned long)。

適用場景

大部分驅動開發場景:比如申請設備緩沖區、臨時存儲數據等,直接用虛擬地址讀寫即可,無需關心物理頁細節。

3.4清零內存:get_zeroed_page ()

如果申請的內存需要初始化為0(避免臟數據影響),get_zeroed_page()是專用函數——它是__get_free_pages()"清零版本"

函數原型

unsignedlongget_zeroed_page(gfp_tgfp_mask);

核心作用

申請1頁(order=0)內存,并將整個頁面清零,返回內核虛擬地址。

內部邏輯

// 偽代碼:get_zeroed_page()的實現邏輯unsignedlongget_zeroed_page(gfp_tgfp_mask){ unsignedlongaddr = __get_free_pages(gfp_mask | __GFP_ZERO,0); // __GFP_ZERO標志會讓內核在分配時自動清零 returnaddr;}

適用場景

需要"干凈內存"的場景:比如存放配置結構體、用戶數據拷貝緩沖區等,避免未初始化的臟數據導致邏輯錯誤。

3.5簡化變體:alloc_page ()__get_free_page ()

為了方便"申請1頁內存"的場景,內核提供了兩個簡化函數(本質是宏定義):

?alloc_page(gfp_mask)=alloc_pages(gfp_mask, 0)(申請1頁,返回page指針);

?__get_free_page(gfp_mask)=__get_free_pages(gfp_mask, 0)(申請1頁,返回虛擬地址)。

四、實戰示例:函數怎么用?

光說不練假把式,我們用3個實際示例,演示核心函數的使用(基于Linux內核5.4,可直接編譯為內核模塊)。

示例1__get_free_pages ()分配內存(最常用場景)

#include#include#include#includeMODULE_LICENSE("GPL");MODULE_DESCRIPTION("__get_free_pages() Example");#defineALLOC_ORDER 1 // 申請2^1=2頁內存#defineALLOC_SIZE (PAGE_SIZE << ALLOC_ORDER) ?// 總大小=2*4096=8192字節staticunsignedlongvirt_addr; // 保存分配的虛擬地址// 模塊加載函數(進程上下文,可用GFP_KERNEL)staticint__initfree_pages_init(void){ // 申請2頁內存,策略GFP_KERNEL(可睡眠)  virt_addr = __get_free_pages(GFP_KERNEL, ALLOC_ORDER); if(!virt_addr) { // 檢查分配結果   printk(KERN_ERR"Failed to allocate memory with __get_free_pagesn");   return-ENOMEM; // 分配失敗,模塊加載失敗  } // 向分配的內存寫入數據(直接用虛擬地址訪問) sprintf((char*)virt_addr,"Buddy System: Allocate %d pages, size %d bytes",      (1<< ALLOC_ORDER), ALLOC_SIZE);  // 打印日志(dmesg查看) printk(KERN_INFO"Allocated virtual address: 0x%lxn", virt_addr); printk(KERN_INFO"Data in memory: %sn", (char*)virt_addr); return0;}// 模塊卸載函數(釋放內存)staticvoid__exitfree_pages_exit(void){ if(virt_addr) { // 確認內存已分配   free_pages(virt_addr, ALLOC_ORDER); // 對應__get_free_pages()的釋放函數   printk(KERN_INFO"Memory freed successfullyn");  }}module_init(free_pages_init);module_exit(free_pages_exit);

編譯運行步驟

1.編寫Makefile

obj-m += buddy_demo1.oall:  make -C /lib/modules/$(shelluname -r)/build M=$(PWD)modulesclean:  make -C /lib/modules/$(shelluname -r)/build M=$(PWD)clean

1.編譯:make

2.加載模塊:sudo insmod buddy_demo1.ko

3.查看日志:dmesg | grep "Buddy System"

4.卸載模塊:sudo rmmod buddy_demo1

預期輸出

[12345.678901] Allocatedvirtualaddress:0xffff88800abc0000[12345.678905] Datainmemory: Buddy System: Allocate2pages, size8192bytes[12345.678907] Memory freed successfully

示例2alloc_pages ()操作page結構體

#include#include#include#includeMODULE_LICENSE("GPL");MODULE_DESCRIPTION("alloc_pages() Example");staticstructpage*page_ptr;staticunsignedlongvirt_addr;staticint__initalloc_pages_init(void){ // 申請1頁內存,返回page結構體指針  page_ptr =alloc_pages(GFP_KERNEL,0); if(!page_ptr) {   printk(KERN_ERR"Failed to allocate page with alloc_pagesn");   return-ENOMEM;  } // 操作page結構體:設置頁為只讀(通過page屬性) set_bit(PG_ro, &page_ptr->flags); printk(KERN_INFO"Allocated page: frame number = %lun",page_to_pfn(page_ptr)); // 轉換為虛擬地址并寫入數據  virt_addr = (unsignedlong)page_to_virt(page_ptr); sprintf((char*)virt_addr,"Page frame %lu is read-only",page_to_pfn(page_ptr)); printk(KERN_INFO"Virtual address: 0x%lx, Data: %sn", virt_addr, (char*)virt_addr); return0;}staticvoid__exitalloc_pages_exit(void){ if(page_ptr) {    __free_pages(page_ptr,0); // 對應alloc_pages()的釋放函數   printk(KERN_INFO"Page freed successfullyn");  }}module_init(alloc_pages_init);module_exit(alloc_pages_exit);

示例3get_zeroed_page ()分配清零內存

#include#include#includeMODULE_LICENSE("GPL");MODULE_DESCRIPTION("get_zeroed_page() Example");staticunsignedlongzero_addr;staticint__initzero_page_init(void){ // 申請1頁清零內存  zero_addr =get_zeroed_page(GFP_KERNEL); if(!zero_addr) {   printk(KERN_ERR"Failed to allocate zeroed pagen");   return-ENOMEM;  } // 驗證清零:直接讀取內存,確認初始值為0 printk(KERN_INFO"Zeroed page address: 0x%lxn", zero_addr); printk(KERN_INFO"Initial value (first byte): %d (should be 0)n",     *(unsignedchar*)zero_addr); // 寫入數據  *(char*)zero_addr ='A'; printk(KERN_INFO"After writing 'A', value: %cn", *(char*)zero_addr); return0;}staticvoid__exitzero_page_exit(void){ if(zero_addr) {   free_pages(zero_addr,0); // get_zeroed_page()用free_pages()釋放   printk(KERN_INFO"Zeroed page freedn");  }}module_init(zero_page_init);module_exit(zero_page_exit);

五、內存申請流程可視化(流程圖)

5.1伙伴系統整體分配流程

wKgZO2kXJXuAa_PwAASMpMJBbYc948.png

5.2 __alloc_pages ()內部核心流程

wKgZO2kXJXuAMJY_AAK_HMsb3dU170.png

六、關鍵注意事項(避坑指南)

1.order不能超范圍:必須滿足0 ≤ order < MAX_ORDER(默認MAX_ORDER=11),否則分配必失敗;

2.gfp_mask選對場景

?中斷上下文、原子操作中,必須用GFP_ATOMIC(不能睡眠);

?進程上下文(如probe、內核線程),優先用GFP_KERNEL(可睡眠,分配成功率更高);

1.必須檢查返回值:分配失敗是常見情況(比如內存不足),一定要判斷返回值是否為NULL/0,避免空指針崩潰;

2.釋放函數要對應

?__get_free_pages()/get_zeroed_page()free_pages()

?alloc_pages()__free_pages()

?釋放時的order必須和申請時一致,否則會破壞伙伴系統鏈表;

1.避免內存泄漏:內核內存沒有"自動回收",分配的內存必須在模塊卸載、函數退出時釋放,否則會導致內存泄漏;

2.不要越界訪問:分配的內存大小是PAGE_SIZE << order,超出范圍會觸發內核Oops

七、總結

伙伴系統的內存申請函數看似多,但核心邏輯很統一:都是向伙伴系統請求"2^order個連續物理頁",區別僅在于返回形式(page指針/虛擬地址)和附加功能(清零、參數檢查)。

用一張表總結函數選擇邏輯:

需求場景

推薦函數

返回值類型

直接用虛擬地址、無需操作page

__get_free_pages()

內核虛擬地址

申請1頁、需要清零

get_zeroed_page()

內核虛擬地址

需要操作page結構體(設置屬性等)

alloc_pages()

struct page指針

申請1頁、需要操作page結構體

alloc_page()

struct page指針

掌握這些函數,你就能應對絕大多數內核態內存申請場景。記住核心:選對函數、傳對參數、檢查返回值、及時釋放,就能安全、高效地使用內核內存。

如果覺得這篇文章有用,歡迎點贊、在看、轉發給身邊的開發小伙伴~

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Linux
    +關注

    關注

    88

    文章

    11758

    瀏覽量

    219001
  • 函數
    +關注

    關注

    3

    文章

    4417

    瀏覽量

    67499
  • 內存分配
    +關注

    關注

    0

    文章

    19

    瀏覽量

    8560
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關推薦
    熱點推薦

    Linux內存管理是什么,Linux內存管理詳解

    Linux內存管理 Linux內存管理是一個非常復雜的過程,主要分成兩個大的部分:內核內存
    的頭像 發表于 05-11 17:54 ?7212次閱讀
    <b class='flag-5'>Linux</b>的<b class='flag-5'>內存</b>管理是什么,<b class='flag-5'>Linux</b>的<b class='flag-5'>內存</b>管理<b class='flag-5'>詳解</b>

    Linux Shell腳本入門到實戰詳解

    Linux Shell腳本入門到實戰詳解
    發表于 02-17 15:03 ?1107次閱讀

    Linux內核系統調用詳解

    Linux內核中設置了一組用于實現各種系統功能的子程序,稱為系統調用。用戶可以通過系統調用命令在自己的應用程序中調用它們。
    發表于 08-23 10:37 ?1282次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>中<b class='flag-5'>系統</b>調用<b class='flag-5'>詳解</b>

    Linux內核內存規整總結

    分配需求,如下圖所示: 內存外部碎片導致實際占用物理頁不多,但是已無法申請>=4個頁連續內存,理想當中我們希望內存沒有外部碎片,如下圖所示: 內核
    的頭像 發表于 11-11 11:17 ?2319次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b><b class='flag-5'>內存</b>規整總結

    Linux內核地址映射模型與Linux內核高端內存詳解

    Linux 操作系統和驅動程序運行在內核空間,應用程序運行在用戶空間,兩者不能簡單地使用指針傳遞數據,因為Linux使用的虛擬內存機制,用戶
    發表于 05-08 10:33 ?3812次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>地址映射模型與<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>高端<b class='flag-5'>內存</b><b class='flag-5'>詳解</b>

    Linux內存系統Linux 內存分配算法

    將共享內存當前進程中分離Linux內存系統:五、 內存使用那些坑1、C
    發表于 08-24 07:44

    高端內存詳解linux用戶空間與內核空間

    Linux 操作系統和驅動程序運行在內核空間,應用程序運行在用戶空間,兩者不能簡單地使用指針傳遞數據,因為Linux使用的虛擬內存機制,用戶
    發表于 04-28 17:33 ?1346次閱讀
    高端<b class='flag-5'>內存</b>的<b class='flag-5'>詳解</b>:<b class='flag-5'>linux</b>用戶空間與<b class='flag-5'>內核</b>空間

    詳解Linux的物理內存

    內核申請內存比在用戶態申請內存要更為直接,它沒有采用用戶態那種延遲分配內存技術。
    的頭像 發表于 01-18 17:45 ?2866次閱讀
    <b class='flag-5'>詳解</b><b class='flag-5'>Linux</b>的物理<b class='flag-5'>內存</b>

    Linux內核GPIO操作函數詳解分析

    本文檔的主要內容詳細介紹的是Linux內核GPIO操作函數詳解分析免費下載。
    發表于 01-22 16:58 ?28次下載

    伙伴算法如何才能在Linux內核中實現應用及其改進

    伙伴算法是內存管理的比較常用的算法之一。以Linux內存管理為基礎,闡述了Linux內核中關于
    發表于 03-04 14:37 ?14次下載
    <b class='flag-5'>伙伴</b>算法如何才能在<b class='flag-5'>Linux</b><b class='flag-5'>內核</b>中實現應用及其改進

    Linux內存管理之伙伴系統

    內核初始化完成之后, 內存管理的責任就由伙伴系統來承擔. 伙伴系統基于一種相對簡單然而令人吃驚
    發表于 05-16 09:01 ?1653次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內存</b>管理之<b class='flag-5'>伙伴</b><b class='flag-5'>系統</b>

    申請函數kmalloc、kzalloc、vmalloc區別說明

    內存泄漏,如果內存泄漏發生在內核空間,則會造成系統崩潰。 那么,在內核空間中如何申請
    的頭像 發表于 05-19 16:13 ?2246次閱讀

    Linux內核引導內存分配器的原理

    Linux內核引導內存分配器使用的是伙伴系統算法。這種算法是一種用于動態內存分配的高效算法,它將
    發表于 04-03 14:52 ?819次閱讀

    Linux內核如何使用結構體和函數指針?

    我將結合具體的Linux內核驅動框架代碼來展示Linux內核如何使用結構體和函數指針。
    的頭像 發表于 09-06 14:17 ?1858次閱讀
    <b class='flag-5'>Linux</b><b class='flag-5'>內核</b>如何使用結構體和<b class='flag-5'>函數</b>指針?

    RK平臺Linux IOMMU開發:理到實戰

    顯示(VOP)、編解碼(VPU/HEVC)等場景。今天就從原理、驅動、實戰、問題排查、Linux 內存管理支撐五個維度,帶大家快速上手 RK 平臺 IOMMU 開發。
    的頭像 發表于 02-04 16:24 ?1443次閱讀
    RK平臺<b class='flag-5'>Linux</b> IOMMU開發:<b class='flag-5'>從</b>原<b class='flag-5'>理到</b><b class='flag-5'>實戰</b>