[Linux]How to alloc memory (kmalloc) in Linux ISR

cat /proc/buddyinfolinux

分類: LINUX Make sure you're familiar with GFP_KERNEL and GFP_ATOMIC. There is a certain amount of memory reserved for ISRs etc. If you acquire it using GFP_ATOMIC, the kernel guarantees that it doesn't suspend your ISR. Acquiring memory with kmalloc(size,GFP_KERNEL), it would possibly suspend your ISR until enough memory is available (via page fault). Since your ISR probably blocks most of the other processes and even other ISRs, it could easily happen that your system locks up.程序員

Therefore it's a very good idea to never acquire memory via an ISR. There shouldn't be a need for it in a carefully designed driver. There are different approaches to overcome this issue:算法

  1. usually your driver is controlled by a user process (or the scheduler workqueue) and you can safely acquire memory.
  2. If not, you might want to install a scheduled process which pends on a flag, and you can delegate memory allocation to such a safe process, just by talking to it from the ISR and set/clear this flag.

最經常使用的標誌是GFP_KERNEL, GFP_ATOMIC。其次是__GFP_DMA和__GFP_HIGHMEM。其中GFP的意思就是get free page。GFP_KERNEL和GFP_ATOMIC的區別在於前者使用在進程上下文中,後者使用在進程上下文以外,例如中斷上下文。因此前者可能在沒有空閒內存時使進程休眠,然後者是不能休眠的。shell

到目前爲止,咱們老是用kmalloc和kfree來進行內存分配。固然,只用這些函數的確是 管理內存的捷徑。本章將會介紹其餘一些內存分配技術。但咱們目前並不關心不一樣的體 繫結構其實是如何進行內存管理的。由於內核爲設備驅動程序提供了一致的接口,本 章的模塊都沒必要涉及分段,分頁等問題。另外,本章我也不會介紹內存管理的內部細節 ,這些問題將留到第13章「Mmap和DMA」的「Linux的內存管理」一節討論。編程

kmalloc函數的內幕 kmalloc內存分配引擎功能強大,因爲和malloc函數很類似,很容易就能夠學會 。這個函數運行得很快-一除非它被阻塞-一它不清零它得到的內存空間;分配給它的 區域仍存放着原有的數據。在下面幾節,我會詳細介紹kmalloc函數,你能夠將它和我後 面要介紹的一些內存分配技術做個比較。數組

優先權參數 kmalloc函數的第一個參數是size(大小),我留在下個小節介紹。第二個參數, 是優先權,更有意思,由於它會使得kmalloc函數在尋找空閒頁較困難時改變它的行爲。安全

最經常使用的優先權是GFP_KERNEL,它的意思是該內存分配(內部是經過調用get_fre

e_pages來實現的,因此名字中帶GFP)是由運行在內核態的進程調用的。也就是說,調用 它的函數屬於某個進程的,使用GFP_KERNEL優先權容許kmalloc函數在系統空閒內存低於 水平線min_free_pages時延遲分配函數的返回。當空閒內存太少時,kmalloc函數會使當 前進程進入睡眠,等待空閒頁的出現。數據結構

新的頁面能夠經過如下幾種途徑得到。一種方法是換出其餘頁;由於對換須要時

間,進程會等待它完成,這時內核能夠調度執行其餘的任務。所以,每一個調用kmalloc(G FP_KERNEL)的內核函數都應該是可重入的。關於可重入的更多細節可見第5章「字符設備 驅動程序的擴展操做」的「編寫可重入的代碼」一節。app

並不是使用GFP_KERNEL優先權後必定正確;有時kmalloc是在進程上下文以外調用

的-一好比,在中斷處理,任務隊列處理和內核定時器處理時發生。這些狀況下,curre nt進程就不該該進入睡眠,這時應該就使用優先權GFP_ATOMIC。原子性(atomic)的內存 分配容許使用內存的空閒位,而與min_free_pages值無關。實際上,這個最低水平線值 的存在就是爲了能知足原子性的請求。但因爲內核並不容許經過換出數據或縮減文件系 統緩衝區來知足這種分配請求,因此必須還有一些真正能夠得到的空閒內存。ide

爲kmalloc還定義了其餘一些優先權,但都不常用,其中一些只在內部的內

存管理算法中使用。另外一個值的注意的優先權是GFP_NFS,它會使得NFS文件系統縮減空 閒列表到min_free_pages值如下。顯然,爲使驅動程序「更快」而用GFP_NFS優先權取代 GFP_KERNEL優先權會下降整個系統的性能。

除了這些經常使用的優先權,kmalloc還能夠識別一個位域:GFP_DMA。GFP_DMA標誌

位要和GFP_KERNEL和GFP_ATOMIC優先權一塊兒使用來分配用於直接內存訪問(DMA)的內存頁 。咱們將在第13章的「直接內存訪問」一節討論如何使用這個標誌位。

size參數 系統物理內存的管理是由內核負責的,物理內存只能按頁大小進行分配。這就需 要一個面向頁的分配技術以取得計算機內存管理上最大的靈活性。相似malloc函數的簡 單的線性的分配技術再也不有效了;在象Unix內核這樣的面向頁的系統中內存若是是線性 分配的就很難維護。空洞的處理很快就會成爲一個問題,會致使內存浪費,下降系統的 性能。

Linux是經過維護頁面池來處理kmalloc的分配要求的,這樣頁面就能夠很容易地

放進或者取出頁面池。爲了可以知足超過PAGE_SIZE字節數大小的內存分配請求,fs/kma lloc.c文件維護頁面簇的列表。每一個頁面簇都存放着連續若干頁,可用於DMA分配。在這 裏我不介紹底層的實現細節,由於內部的數據結構能夠在不影響分配語義和驅動程序代 碼的前提下加以改變。事實上,2.1.38版已經將kmalloc從新實現了。2.0版的內存分配 實現代碼能夠參見文件mm/malloc.c,而新版的實如今文件mm/slab.c中。想了解2.0版實 現的詳情可參見第16章「內核代碼的物理佈局」的「分配和釋放」一節。

Linux所使用的分配策略的最終方案是,內核只能分配一些預約義的固定大小的

字節數組。若是你申請任意大小的內存空間,那麼極可能系統會多給你一點。

這些預約義的內存大小通常「稍小於2的某次方」(而在更新的實現中系統管理的

內存大小剛好爲2的各次方)。若是你能記住這一點,就能夠更有效地使用內存了。例如 ,若是在Linux 2.0上你須要一個2000字節左右的緩衝區,你最好仍是申請2000字節,而 不要申請2048字節。在低與2.1.38版的內核中,申請剛好是2的冪次的內存空間是最糟糕 的狀況了-內核會分配兩倍於你申請空間大小的內存給你。這也就是爲何在示例程序s cull中每一個單元(quantum)要用4000字節而不是4096字節的緣由了。

你能夠從文件mm/malloc.c(或者mm/slab.c)獲得預約義的分配塊大小的確切數值

,但注意這些值可能在之後的版本中被改變。在當前的2.0版和2.1版的內核中,均可以 用個小技巧-儘可能分配小於4K字節的內存空間,但不能保證這種方法未來也是最優的。

不管如何,Linux2.0中kmalloc函數能夠分配的內存空間最大不能超過32個頁-A

lpha上的256KB或者Intel和其餘體系結構上的128KB。2.1.38版和更新的內核中這個上限 是128KB。若是你須要更多一些空間,那麼有下面一些的更好的解決方法。

get_free_page和相關函數 若是模塊須要分配大塊的內存,那使用面向頁的分配技術會更好。請求整頁還有 其餘一些好處,後面第13章的「mmap設備驅動程序操做」一節將會介紹。

分配頁面可以使用下面一些函數:

l get_free_page返回指向新頁面的指針並將頁面清零。

l __get_free_pages和get_free_page相似,但不清零頁面。

l __get_free_pages返回一個指向大小爲幾個頁的內存區域的第一個字節位置的 指針,但也不清零這段內存區域。

l __get_dma_pages返回一個指向大小爲幾個頁的內存區域的第一個字節位置的 指針;這些頁面在物理上是連續的,可用於DMA傳輸。

這些函數的原型在Linux2.0中定義以下:

unsigned long get_free_page(int priority);

unsigned long __get_free_page(int priority);

unsigned long __get_dma_pages(int priority, unsigned long order);

unsigned long __get_free_pages(int priority, unsigned long order, int dma);

實際上,除了__get_free_pages,這些函數或者是宏或者是最終調用了__get_free_page s的內聯函數。

當程序使用完分配給它的頁面,就應該調用下面的函數。下面的第一個函數是個宏,其 中調用了第二個函數:

void free_page(unsigned long addr);

void free_pages(unsigned long addr, unsigned long order);

若是你但願代碼在1.2版和2.0版的Linux上都能運行,那最好仍是不要直接使用函數__ge t_free_pages,由於它的調用方式在這兩個版本間修改過2次。只使用函數get_free_pag e(和__get_free_page)更安全更可移植,並且也足夠了。

至於DMA,因爲PC平臺設計上的一些「特殊性」,要正確尋址ISA卡還有些問題。我在第1 3章的「直接內存訪問」一節中介紹DMA時,將只限於2.0版內核上的實現,以免引入移 植方面的問題。

分配函數中的priority參數和kmalloc函數中含義是同樣的。__get_free_pages函數中的 dma參數是零或非零;若是不是零,那麼對分配的頁面簇能夠進行DMA傳輸。order是你請 求分配或釋放的內存空間相對2的冪次(即log2N)。例如,若是須要1頁,order爲0;須要 8頁,order爲3。若是order太大,分配就會失敗。若是你釋放的內存空間大小和分配得 到的大小不一樣,那麼有可能破壞內存映射。在Linux目前的版本中,order最大爲5(至關 於32個頁)。總之,order越大,分配就越可能失敗。

這裏值得強調的是,可使用相似kmalloc函數中的priority參數調用get_free_pages和 其餘這些函數。某些狀況下內存分配會失敗,最常常的情形就是優先權爲GFP_ATOMIC的 時候。所以,調用這些函數的程序在分配出錯時都應提供相應的的處理。

咱們已經說過,若是不怕冒險的話,你能夠假定按優先權GFP_KERNEL調用kmalloc和底層 的get_free_pages函數都不會失敗。通常說來這是對的,但有時也未必:我那臺忠實可 靠的386,有着4MB的空閒的RAM,但當我運行一個"play-it-dangerous"(冒險)模塊時卻 象瘋了同樣。除非你有足夠的內存,想寫個程序玩玩,不然我建議你總檢查檢查調用分 配函數的結果。

儘管kmalloc(GFP_KERNEL)在沒有空閒內存時有時會失敗,但內核老是儘量知足該內存 分配請求。所以,若是分配太多內存,系統的響應性能很容易就會降下來。例如,若是 往scull設備寫入大量數據,計算機可能就會死掉;爲知足kmalloc分配請求而換出內存 頁,系統就會變得很慢。全部資源都被貪婪的設備所吞噬,計算機很快就變的沒法使用 了;由於此時已經沒法爲你的shell生成新的進程了。我沒有在scull模塊中提到這個問 題,由於它只是個例子模塊,並不能真的在多用戶系統中使用。但做爲一個編程者,你 必需要當心,由於模塊是特權代碼,會帶來系統的安全漏洞(好比說,極可能會形成DoS( "denail-of-service")安全漏洞)。

使用一整頁的scull: scullp 至此,咱們已經較徹底地介紹了內存分配的原理,下面我會給出一些使用了頁面 分配技術的程序代碼。scullp是scull模塊的一個變種,它只實現了一個裸(bare)設備- 持久性的內存區域。和scull不一樣,scullp使用頁面分配技術來獲取內存;scullp_order 變量缺省爲0,也能夠在編譯時或裝載模塊時指定。在Linux 1.2上編譯的scullp設備在o rder大於零時會據絕被加載,緣由咱們前面已經說明過了。在Linux 1.2上,scullp模塊 只容許「安全的」單頁的分配函數。

儘管這是個實際的例子,但值的在這提到的只有兩行代碼,由於該設備其實只是

分配和釋放函數略加改動的scull設備。下面給出了分配和釋放頁面的代碼行及其相關的 上下文:

/* 此處分配一個單位內存 */

if (!dptr->data[s_pos]) {

dptr->data[s_pos] = (void *)__get_free_pages(GFP_KERNEL,

dptr->order,0);

if (!dptr->data[s_pos])

        return -ENOMEM;

memset(dptr->data[s_pos], 0, PAGE_SIZE << dptr->order);

/* 這段代碼釋放全部分配單元 */

        for (i = 0; i < qset; i++)

            if (dptr->data[i])

                free_pages((unsigned long)(dptr->data[i]), dptr->order);



   從用戶的角度看,能夠感受到的差別就是速度快了一些。我做了寫測試,把4M字

節的數據從scull0拷貝到scull1,而後再從scullp0拷貝到scullp1;結果代表內核空間處 理器的使用率有所提升。

但性能提升的並很少,由於kmalloc設計得也運行得很快。基於頁的分配策略的

優勢實際不在速度上,而是更有效地使用了內存。按頁分配不會浪費內存空間,而用kma lloc函數則會浪費必定數量的內存。事實上,你可能會回想起第5章的「所使用的數據結 構」一節中咱們已經提到過select_table用了__get_free_page函數。

使用__get_free_page函數的最大優勢是這些分配獲得頁面徹底屬於你,並且在

理論上能夠經過適當地調整頁表將它們合併成一個線性區域。結果就容許用戶進程對這 些分配獲得的不連續內存區域進行mmap。我將在第13章的「mmap設備驅動程序操做」一 節中討論mmap調用和頁表的實現內幕。

vmalloc和相關函數 下面要介紹的內存分配函數是vmalloc,它分配虛擬地址空間的連續區域。儘管 這段區域在物理上多是不連續的(要訪問其中的每一個頁面都必須獨立地調用函數__get_ free_page),內核卻認爲它們在地址上是連續的。分配的內存空間被映射進入內核數據 段中,從用戶空間是不可見的-這一點上與其餘分配技術不一樣。vmalloc發生錯誤時返回 0(NULL地址),成功時返回一個指向一個大小爲size的線性地址空間的指針。

該函數及其相關函數的原型以下:



   void* vmalloc(unsigned long size);

   void vfree(void* addr);

   void* vremap(unsigned long offset, unsigned long size);



   注意在2.1版內核中vremap已經被重命名爲ioremap。並且,Linux 2.1引入了一

個新的頭文件,<linux/vmalloc.h>,使用vmalloc時應將它包含進來。

與其餘內存分配函數不一樣的是,vmalloc返回很「高」的地址值-這些地址要高

於物理內存的頂部。因爲vmalloc對頁表調整後容許用連續的「高」地址訪問分配獲得的 頁面,所以處理器是能夠訪問返回獲得的內存區域的。內核能和其餘地址同樣地使用vma lloc返回的地址,但程序中用到的這個地址與地址總線上的地址並不相同。

用vmalloc分配獲得的地址是不能在微處理器以外使用的,由於它們只有在處理

器的分頁單元之上纔有意義。但驅動程序須要真正的物理地址時(象外設用以驅動系統總 線的DMA地址),你就不能使用vmalloc了。正確使用vmalloc函數的場合是爲軟件分配一 大塊連續的用於緩衝的內存區域。注意vmalloc的開銷要比__get_free_pages大,由於它 處理獲取內存還要創建頁表。所以,不值得用vmalloc函數只分配一頁的內存空間。

使用vmalloc函數的一個例子函數是create_module系統調用,它利用vmalloc函

數來獲取被建立模塊須要的內存空間。而在insmod調用重定位模塊代碼後,將會調用mem cpy_fromfs函數把模塊自己拷貝進分配而得的空間內。

用vmalloc分配獲得的內存空間用vfree函數來釋放,這就象要用kfree函數來釋

放kmalloc函數分配獲得的內存空間。

和vmalloc同樣,vremap(或ioremap)也創建新的頁表,但和vmalloc不一樣的是,v

remap實際上並不分配內存。vremap的返回值是個虛擬地址,能夠用來訪問指定的物理內 存區域;獲得的這個虛擬地址最後要調用vfree來釋放掉。

vremap用於將高內存空間的PCI緩衝區映射到用戶空間。例如,若是VGA設備的幀緩衝區 被映射到地址0xf0000000(典型的一個值)後,vremap就能夠創建正確的頁表讓處理機可 以訪問。而系統初始化時創建的頁表只是用於訪問低於物理地址空間的內存區域。系統 的初始化過程並不檢測PCI緩衝區,而是由各個驅動程序本身負責管理本身的緩衝區;PC I的細節將在第15章「外設總線概貌」的「PCI接口」一節中討論。另外,你沒必要重映射 低於1MB的ISA內存區域,由於這段內存空間可用其餘方法訪問,參見第8章「硬件管理」 的「訪問設備卡上的內存」一節。

若是你但願驅動程序能在不一樣的平臺間移植,那麼使用vremap時就要當心。在一

些平臺上是不能直接將PCI內存區域映射處處理機的地址空間的,例如Alpha上就不行。 此時你就不能象普通內存區域那樣地對重映射區域進行訪問,你要用readb函數或者其餘 一些I/O函數(可參見第8章的「1M內存空間之上的ISA內存」一節)。這套函數能夠在不一樣 平臺間移植。

對vmalloc和vremap函數可分配的內存空間大小並無什麼限制,但爲了能檢測

到程序員的犯下的一些錯誤,vmalloc不容許分配超過物理內存大小的內存空間。可是記 着,vmalloc函數請求過多的內存空間會產生一些和調用kmalloc函數時相同的問題。

vremap和vmalloc函數都是面向頁的(它們都會修改頁表);所以分配或釋放的內

存空間實際上都會上調爲最近的一個頁邊界。並且,vremap函數並不考慮如何重映射不 是頁邊界的物理地址。

vmalloc函數的一個小缺點是它不能在中斷時間內使用,由於它的內部實現調用

了kmalloc(GFP_KERNEL)來獲取頁表的存儲空間。但這不是什麼問題-若是__get_free_p age函數都還不能知足你的中斷處理程序的話,那你仍是先修改一下你的軟件設計吧。

使用虛擬地址的scull: scullv 使用了vmalloc的示例程序是scullv模塊。正如scullp,這個模塊也是scull的一 個變種,只是使用了不一樣的分配函數來獲取設備用以儲存數據的內存空間。

該模塊每次分配16頁的內存(在Alpha上是128KB,x86上是64KB)。這裏內存分配

用了較大的數據塊,目的是獲取比scullp更好的性能,而且代表此時使用其餘可行的分 配技術相對來講會更耗時。用__get_free_pages函數來分配一頁以上的內存空間容易出 錯,並且即便成功了,也相對較慢。前面咱們已經看到,用vmalloc分配若干頁比其餘函 數要快一些,但因爲存在創建頁表的開銷,只分配一頁時卻會慢一些。scullv設計得和s cullp很類似。order參數指定分配的內存空間的「冪」,缺省爲4。scullv和scullp的惟 一差異在下面一段代碼:

/* 此處用虛擬地址來分配一個單位內存 */

if (!dptr->data[s_pos]) {

dptr->data[s_pos] = (void *)vmalloc(PAGE_SIZE <<order);

  if (!dptr->data[s_pos])

        return -ENOMEM;



        /* 這段代碼釋放全部分配單元 */

        for (i = 0; i < qset; i++)

            if (dptr->data[i])

                vfree (dptr->data[i]);

若是你在編譯這兩個模塊時都打開了調試開關,就能夠經過讀它們在/proc下建立的文件 來查看它們進行的數據分配。下面的快照取自個人計算機,我機器的物理地址是從0到0x 1800000(共24MB):

morgana.root# cp /bin/cp /dev/scullp0

morgana.root# cat /proc/scullpmem

Device 0: qset 500, order 0, sz 19652

Item at 0063e598, qset at 006eb018

0: 150e000

     1:  de6000

     2: 10ca000

     3:  e19000

     4:  bd1000

morgana.root# cp /zImage.last /dev/scullv0

morgana.root# cat /proc/scullvmem

Device 0: qset 500, order 4, sz 289840

Item at 0063ec98, qset at 00b3e810

0: 2034000

     1: 2045000

     2: 2056000

     3: 2067000

     4: 2078000



   從這些值能夠看到,scullp分配物理地址(小於0x1800000),而scullv分配虛擬

地址(但注意實際數值與Linux 2.1會不一樣,由於虛擬地址空間的組織形式變了-見第17 章「近期發展」的「虛擬內存」一節)。

「髒」的處理方法(Playing Dirty) 若是你確實須要大量的連續的內存用做緩衝區,最簡單的(也是最不靈活的,但 也最容易出錯的)方法是在系統啓動時分配。顯然,模塊不能在啓動時分配內存;只有直 接連到內核的設備驅動程序才能運行這種「髒」的處理方式,在啓動時分配內存。

儘管在啓動時就進行分配彷佛是得到大量內存緩衝區的惟一方法,但我還會在第

13章的「分配DMA緩衝區」一節中介紹到另外一種分配技術(雖然可能更很差)。在啓動時分 配緩衝區有點「髒」,由於它跳過了內核內存管理機制。並且,這種技術普通用戶沒法 使用,由於它要修改內核。絕大多數用戶仍是願意裝載模塊,而並不肯意對內核打補定 或從新編譯內核。儘管我不推薦你使用這種「分配技術」,但它仍是值得在此說起的, 由於在GFP_DMA被引入以前,這種技術曾是Linux的早期版本里分配可用於DAM傳輸的緩衝 區的惟一方法。

讓咱們先看看啓動是是如何進行分配的。內核啓動時,它能夠訪問系統全部的內

存空間。而後以空閒內存區域的邊界做爲參數,調用內核的各個子系統的初始化函數進 行初始化。每一個初始化函數均可以「偷取」一部分空閒區域,並返回新的空閒內存下界 。因爲驅動程序是在系統啓動時進行內存分配的,因此能夠從空閒RAM的線性數組獲取連 續的內存空間。

除了不能釋放獲得的緩衝區,這種內存分配技術還有些缺點。驅動程序獲得這些

內存頁後,就沒法將它們再放到空閒頁面池中了;頁面池是在已經物理內存的分配結束 後才創建起來的,並且我也不推薦象這樣「黑客」內存管理的內部數據結構。但另外一方 面,這種技術的優點是,它能夠獲取用於DMA傳輸等用途的一段連續區域。目前這也是分 配超過32頁的連續內存緩衝區的惟一的「安全」的方式,32頁這個值是源於get_free_pa ges函數參數order可取的最大值爲5。可是若是你須要的多個內存頁能夠是物理上不連續 的,最好仍是用vmalloc函數。

若是你真要在啓動時獲取內存的話,你必須修改內核代碼中的init/main.c文件

。關於main.c文件的更多細節可參見第16章和第8章的「1M內存空間之上的ISA內存」一 節。

注意,這種「分配」只能是按頁面大小的倍數進行,而頁面數沒必要是2的某個冪

次。

快速參考 與內存分配有關的函數和符號列在下面:

#include <linux/malloc.h>

void *kmalloc(unsigned int size, int priority);

void kfree(void *obj);

這兩個函數是最經常使用的內存分配函數。

#include <linux/mm.h>

GFP_KERNEL

GFP_ATOMIC

GFP_DMA

kmalloc函數的優先權。GFP_DMA是個標誌位,能夠與GFP_KERNEL和/或GFP_ATOMIC相或。

unsigned long get_free_page(int priority);

unsigned long __get_free_page(int priority);

unsigned long __get_dma_pages(int priority, unsigned long order);

unsigned long __get_free_pages(int priority, unsigned long order,int dma);

這些都是面向頁的內存分配函數。如下劃線開頭的函數不清零分配而得的頁。只有前兩 個函數是在1.2版和2.0版的Linux間可移植的,然後二者在1.2版與2.0版中的行爲並不一樣 。

void free_page(unsigned long addr);

void free_pages(unsigned long addr, unsigned long order);

這些函數用於釋放面向頁的分配獲得的內存空間。

void* vmalloc(unsigned long size);

void* vremap(unsigned long offset, unsigned long size);

void vfree(void* addr);

這些函數分配或釋放連續的虛擬地址空間。vremap用虛擬地址訪問物理內存(在2.1版的L inux中被稱爲ioremap),而vmalloc是用來分配空閒頁面。兩種狀況下,都是用vfree來 釋放分配的內存頁。2.1版的Linux引入了頭文件<linux/vmalloc.h>,使用這些函數時必 須先包含(#include)這個頭文件。

相關文章
相關標籤/搜索