• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            統計

            • 隨筆 - 50
            • 文章 - 42
            • 評論 - 147
            • 引用 - 0

            留言簿(6)

            隨筆分類

            文章分類

            Link

            搜索

            •  

            積分與排名

            • 積分 - 164634
            • 排名 - 159

            最新評論

            閱讀排行榜

            評論排行榜

            kmalloc vmalloc

            在設備驅動程序中動態開辟內存,不是用malloc,而是kmalloc,或者用get_free_pages直接申請頁。釋放內存用的是kfree,或free_pages.

               對于提供了MMU(存儲管理器,輔助操作系統進行內存管理,提供虛實地址轉換等硬件支持)的處理器而言,Linux提供了復雜的存儲管理系統,使得進程所能訪問的內存達到4GB。

              進程的4GB內存空間被人為的分為兩個部分--用戶空間與內核空間。用戶空間地址分布從0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB為內核空間。

              內核空間中,從3G到vmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁框表mem_map等等),比如我們使用的 VMware虛擬系統內存是160M,那么3G~3G+160M這片內存就應該映射物理內存。在物理內存映射區之后,就是vmalloc區域。對于 160M的系統而言,vmalloc_start位置應在3G+160M附近(在物理內存映射區與vmalloc_start期間還存在一個8M的gap 來防止躍界),vmalloc_end的位置接近4G(最后位置系統會保留一片128k大小的區域用于專用頁面映射)

                 kmalloc和get_free_page申請的內存位于物理內存映射區域,而且在物理上也是連續的,它們與真實的物理地址只有一個固定的偏移,因此存在較簡單的轉換關系,virt_to_phys()可以實現內核虛擬地址轉化為物理地址:
               #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
               extern inline unsigned long virt_to_phys(volatile void * address)
               {
                    return __pa(address);
               }
            上面轉換過程是將虛擬地址減去3G(PAGE_OFFSET=0XC000000)。

            與之對應的函數為phys_to_virt(),將內核物理地址轉化為虛擬地址:
               #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
               extern inline void * phys_to_virt(unsigned long address)
               {
                    return __va(address);
               }
            virt_to_phys()和phys_to_virt()都定義在include\asm-i386\io.h中。

            -------------------------------------------------------------------------------------
            1、kmalloc() 分配連續的物理地址,用于小內存分配。

              2、__get_free_page() 分配連續的物理地址,用于整頁分配。

              至于為什么說以上函數分配的是連續的物理地址和返回的到底是物理地址還是虛擬地址,下面的記錄會做出解釋。

              kmalloc() 函數本身是基于 slab 實現的。slab 是為分配小內存提供的一種高效機制。但 slab 這種分配機制又不是獨立的,它本身也是在頁分配器的基礎上來劃分更細粒度的內存供調用者使用。也就是說系統先用頁分配器分配以頁為最小單位的連續物理地址,然后 kmalloc() 再在這上面根據調用者的需要進行切分。

              關于以上論述,我們可以查看 kmalloc() 的實現,kmalloc()函數的實現是在 __do_kmalloc() 中,可以看到在 __do_kmalloc()代碼里最終調用了 __cache_alloc() 來分配一個 slab,其實

              kmem_cache_alloc() 等函數的實現也是調用了這個函數來分配新的 slab。我們按照 __cache_alloc()函數的調用路徑一直跟蹤下去會發現在 cache_grow() 函數中使用了 kmem_getpages()函數來分配一個物理頁面,kmem_getpages() 函數中調用的alloc_pages_node() 最終是使用 __alloc_pages() 來返回一個struct page 結構,而這個結構正是系統用來描述物理頁面的。這樣也就證實了上面所說的,slab 是在物理頁面基礎上實現的。kmalloc() 分配的是物理地址。

              __get_free_page() 是頁面分配器提供給調用者的最底層的內存分配函數。它分配連續的物理內存。__get_free_page() 函數本身是基于 buddy 實現的。在使用 buddy 實現的物理內存管理中最小分配粒度是以頁為單位的。關于以上論述,我們可以查看__get_free_page()的實現,可以看到__get_free_page()函數只是一個非常簡單的封狀,它的整個函數實現就是無條件的調用 __alloc_pages() 函數來分配物理內存,上面記錄 kmalloc()實現時也提到過是在調用 __alloc_pages() 函數來分配物理頁面的前提下進行的 slab 管理。那么這個函數是如何分配到物理頁面又是在什么區域中進行分配的?回答這個問題只能看下相關的實現。可以看到在 __alloc_pages() 函數中,多次嘗試調用get_page_from_freelist() 函數從 zonelist 中取得相關 zone,并從其中返回一個可用的 struct page 頁面(這里的有些調用分支是因為標志不同)。至此,可以知道一個物理頁面的分配是從 zonelist(一個 zone 的結構數組)中的 zone 返回的。那么 zonelist/zone 是如何與物理頁面關聯,又是如何初始化的呢?繼續來看 free_area_init_nodes() 函數,此函數在系統初始化時由 zone_sizes_init() 函數間接調用的,zone_sizes_init()函數填充了三個區域:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。并把他們作為參數調用 free_area_init_nodes(),在這個函數中會分配一個 pglist_data 結構,此結構中包含了 zonelist/zone結構和一個 struct page 的物理頁結構,在函數最后用此結構作為參數調用了 free_area_init_node() 函數,在這個函數中首先使用 calculate_node_totalpages() 函數標記 pglist_data 相關區域,然后調用 alloc_node_mem_map() 函數初始化 pglist_data結構中的 struct page 物理頁。最后使用 free_area_init_core()函數關聯 pglist_data 與 zonelist。現在通以上分析已經明確了__get_free_page() 函數分配物理內存的流程。但這里又引出了幾個新問題,那就是此函數分配的物理頁面是如何映射的?映射到了什么位置?到這里不得不去看下與 VMM 相關的引導代碼。

              在看 VMM 相關的引導代碼前,先來看一下 virt_to_phys() 與phys_to_virt 這兩個函數。顧名思義,即是虛擬地址到物理地址和物理地址到虛擬地址的轉換。函數實現十分簡單,前者調用了__pa( address ) 轉換虛擬地址到物理地址,后者調用 __va(addrress ) 將物理地址轉換為虛擬地址。再看下 __pa __va 這兩個宏到底做了什么。

              #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
              #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))

              通過上面可以看到僅僅是把地址加上或減去 PAGE_OFFSET,而PAGE_OFFSET 在 x86 下定義為 0xC0000000。這里又引出了疑問,在 linux 下寫過 driver 的人都知道,在使用 kmalloc() 與

              __get_free_page() 分配完物理地址后,如果想得到正確的物理地址需要使用 virt_to_phys() 進行轉換。那么為什么要有這一步呢?我們不分配的不就是物理地址么?怎么分配完成還需要轉換?如果返回的是虛擬地址,那么根據如上對 virt_to_phys() 的分析,為什么僅僅對 PAGE_OFFSET 操作就能實現地址轉換呢?虛擬地址與物理地址之間的轉換不需要查頁表么?代著以上諸多疑問來看 VMM 相關的引導代碼。

              直接從 start_kernel() 內核引導部分來查找 VMM 相關內容。可以看到第一個應該關注的函數是 setup_arch(),在這個函數當中使用paging_init() 函數來初始化和映射硬件頁表(在初始化前已有 8M內存被映射,在這里不做記錄),而 paging_init() 則是調用的pagetable_init() 來完成內核物理地址的映射以及相關內存的初始化。在 pagetable_init() 函數中,首先是一些 PAE/PSE/PGE 相關判斷與設置,然后使用 kernel_physical_mapping_init() 函數來實現內核物理內存的映射。在這個函數中可以很清楚的看到,pgd_idx 是以PAGE_OFFSET 為啟始地址進行映射的,也就是說循環初始化所有物理地址是以 PAGE_OFFSET 為起點的。繼續觀察我們可以看到在 PMD 被初始化后,所有地址計算均是以 PAGE_OFFSET 作為標記來遞增的。分析到這里已經很明顯的可以看出,物理地址被映射到以 PAGE_OFFSET 開始的虛擬地址空間。這樣以上所有疑問就都有了答案。kmalloc() 與__get_free_page() 所分配的物理頁面被映射到了 PAGE_OFFSET 開始的虛擬地址,也就是說實際物理地址與虛擬地址有一組一一對應的關系,

              正是因為有了這種映射關系,對內核以 PAGE_OFFSET 啟始的虛擬地址的分配也就是對物理地址的分配(當然這有一定的范圍,應該在 PAGE_OFFSET與 VMALLOC_START 之間,后者為 vmalloc() 函數分配內存的啟始地址)。這也就解釋了為什么 virt_to_phys() 與 phys_to_virt() 函數的實現僅僅是加/減 PAGE_OFFSET 即可在虛擬地址與物理地址之間轉換,正是因為了有了這種映射,且固定不變,所以才不用去查頁表進行轉換。這也同樣回答了開始的問題,即 kmalloc() / __get_free_page() 分配的是物理地址,而返回的則是虛擬地址(雖然這聽上去有些別扭)。正是因為有了這種映射關系,所以需要將它們的返回地址減去 PAGE_OFFSET 才可以得到真正的物理地址。

            另一篇更容易理解的:

            kmalloc, vmalloc分配的內存結構 zz
            2008-01-20 16:05
            進程空間:| <-用戶空間-> | <-內核空間-> |
            內核空間:| <-物理內存映射區-> | <-vmalloc區域-> |

            ==============原文================================

               對于提供了MMU(存儲管理器,輔助操作系統進行內存管理,提供虛實地址轉換等硬件支持)的處理器而言,Linux提供了復雜的存儲管理系統,使得進程所能訪問的內存達到4GB。

            進程的4GB內存空間被人為的分為兩個部分--用戶空間與內核空間。用戶空間地址分布從0到3GB(PAGE_OFFSET,在0x86中它等于0xC0000000),3GB到4GB為內核空間。

            內核空間中,從3G到vmalloc_start這段地址是物理內存映射區域(該區域中包含了內核鏡像、物理頁框表mem_map等等),比如我們使用 的 VMware虛擬系統內存是160M,那么3G~3G+160M這片內存就應該映射物理內存。在物理內存映射區之后,就是vmalloc區域。對于 160M的系統而言,vmalloc_start位置應在3G+160M附近(在物理內存映射區與vmalloc_start期間還存在一個8M的gap 來防止躍界),vmalloc_end的位置接近4G(最后位置系統會保留一片128k大小的區域用于專用頁面映射)

            kmalloc和get_free_page申請的內存位于物理內存映射區域,而且在物理上也是連續的,它們與真實的物理地址只有一個固定的偏移,因此存在較簡單的轉換關系,virt_to_phys()可以實現內核虛擬地址轉化為物理地址:
               #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
               extern inline unsigned long virt_to_phys(volatile void * address)
               {
                    return __pa(address);
               }
            上面轉換過程是將虛擬地址減去3G(PAGE_OFFSET=0XC000000)。

            與之對應的函數為phys_to_virt(),將內核物理地址轉化為虛擬地址:
               #define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
               extern inline void * phys_to_virt(unsigned long address)
               {
                    return __va(address);
               }
            virt_to_phys()和phys_to_virt()都定義在include\asm-i386\io.h中。

            而vmalloc申請的內存則位于vmalloc_start~vmalloc_end之間,與物理地址沒有簡單的轉換關系,雖然在邏輯上它們也是連續的,但是在物理上它們不要求連續。

            我們用下面的程序來演示kmalloc、get_free_page和vmalloc的區別:
            #include <linux/module.h>
            #include <linux/slab.h>
            #include <linux/vmalloc.h>
            MODULE_LICENSE("GPL");
            unsigned char *pagemem;
            unsigned char *kmallocmem;
            unsigned char *vmallocmem;

            int __init mem_module_init(void)
            {
            //最好每次內存申請都檢查申請是否成功
            //下面這段僅僅作為演示的代碼沒有檢查
            pagemem = (unsigned char*)get_free_page(0);
            printk("<1>pagemem addr=%x", pagemem);

            kmallocmem = (unsigned char*)kmalloc(100, 0);
            printk("<1>kmallocmem addr=%x", kmallocmem);

            vmallocmem = (unsigned char*)vmalloc(1000000);
            printk("<1>vmallocmem addr=%x", vmallocmem);

            return 0;
            }

            void __exit mem_module_exit(void)
            {
            free_page(pagemem);
            kfree(kmallocmem);
            vfree(vmallocmem);
            }

            module_init(mem_module_init);
            module_exit(mem_module_exit);

            我們的系統上有160MB的內存空間,運行一次上述程序,發現pagemem的地址在0xc7997000(約3G+121M)、kmallocmem 地址在0xc9bc1380(約3G+155M)、vmallocmem的地址在0xcabeb000(約3G+171M)處,符合前文所述的內存布局。

             

            本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/sandflee/archive/2009/07/28/4388322.aspx

            posted on 2009-11-17 17:18 pear_li 閱讀(1350) 評論(0)  編輯 收藏 引用 所屬分類: Linux/Unix

            婷婷综合久久中文字幕| 欧美日韩成人精品久久久免费看| 三上悠亚久久精品| 国内精品久久久人妻中文字幕| 人妻无码αv中文字幕久久 | 久久精品国产亚洲AV蜜臀色欲| 亚洲国产婷婷香蕉久久久久久| 国产成人无码精品久久久性色| 久久久一本精品99久久精品66| 91精品国产91久久久久久| 亚洲国产成人精品女人久久久 | 久久发布国产伦子伦精品| 日韩精品久久久久久| 久久只有这精品99| 国产精品久久久久久久久鸭| 久久精品免费大片国产大片| 亚洲精品无码久久千人斩| 久久免费小视频| 亚洲中文字幕无码久久2017| 国产成人综合久久精品尤物| 精产国品久久一二三产区区别| 久久精品国产69国产精品亚洲| 亚洲欧洲中文日韩久久AV乱码| 2021少妇久久久久久久久久| 亚洲精品无码久久久久AV麻豆| 国产精品久久永久免费| 中文字幕精品久久久久人妻| 亚洲国产精久久久久久久| 久久精品国产2020| 久久久网中文字幕| 久久精品视频网| 久久影院综合精品| 久久人人爽人人人人爽AV| 国内精品伊人久久久久网站| 国产人久久人人人人爽| 伊人久久大香线蕉av不变影院| 久久精品一区二区三区中文字幕| 国产V综合V亚洲欧美久久| 久久综合九色综合网站| 老司机午夜网站国内精品久久久久久久久 | 亚洲中文字幕无码久久2017|