Linux內核空間內存管理(一):內存尋址,內存管理機制綜述

2019-08-31     進擊的IT程式設計師

內存地址

我們偶爾會引用內存地址作為訪問內存單元內容的一種方式,但是,當使用 80x86 微處理器時,我們必須區分以下三種不同的地址:

邏輯地址(logical address)

包含在機器語言指令中用來規定一個操作數或一條指令的地址。。每個邏輯地址都由一個段(segment)和偏移量(offset或displacement)組成,偏移量指明了從段開始的地方到實際地址之間的距離。

線性地址(linear address)

線性地址也稱為虛擬地址(virtual address)。線性地址是一個32位無符號整數,可以用來表示高達4GB的地址,也就是,高達4294967296個內存單元。線性地址通常用十六進位數字表示,值的範圍從0x00000000到0xffffffff。

物理地址(physical address)

用於內存晶片級內存單元尋址。它們與從微處理器的地址引腳發送到內存總線上的電信號相對應。物理地址由32位或36位無符號整數表示。

內存控制單元(MMU)通過一種稱為分段單元(segmentation unit)的硬體電路把一個邏輯地址轉換成線性地址;然後,第二個稱為分頁單元(paging unit)的硬體電路把線性地址轉換成一個物理地址。

想要提高技術,或者進交流群學習的小夥伴

小編這有一些相關的學習資料和技術交流群聊,需要的可以。

私信我:學習

硬體中的分段

分段單元把邏輯地址轉換成線性地址。從80286 模型開始,Intel 微處理器以兩種不同的方式執行地址轉換,這兩種方式分別稱為實模式(real mode) 和 保護模式(protected mode)。這些內容我們之後會描述,實模式存在的主要原因是要維持處理器與早期模型兼容。

段選擇符和段寄存器

一個邏輯地址由兩部分組成:一個段標識符和一個指定段內相對地址的偏移量。段標識符是一個16 位長的欄位,稱為段選擇符(Segment Selector),而偏移量是一個32 位長的欄位。段選擇符並不直接指向段,而是指向段描述符表中定義段的段描述符。

為了快速方便地找到段選擇符,處理器提供段寄存器,段寄存器的唯一目的是存放段選擇符。這些段寄存器稱為cs、ss、ds、es、fs 和gs。儘管只有 6 個段寄存器,但程序可以把同一個段寄存器用於不同的目的,方法是先將其值保存在內存中,用完後再恢復。

6 個寄存器中 3 個有專門的用途:

  • cs 代碼段寄存器,指向包含程序指令的段。
  • ss 棧段寄存器,指向包含當前程序棧的段。
  • ds 數據段寄存器,指向包含靜態數據或者全局數據段。

其他3 個段寄存器作一般用途,可以指向任意的數據段。

cs 寄存器還有一個很重要的功能:它含有一個兩位的欄位,用以指明CPU 的當前特權級(Current Privilege Level,CPL)。值為 0 代表最高優先級,而值為 3 代表最低優先級。Linux 只用 0 級和 3 級,分別稱之為內核態和用戶態。

段描述符(Segment Descriptor)

每個段由一個 8 位元組的段描述符表示,它描述了段的特徵。段描述符放在全局描述符表(Global Descriptor Table ,GDT)或局部描述符表(Local Descriptor Table, LDT)中。

80386 CPU中增設了兩個寄存器:一個是全局性的段描述表寄存器GDTR,另一個是局部性的段描述表寄存器LDTR,分別可以用來指向存儲在內存中的一個段描述結構數組,或者稱為段描述表。由於這兩個寄存器是新增設的,不存在與原有的指令是否兼容的問題,訪問這兩個寄存器的專用指令便設計成「特權指令」。

在此基礎上,段寄存器的高13位(低3位另作它用)用作訪問段描述表中具體結構的下標(index),如下圖所示:

通常只定義一個GDT,而每個進程除了存放在GDT 中的段之外如果還需要創建附加的段,就可以有自己的LDT。GDT在主存中的地址和大小存放在GDTR控制寄存器中,當前正被使用的LDT 地址和大小放在LDTR控制寄存器中。

GDTR或LDTR中的段描述表指針和段寄存器中給出的下標結合在一起,才決定了具體的段描述表項在內存中的什麼地方,也可以理解成,將段寄存器內容的低3位屏蔽掉以後與GDTR或LDTR中的基地址相加得到描述表項的起始地址。因此就無法通過修改描述表項的內容來玩弄詭計,從而起到保護的作用。每個段描述表項的大小是8個位元組,每個描述表項含有段的基地址和段的大小,再加上其它一些信息,其結構如下圖所示。

結構中的 B31 ~ B24 和 B23 ~ B16分別為基地址的 bit16 ~ bit23 和 bit24 ~ bit31,而L19 ~ L16和L15 ~ L0則為段長度的 bit10 ~ bit15 和 bit16 ~ bit19

分段單元

下圖詳細顯示了一個邏輯地址是怎樣轉換成相應的線性地址的。

分段單元(segmentationunit)執行以下操作:

先檢查段選擇符的TI欄位,以決定段描述符保存在哪一個描述符表中。TI欄位指明描述符是在GDT 中(在這種情況下,分段單元從GDTR寄存器中得到GDT的線性基地址)還是在激活的LDT中(在這種情況下,分段單元從LDTR寄存器中得到LDT 的線性基地址)。

從段選擇符的 index 索引欄位計算段描述符的地址,index 欄位的值乘以8(一個段描述符的大小),這個結果與GDTR或LDTR寄存器中的內容相加。

把邏輯地址的偏移量與段描述符Base 欄位的值相加就得到了線性地址。

硬體中的分頁

分頁單元把線性地址轉換成物理地址。 其中的一個關鍵任務是把所請求的訪問類型與線性地址的訪問權限相比較,如果這次內存訪問是無效的,就產生一個缺頁異常。

為了效率起見,線性地址被分成以固定長度為單位的組,稱為頁(page)。頁內部連續的線性地址被映射到連續的物理地址中。這樣,內核可以指定一個頁的物理地址和其存取權限,而不用指定頁所包含的全部線性地址的存取權限。我們遵循通常習慣,使用術語「頁」既指一組線性地址,又指包含在這組地址中的數據。

分頁單元把所有的RAM 分成固定長度的頁框(有時叫做物理頁)。每一個頁框包含一個頁,也就是說一個頁框的長度與一個頁的長度一致。頁框是主存的一部分,因此也是一個存儲區域。區分一頁和一個頁框是很重要的,前者只是一個數據塊,可以存放在任何頁框或磁碟中。

把線性地址映射到物理地址的數據結構稱為頁表(page table)。頁表存放在主存中,並在啟用分頁單元之前必須由內核對頁表進行適當的初始化。

常規分頁

從80386 起,Intel 處理器的分頁單元處理4KB 的頁。

32 位的線性地址被分成 3 個域:

Directory(目錄):最高10 位

Table(頁表):中間10 位

Offset(偏移量):最低12 位

線性地址的轉換分兩步完成,每一步都基於一種轉換表,第一種轉換表稱為頁目錄表,第二種轉換表稱為頁表。

使用這種二級模式的目的在於減少每個進程頁表所需RAM的數量。如果使用簡單的一級頁表

個表項(也就是,在每項 4 個位元組時,需要 4MB RAM)來表示每個進程的頁表(如果進程使用全部4GB線性地址空間),即使一個進程並不使用那個範圍內的所有地址。二級模式通過只為進程實際使用的那些虛擬內存區請求頁表來減少內存容量。

每個活動進程必須有一個分配給它的頁目錄。不過,沒有必要馬上為進程的所有頁表都分配RAM。只有在進程實際需要一個頁表時才給該頁表分配RAM會更為有效率。

正在使用的頁目錄的物理地址存放在控制寄存器 cr3 中。線性地址內的Directory 欄位決定頁目錄中的目錄項,而目錄項指向適當的頁表。地址的Table 欄位依次又決定頁表中的表項,而表項含有頁所在頁框的物理地址。Offset 欄位決定頁框內的相對位置。由於它是12 位長,故每一頁含有4096 位元組的數據。

Directory 欄位和 Table 欄位都是10 位長,因此頁目錄和頁表都可以多達1024 項。

這和我們對32 位地址所期望的一樣。

頁目錄項和頁表項有同樣的結構,每項都包含下面的欄位:

  • Present 標誌
  • 包含頁框物理地址最高20 位的欄位
  • Accessed 標誌
  • Dirty 標誌
  • Read/Write 標誌
  • User/Supervisor 標誌
  • PCD 和 PWT 標誌
  • Page Size 標誌
  • Global 標誌

常規分頁舉例

這個簡單的例子將有助於闡明常規分頁是如何工作的。我們假定內核已給一個正在運行的進程分配的線性地址空間範圍是 0x20000000 到 0x2003ffff。這個空間正好由64頁組成。我們不必關心包含這些頁的頁框的物理地址,事實上,其中的一些頁甚至可能不在主存中。我們只關注頁表項中剩餘的欄位。

讓我們從分配給進程的線性地址的最高10 位(分頁單元解釋為Directory 欄位)開始。這兩個地址都以 2 開頭後面跟著0,因此高10 位有相同的值,即0x080 或十進位的128。因此,這兩個地址的Directory 欄位都指向進程頁目錄的第129項。相應的目錄項中必須包含分配給該進程的頁表的物理地址。

如果沒有給這個進程分配其它的線性地址,則頁目錄的其餘 1023 項都填為0。

中間 10 位的值(即Table 欄位的值)範圍從0 到 0x03f,或十進位的從 0 到 63。因而只有頁表的前 64 個表項是有意義的,其餘 960 個表項都填0。

假設進程需要讀線性地址 0x20021406 中的位元組。這個地址由分頁單元按下面的方法處理:

Directory 欄位的 0x80 用於選擇頁目錄的第 0x80 目錄項, 此目錄項指向和該進程的頁相關的頁表。

Table 欄位 0x21 用於選擇頁表的第 0x21 表項, 此表項指向包含所需頁的頁框。

最後,Offet 欄位0x406 用於在目標頁框中讀偏移量為0x406 中的位元組。

如果頁表第0x21 表項的Present 標誌為0,則此頁就不在主存中;在這種情況下,分頁單元在線性地址轉換的同時產生一個缺頁異常。無論何時,當進程試圖訪問限定在0x20000000 到0x2003ffff 範圍之外的線性地址時,都將產生一個缺頁異常,因為這些頁表項都填充了0,尤其是它們的Present 標誌都被清0。

Linux內存管理

綜述

內存管理是運行在計算機上的應用程式通過軟硬體協作來訪問內存的一種方法。內存管理子系統的職責為:進程請求內存時分配可用內存,進程釋放內存後回收內存,以及跟蹤系統中內存的使用狀況。

作業系統的生命周期劃分為兩個階段:正常執行階段和自舉階段。自舉階段使用臨時內存;而正常運行階段使用的內存有兩種情況:一種是有一部分固定的內存分配給內核代碼和數據,另一種是為動態內存請求分配內存。動態內存請求源於進程的創建和空間的擴張。我們著重介紹作業系統正常運行時對內存的管理。

最簡單的內存管理系統是運行進程對所有內存具有訪問權的系統。以這樣方式運行的進程必須包含對所需要的硬體進行操作的全部代碼,必須能找到自己的內存地址,而且還必須能將自身的數據載入內存。這種方式不但給開發人員造成了很重的負擔,而且還要保證進程與可用內存的大小合適。這些苛刻的要求對於日益複雜化的程序需求來說顯然很不現實,所以要將內存管理這個棘手的任務交給作業系統來對付,可用內存會在作業系統和用戶進程之間劃分。

當代作業系統既要求能夠使多個程序共享系統資源,同時還要求內存限制對程序開發者透明。在此需求下,虛擬內存(Virtual memory)應運而生,虛擬內存支持程序訪問比系統物理可用內存大得多的內存空間,而且也使得多個程序共享內存變得更再效。物理內存(或叫核心內存)是系統中由RAM晶片控制的可用內存。虛擬內存依靠透明地使用磁碟空間,得以使程序運行起來好像它們使用比系統物理內存更多的內存空間。磁碟空間(相比物理內存價格更低廉,容量也更大)可作為物理內存的擴充。我們之所以稱其為虛擬內存就是因為磁碟存儲體有效地充當內存,但它本身並不是內存。下圖描述了多級數據存儲體的層次關係。

使用虛擬內存時,程序數據被分割成基本單元,這些單元可以在磁碟與內存間來回移動。這樣,程序正使用的那部分就可以置於內存中,以便快速被訪問,而未用的部分則被臨時存放在磁碟,如此來減輕待訪問數據存在磁碟上導致讀取時間過長的問題。這些數據單元(或者說虛擬內存塊)被稱作頁(Page)。

同理,物理內存也需要被劃分成用於保存這些頁的區,這些區被稱作頁面(Page Frame)。當進程請求訪問一個地址時,該地址所在的頁被載入內存,對頁中任一數據的請求都會產生對該頁的訪問。如果頁中的任一地址以前都沒有被訪問過,說明該頁尚未被裝入內存。對頁中地址第一次訪問便會產生一個失敗或缺頁(page fault),因為這時該頁並沒有在內存中,因此必須從磁碟請求。一次缺頁便是一個中斷(trap)。當發生缺頁時,內核必須選擇一個頁面,然後將其內容(頁)寫回到磁碟,從而用程序剛剛請求的頁的內容來填充它。

當程序從內存中存取數據時,會使用地址來指出需要訪問的內存位置。該地址被稱作虛擬地址(virtual address),它們組成進程虛擬地址空間(virtual address space)。每個進程都有自己獨立的虛擬地址範圍,這樣做的好處是可防止非法讀取或寫覆蓋其他進程的數據。虛擬內存允許進程「使用」超過可用物理內存的內存空間,因此作業系統可以給每個進程提供獨立的虛擬線性地址空間。

虛擬地址空間的大小取決於體系結構的字長,如果處理器的寄存器可容納32位數值,那麼運行該程序的處理器支持的進程虛擬地址空間由 232 2^{32}2

32

個地址構成。虛擬內存不但擴大了可尋址的內存範圍,而且也使得用戶空間的開發者不必擔心物理內存的本質所帶來的限制,比如開發者不需要管理內存中的任何漏洞。以32位的系統為例,其虛擬地址空間的範圍是0~4G,如果系統有 2G 的物理內存,那麼它的物理地址範圍是 0 ~ 2G。而程序可能有4GB之巨,但是必須被裝入可用內存中才能運行,因此整個程序將被存放在磁碟上,只有當需要時頁才會被載入到內存。

將頁在內存到磁碟之間調入調出的機制被稱作分頁機制(paging),分頁包括程序虛擬地址到物理內存地址的轉換。

內存管理器是作業系統中負責維護虛擬地址和物理地址之間的關係,並且實現分頁機制。對內存管理來說,頁是基本的內存單元;MMU (Memory Management Unit,內存管理單元)是完成實際的地址轉換工作硬體部件,內核提供了頁表(對可用頁進行索引的列表)以及MMU在執行地址轉換時要訪問的相關地址。上述這些數據都會在頁面載入內存時被更新。

作為內存管理器管理的基本內存單元,頁的許多狀態需要被記錄下來。比如內核需要知道什麼時候頁可以被回收。為此,內核使用頁描述符,內存中每個物理頁都對應一個頁描述符。

頁結構定在 include/linux/mm.h 文件中。

struct page 
{
unsigned long flags; //32位的位圖,每一位表示頁面的一個屬性
atomic_t count ; // 頁的引用計數
struct list_head list; //頁表的雙向鍊表
struct address_space *mapping;
unsigned long index;
struct list_head lru; // 連結最少使用的頁表,可能會被回收
union{
struct pte_chain *chain;
pte_addr_t direct
}pte;
unsigned long private;

#if definde (WANT_PAGE_VIRTUAL)
void *virtual; // 指向頁的虛擬地址
#endif
}

我們重點解釋兩個欄位:

count:count欄位相當於計數器,統計一個頁使用或引用的次數。數值0表示頁空閒,即頁面是可重用的;正數表示訪問該頁數據的進程數;當計數值變為-1時,就說明當前內核並沒有引用這一頁。

virtual:virtual是一個指向頁所對應虛擬地址的指針。通常情況下,它就是頁在虛擬內存中的地址。但是,有些內存並不永久映射到內核地址空間上(高端內存),在這種情況下,這個域的值為NULL,需要的時候,必須動態映射這些頁。

內存管理區

由於硬體的限制,內核並不能對所有的頁一視同仁。有些頁位於內存中特定的物理地址上,所以不能將其用於一些特定的任務。由於存在這種限制,所以內核把頁劃分為不同的區(zone)。內核使用區對具有相似特性的頁進行分組。

Linux必須處理如下兩種由於硬體存在缺陷而引起的內存尋址問題:

一些硬體只能用某些特定的內存地址來執行DMA(直接內存訪問)。

一些體系結構的內存的物理尋址範圍比虛擬尋址範圍大的多,這就有一些內存不能永久的映射到內核空間上。

內存管理區是由頁面(或叫物理頁)組成,這意味著,頁面的分配來自特定的內存管理區。在Linux系統中存在三個內存管理區:

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

ZONE_NORMAL :具有虛擬映射的非DMA頁,這個區包含的都是能正常映射的頁。

ZONE_HIGHMEM : 這個區包含「高端內存」,其中的頁並不能永久地映射到內核地址空間。

區的實際使用和分布是與體系結構相關的。比如某些體系結構在內存的任何地址上執行DMA都沒有問題。

Linux把系統的頁劃分為區,形成不同的內存池,這樣就可以根據用途進行分配了。例如,ZONE_DMA內存池讓內核有能力為DMA分配所需的內存。如果需要這樣的內存,那麼,內核就可以從ZONE_DMA中按照請求的數目取出頁。注意,區的劃分沒有任何物理意義,這只不過是內核為了管理頁而採取的一種邏輯上的分組。

儘管某些分配可能需要從特定的區中獲取頁,但這並不是說,某種用途的內存一定要從對應的區獲取。儘管用於DMA的內存必須從ZONE_DMA中進行分配,但是一般用途的內存卻既能從ZONE_DMA分配,也能從ZONE_NORMAL分配。當然,內核更希望一般用途的內存從常規區分配,這樣能節省ZONE_DMA中的頁,保證滿足DMA的使用需求。但是,如果可供分配的資源不夠用了(如果內存已經變得很少了),那麼,內核就會去占用其他可用區的內存。

每個區都用struct zone表示,定義在<linux/mmzone.h>中,我們只介紹下面一些重要的欄位:

lock:是一個自旋鎖,它防止該結構被並發訪問

watermark數組持有該區的最小值、最低和最該水位值

name:是一個以NULL結束的字符串表示這個區的名字。內核啟動期間初始化這個值。三個區的名字分別為"DMA"、「Normal」、「HighMem」

頁面

頁面(page frame)是存放頁的基本內存單元。只要進程請求內存,內核便會請求一個頁面給它;同樣的,如果頁面不再被使用,那麼內核便會將其釋放,以便其他進程可以再使用。

請求頁面的函數

內核提供了一種請求內存的底層機制,應提供了對它進行訪問的幾個接口,所有的接口都以頁為單位分配內存,定義與於

調用層次圖如下:

/*

* 該函數分配2的order次方個連續的物理頁,並返回一個指針,該指針指向第一頁

* 的page結構體,如果出錯,就返回NULL。

*/

struct page * alloc_pages(gfp_t gfl_mask,unsigned int order)

/*

* 該函數分可以把給定的頁轉換成它的邏輯地址,

* 該函數返回一個指針,指向給定物理頁當前所在的邏輯地址。

*/

void * page_address(struct page *page)

/*

* 該函數與alloc_pages()作用相同,

* 不過它直接返回所請求的第一個頁的邏輯地址。

*/

unsigned long __get_free_pages(gft_t gft_mask, unsigned int order)

/*

* 如果只需要一頁,可以使用下面兩個封裝好的函數

* 這兩個函數與其兄弟函數工作方式相同,只不過傳遞給order的值為0

*/

struct page * alloc_page(gfp_t gfp_mask)

unsigned long __get_free_page(gfp_t gfp_mask)

/*

* 如果需要讓返回的頁的內容全為0,可以使用下面的函數

* 該函數與__get_free_page()工作方式相同,只是把分配好的頁都填充成了0

*/

unsigned long get_zeroed_page( unsigned int gfp_mask)

釋放頁面的函數

當不再需要頁時,可以使用下面的函數來釋放它們:

void __free_pages(struct page * page, unsigned int order)

void free_pages(unsigned long addr, unsigned int order)

void free_page(unsigned long addr)

釋放頁面時要謹慎,只能釋放你自己的頁。傳遞了錯誤的struct page或地址,用了錯誤的order值,這些都可能導致系統崩潰。

調用層次圖如下:

下面看一個示例,我們想要得到8個頁:

unsigned long page;

page = __get_free_pages(GFP_KERNEL,3); //GFP_KERNEL參數是gfp_mask標誌的一個例子

if(!page){

/* 沒有足夠的內存,返回錯誤 */

return -ENOMEN;

}

// 釋放8個頁

free_pages(page,3); // 頁已經被釋放,就不應該再訪問存放在「page」中的地址了

調用__get_free_pages()之後需要注意進行錯誤檢查,防止內核分配內存失敗。

當我們需要以頁為單位的一族連續物理頁時,尤其是你只需要一兩頁時,這些低級頁函數很有用,對於常用的以位元組為單位的分配來說,內核提供的函數是kmalloc()。

kmalloc()

kmalloc()函數與用戶空間的malloc()函數非常的類似,只多了一個flags參數。

kmalloc()函數是一個簡單的接口,用它可以獲得以位元組為單位的一塊內核內存。如果需要整個頁,那麼前面討論的頁分配接口更為適合。但是,對於大多數內核分配來說,kmalloc()接口用得更多。

kmalloc()函數在中聲明:

void * kmalloc(size_t size,gfp_t flags);

這個函數返回一個指向內存塊的指針,其內存塊至少要有size大小,所分配的內存區在物理上是連續的。在出錯時,它返回NULL。

gfp_mask標誌

這些標誌分為三類:行為修飾符,區修飾符和類型

行為修飾符:表示內核應該如何分配所需的內存,在某些特殊情況下,只能使用某些特定的方法分配內存。例如,中斷處理程序要求內核分配內存的過程中不能睡眠。

區修飾符:表示內存應當從何處分配,內核把物理內存分為多個區,每個區用於不同的目的。

類型:組合了行為修飾符和區修飾符,將各種可能用到的組合歸納為不同的類型,簡化修飾符的使用,這樣只需指定一個類型標誌就可以了。

kfree()

kmalloc() 的另一端就是 kfree(),kfree()聲明於中:

void kfree(const void *ptr)

kfree()函數釋放由 kmalloc() 分配出來的內存塊。與用戶空間類似,分配和回收要注意配對使用,以避免內存泄露和其他bug。注意,調用kfree(NULL)是安全的。

vmalloc()

vmalloc()函數的工作方式類似於kmalloc(),只不過vmalloc()函數所分配的內存虛擬地址是連續的,而物理地址則無需連續。這也是用戶空間分配函數的工作方式:由malloc()返回的頁在進程的虛擬地址空間內是連續的,但是,這並不保證它們在物理RAM中也連續。kmalloc() 函數確保頁在物理地址上是連續的(虛擬地址自然也是連續的)。vmalloc() 函數隻確保頁在虛擬地址空間內是連續的。它通過分配非連續的物理內存塊,再「修正」頁表,把內存映射到邏輯地址空間的連續區域中,就能做到這點。

大多數情況下,只有硬體設備需要得到物理地址連續的內存。在很多體系結構上,硬體設備存在於內存管理單元以外,它根本不理解什麼是虛擬地址。因此,硬體設備用到的任何內存區都必須是物理上連續的塊,而不僅僅是虛擬地址連續的塊。而僅供軟體使用的內存塊(例如與進程相關的緩衝區)就可以使用只有虛擬地址連續的內存塊。但在編程中,根本察覺不到這種差異。對內核而言,所有內存看起來都是邏輯上連續的。

儘管僅僅在某些情況下才需要物理上連續的內存塊,但是,很多內核代碼仍然使用kmalloc()來獲得內存,而不是vmalloc()。這主要是出於性能的考慮。vmalloc()函數為了把物理上不連續的頁轉換為虛擬地址空間上連續的頁,必須專門建立頁表項。糟糕的是,通過vmalloc()獲得的頁必須一個一個地進行映射,因為它們物理上是不連續的。這就會導致比直接內存映射大得多的TLB抖動。

因為上述原因,vmalloc()僅在不得已時才會使用,一般是在為了獲得大塊內存時,例如,當模塊被動態插入到內核中時,就把模塊裝載到由vmalloc()分配的內存上。

vmalloc()函數聲明在中,定義在

void vmalloc(unsigned long size)

該函數返回一個指針,指向邏輯上連續的一塊內存區,其大小為size。在發生錯誤時,函數返回NULL。函數可能睡眠,因此,不能從中斷上下文中進行調用,也不能從其他不允許阻塞的情況下進行調用。

要釋放通過vmalloc()所獲得的內存,使用下面的函數:

void vfree(const void *addr)

這個函數會釋放從 addr 開始的內存塊,其中addr是以前由 vmalloc() 分配的內存塊的地址。這個函數也可以睡眠,因此,不能從中斷上下文中調用。它沒有返回值。

想要提高技術,或者進交流群學習的小夥伴

小編這有一些相關的學習資料和技術交流群聊,需要的可以。

私信我:學習

文章來源: https://twgreatdaily.com/zh-sg/NEbd9GwBJleJMoPMqBJO.html