周六早上读核感悟
今天周六,因?yàn)槔掀胚€要早起備課,我也就跟著早早起來了,九點(diǎn)半就起床了,閑來無事就看了一會代碼,懶得寫代碼看代碼總可以了吧,主要看了兩塊,都是關(guān)于內(nèi)存的,一個是內(nèi)核高端臨時映射,一個是伙伴系統(tǒng)的per_cpu_pages中的冷熱頁面隊(duì)列,一個一個說。
關(guān)于高端映射的原理我就不多說了,內(nèi)核代碼很清晰,基本就是在虛擬內(nèi)存的高端為每個cpu都預(yù)留了一片區(qū)域:
enum fixed_addresses {
...
#ifdef CONFIG_HIGHMEM
FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */
FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1, //為每一個cpu都留一片區(qū)域
...
__end_of_fixed_addresses
};
enum km_type {
KM_BOUNCE_READ,
KM_SKB_SUNRPC_DATA,
KM_SKB_DATA_SOFTIRQ,
KM_USER0, //操作用戶數(shù)據(jù)時用
KM_USER1,
KM_BIO_SRC_IRQ,
KM_BIO_DST_IRQ,
KM_PTE0,
KM_PTE1,
KM_IRQ0, //中斷用
KM_IRQ1,
KM_SOFTIRQ0, //軟中斷用
KM_SOFTIRQ1,
KM_TYPE_NR //類型的數(shù)量
};
static void __init kmap_init(void)
{
unsigned long kmap_vstart;
kmap_vstart = __fix_to_virt(FIX_KMAP_BEGIN);
kmap_pte = kmap_get_fixmap_pte(kmap_vstart);
kmap_prot = PAGE_KERNEL;
}
void *kmap_atomic(struct page *page, enum km_type type)
{
enum fixed_addresses idx;
unsigned long vaddr;
inc_preempt_count();
if (!PageHighMem(page))
return page_address(page);
idx = type + KM_TYPE_NR*smp_processor_id(); //找到屬于本cpu的那一片臨時映射區(qū)域
vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);
set_pte(kmap_pte-idx, mk_pte(page, kmap_prot));
__flush_tlb_one(vaddr);
return (void*) vaddr;
}
上面的過程簡單而又清晰,在內(nèi)核代碼中很多地方用到了KM_USER0/1的地方,說不能在中斷上下文調(diào)用,這到底是為什么?而且在上面的代碼可以看出來,映射的時候,根本不管這個地址是否已經(jīng)有了映射,而是直接映射進(jìn)去,那么可想而知,如果原來有映射的話,那么數(shù)據(jù)肯定就被破壞了,其實(shí)也不是破壞,而是訪問者的頁面被默默更換了,這可能會釀成事故,我們一般要求虛擬地址是頁面獨(dú)占的,也就是說一個虛擬地址必須對應(yīng)僅一個頁面,除了這個臨時映射,別的映射都可以保證這一點(diǎn),對于用戶映射,如果不是因?yàn)槿表?#xff0c;那是不會映射新頁面的,因此問題解決,對于內(nèi)核一一映射,初始化時就映射好了,以后也不會改變,對于vmalloc動態(tài)映射,vm_struct鏈表可以保證一個地址如果已經(jīng)被映射就不再會有別的映射,對于kmap映射的永久映射,last_pkmap_nr也會保證這一點(diǎn),但是對于高端臨時映射卻沒有任何保證,這該如何是好,這難道是內(nèi)核的不周嗎?如果這樣想的話就錯了,前面幾種映射是有保證,但是這種保證的代價就是沒有話你就必須等,對于不允許等待的操作或者快速操作,有必要提供一個特殊通道給它們用,就好比大型超市結(jié)賬的地方都有快速區(qū),你如果就買一瓶純凈水,那么就不必排隊(duì)了,內(nèi)核也是這樣。快速通道就是專門為一些快速操作提供的,但是內(nèi)核是不允許出錯的,為了防止意外,比如不能保證沒有兩個快速操作同時需要映射,那么就要進(jìn)行排隊(duì),而排隊(duì)就意味著等待,這好像又回到了前面的那些映射方式,這似乎是一個怪圈,如果按照這種線性的思路考慮的話,就不可能找到一種截然不同的又不用等待又不會出錯的解決方案,那么內(nèi)核可以講排隊(duì)的縱向過程鋪開成橫向的多個窗口,也就有了上面的臨時映射的設(shè)計(jì)方式,km_type中的就是這些窗口,用戶當(dāng)然可以隨便使用,但是出了問題內(nèi)核概不負(fù)責(zé),因此把規(guī)則交給用戶自覺遵守而不是靠內(nèi)核來扶正,這就是最好的解決方案,這個方案不用等待,來了一個用戶頁面的映射,那么由于一個cpu一個時間只能有一個用戶進(jìn)程陷入內(nèi)核,那么為每個cpu都提供一個km_type窗口的話所有問題就解決了,臨時映射用戶頁面的時候,內(nèi)核執(zhí)行緒十分了解自己的行為,它將該頁面映射到自己cpu的KM_USER0,或者KM_USER1,然后就可以安心的操作了,因?yàn)閮?nèi)核固定了只有操作用戶頁面的才可以映射到這兩個窗口,這個進(jìn)程之所以安心是因?yàn)樗嘈艅e的執(zhí)行緒會遵守這個規(guī)定的,另外對它本身的限制就是它的操作必須快速而且不許睡眠,當(dāng)然內(nèi)核也不能搶占,這是靠遞增搶占標(biāo)志做到的。km_type窗口中的每一個都有自己的用途,用戶只要按照規(guī)定使用就沒有問題,這個特殊的快速通道確實(shí)起到了快速操作的作用。
關(guān)于伙伴系統(tǒng)的per_cpu_pages中的冷熱頁面隊(duì)列前面我就研究過一段時間,如果是進(jìn)程的頁面釋放,那么就釋放到熱隊(duì)列中,如果是設(shè)備的頁面,那么就釋放到冷隊(duì)列中,因?yàn)樵O(shè)備使用的僅僅是頁面,比如DMA,它是不經(jīng)過cpu的,因此頁面數(shù)據(jù)就不會被cpu的cache緩存,其實(shí)很多硬件外設(shè)沒有MMU,因此要求為硬件外設(shè)分配內(nèi)存的時候必須為它指定物理地址,并且地址必須連續(xù),把它想成實(shí)模式cpu就可以了。外設(shè)不被cpu緩存,那么外設(shè)的頁面置入冷隊(duì)列,這樣在釋放頁面的時候,伙伴系統(tǒng)的熱隊(duì)列頁面數(shù)量肯定比全部數(shù)量少,這樣當(dāng)有頁面分配需要的時候,如果是cpu需要頁面,那么直接從熱隊(duì)列分配,此時很可能數(shù)據(jù)還在cpu緩存中,效率會大大提高,即使你不用老頁面的數(shù)據(jù)而是使用一個零頁面,那么在memset(addr,0,PAGE_SIZE)的時候也會提高效率,因?yàn)閏pu操作的是虛擬地址,總線上才是物理地址。頁面總是順序排列的,不同的是它的虛擬地址可能每次都不同,然而cpu最終訪問的頁面,此時已經(jīng)過了mmu,cpu是以物理地址操作cache的而不是虛擬地址,因此緩存熱頁面才有意義。
轉(zhuǎn)載于:https://blog.51cto.com/dog250/1273506
總結(jié)
- 上一篇: ClearTextBox.Text
- 下一篇: linux内核的反复--一切都是过程