kmap_atomic的細節以及改進

來源:互聯網
上載者:User

kmap_atomic用於高端記憶體映射,用於緊急的,短時間的映射,它沒有使用任何鎖,完全靠一個數學公式來避免混亂,它空間有限且虛擬位址固定,這意味著它映射的記憶體不能長期被佔用而不被unmap,kmap_atomic在效率上要比kmap提升不少,然而它和kmap卻不是用於同一場合的,kmap用於休眠的情況而kmap_atomic則用在不能休眠的情況下(如中斷處理常式等不會被重新調度的時候)。不管怎麼說,它的設計是很完美的。
     kernel可以在多個cpu上同時運行不同的task,然而它們共同使用一個記憶體位址空間,也就是說,地址空間對於多個cpu看到的是同一個,kmap_atomic使用的是地址空間頂部的一小段地址空間,核心邏輯將這一小段地址空間分成了若干個節,每一節的大小是一個page的大小,可以用來映射一個page,根據公用地址空間的原理,所有的cpu共同使用這些節,因此如何能保證N個cpu調用kmap_atomic不會將page映射到一個地址呢?這就是這個數學公式所起的作用:
idx = type + KM_TYPE_NR*smp_processor_id();
vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
其中KM_TYPE_NR代表type的最大值加1:
enum km_type {
    KM_BOUNCE_READ,
    KM_SKB_SUNRPC_DATA,
    KM_SKB_DATA_SOFTIRQ,
    KM_USER0,
    KM_USER1,
...
    KM_TYPE_NR
};
調用kmap_atomic的時候,有一個參數就是上述的枚舉類型km_type,這樣不同的cpu得到的vaddr就不可能一樣了,原因是這樣的:type + KM_TYPE_NR*smp_processor_id()可以寫成z=x+N*y(x<N),只要y不同,z就一定不會相同,因為z=x+N*y<N+N*y=(N+1)*y,設現有y1<y2(二者最少相差1),則若z1=z2,由於y1*N-y2*N>N,必有x1-x2>N,而x1和x2在0-N的範圍內,因此這是不可能的,所以只要cpu不同,它們是不可能映射到同一虛擬位址的,也就是不會導致衝突的發生,最終通過__fix_to_virt(FIX_KMAP_BEGIN + idx)得到映射後的虛擬位址,該__fix_to_virt宏基本是一個常量轉換,根據idx找到虛擬位址空間最高處的那個屬於本次映射的小段。
     再看kmap_atomic這個api的原形:void *kmap_atomic(struct page *page, enum km_type type),有個參數是type,也就是說具體映射到哪一段是由調用者來決定的,可是這真的有必要嗎?調用者無非需要的是一個虛擬位址而已,它不管一個page具體映射到哪個虛擬位址,就像kmap做的那樣,原則上說,kmap_atomic提供的僅僅是底層的一個實現機制,一個介面,它完全可以用不同的方式實現,調用者實在沒有必要牽扯進這個底層的細節問題,因此km_type是沒有必要的,故而2.6.37核心中果斷地去除了這個km_type,現如今2.6.37核心的kmap_atomic的實現如下:
void *kmap_atomic_prot(struct page *page, pgprot_t prot)
{
    unsigned long vaddr;
    int idx, type;
    pagefault_disable(); //原子映射是基於每cpu的,因此在當前cpu上禁用搶佔,直到unmap的時候才開啟,這樣就不會導致原子映射的重入了,畢竟如果禁用搶佔的話,調用者進程在開啟搶佔之前別的進程是不可能在核心空間運行,除非該進程在unmap之前睡眠,如果真的那樣,別的進程就很可能在同一cpu重入kmap_atomic_prot了,然後就可能映射到同一虛擬位址(在當前2.6.37版本內部分解決了這個問題,見下面的push/pop),因此有人說原子映射期間進程不允許睡眠
    if (!PageHighMem(page)) //非高端頁面直接返回一一映射地址
        return page_address(page);
    type = kmap_atomic_idx_push();  //遞增一個每cpu變數,返回遞增後的結果
    idx = type + KM_TYPE_NR*smp_processor_id();
    vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
    set_pte(kmap_pte-idx, mk_pte(page, prot)); //設定頁表
    return (void *)vaddr;
}
static inline int kmap_atomic_idx_push(void)
{
    int idx = __get_cpu_var(__kmap_atomic_idx)++;
#ifdef CONFIG_DEBUG_HIGHMEM
    WARN_ON_ONCE(in_irq() && !irqs_disabled());
    BUG_ON(idx > KM_TYPE_NR);  //這個BUG_ON提醒__kmap_atomic_idx不能超過KM_TYPE_NR,原因同老版本的一樣
#endif
    return idx;
}
可見新版本的原子映射中沒有了km_type參數,只有一個page參數,完全靠一個stack實現了類似km_type的機制,在unmap的時候會調用__get_cpu_var(__kmap_atomic_idx)--將這個變數遞減掉。看了這個新版本的實現之後,我們會發現,既然調用kmap_atomic_prot的時候禁用了搶佔,如果進程不主動睡眠的話,在單一的cpu上__kmap_atomic_idx一般是不會大於1的,那麼push中的BUG_ON當然就是杞人憂天了(除非使用一個原子映射期間又進行了另一個原子映射),然而如果原子映射的調用者睡眠了的話,誰也再來一個映射也不會和前面的重合,因為__kmap_atomic_idx此時遞增了,然後這個進程也睡眠了,喚醒了原來的那個原子映射的調用者,該進程unmap了它的那個原子映射,很不巧,它釋放的是第二個進程映射的頁面...因此還是不要在使用原子映射之間睡眠。
     那麼新版本的原子映射有沒有什麼缺點呢?kmap_atomic_idx_push和kmap_atomic_idx_pop都是函數,增加了幾次函數調用並沒有什麼(它們都是inline的),最重要的是增加了幾個變數的訪問和操作--__kmap_atomic_idx

 

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/dog250/archive/2011/01/11/6129826.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.