不卡AV在线|网页在线观看无码高清|亚洲国产亚洲国产|国产伦精品一区二区三区免费视频

學(xué)習(xí)啦>學(xué)習(xí)電腦>網(wǎng)絡(luò)知識(shí)>網(wǎng)絡(luò)基礎(chǔ)知識(shí)> 虛擬地址物理地址linux

虛擬地址物理地址linux

時(shí)間: 春健736 分享

虛擬地址物理地址linux

  ?學(xué)習(xí)啦小編整理了linux環(huán)境下虛擬地址物理地址的相關(guān)資料。供大家參考!

  虛擬地址物理地址linux

  內(nèi)核從3G開(kāi)始的那一段是連續(xù)映射

  而且這種固定映射最大到896M的地址范圍,也即從0xc0000000-0xf7ffffff的虛擬地址采用固定映射,稱為內(nèi)核邏輯地址.剩下的1G-896=128M范圍的虛擬地址可以映射到任意物理地址.稱為內(nèi)核虛擬地址.當(dāng)實(shí)際內(nèi)存大于1G時(shí)(實(shí)際上是> 896M時(shí)),用這塊地址空間做映射.

  實(shí)際的計(jì)算機(jī)體系結(jié)構(gòu)有硬件的制約,這限制了頁(yè)框可以使用的方式。尤其是,Linux內(nèi)核必須處理80x86體系結(jié)構(gòu)的兩種硬件約束:

  ISA總線的直接存儲(chǔ)器(DMA)處理器有一個(gè)嚴(yán)格的限制:它們只能對(duì)RAM的前16MB尋址。

  在具有大容量RAM的現(xiàn)代32位計(jì)算機(jī)中,CPU不能直接訪問(wèn)所有的物理存儲(chǔ)器,因?yàn)榫€形地址空間太小。

  為了應(yīng)付這兩種限制,Linux把物理存儲(chǔ)器劃分為三個(gè)管理區(qū)(zone):

  ZONE_DMA:包含低于16MB的存儲(chǔ)器頁(yè)

  ZONE_NORMAL:包含高于16MB且低于896MB的存儲(chǔ)器頁(yè)

  ZONE_HIGHMEM:包含高于896MB的存儲(chǔ)器頁(yè)

  ZONE_DMA區(qū)包含的頁(yè)可以由老式基于ISA的設(shè)備通過(guò)DMA使用。

  ZONE_DMA和ZONE_NORMAL和區(qū)包含的存儲(chǔ)器的“常規(guī)”頁(yè),通過(guò)把它們線性地映射到線性地址空間的第4個(gè)GB,內(nèi)核就可以直接進(jìn)行訪問(wèn)。相反,包含的存儲(chǔ)器頁(yè)不能由內(nèi)核直接訪問(wèn),但它們也線性映射到了線性地址空間的第4個(gè)GB。在64位體系結(jié)構(gòu)上沒(méi)有使用在64位體系結(jié)構(gòu)上沒(méi)有使用ZONE_NORMAL。

  這里只分析分配連續(xù)物理地址的函數(shù)。對(duì)于 vmalloc() 這種分配非連續(xù)物理地址的函數(shù)不在本記錄范圍之內(nèi)。

  1、kmalloc() 分配連續(xù)的物理地址,用于小內(nèi)存分配。

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

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

  kmalloc() 函數(shù)本身是基于 slab 實(shí)現(xiàn)的。slab是為分配小內(nèi)存提供的一種高效機(jī)制。但 slab 這種分配機(jī)制又不是獨(dú)立的,它本身也是在頁(yè)分配器的基礎(chǔ)上來(lái)劃分更細(xì)粒度的內(nèi)存供調(diào)用者使用。也就是說(shuō)系統(tǒng)先用頁(yè)分配器分配以頁(yè)為最小單位的連續(xù)物理地址,然后 kmalloc() 再在這上面根據(jù)調(diào)用者的需要進(jìn)行切分。關(guān)于以上論述,我們可以查看malloc() 的實(shí)現(xiàn),kmalloc()函數(shù)的實(shí)現(xiàn)是在 __do_kmalloc() 中,可以看到在__do_kmalloc() 代碼里最終調(diào)用了_cache_alloc() 來(lái)分配一個(gè)slab,其實(shí)kmem_cache_alloc() 等函數(shù)的實(shí)現(xiàn)也是調(diào)用了這個(gè)函數(shù)來(lái)分配新的 slab。我們按_cache_alloc() 函數(shù)的調(diào)用路徑一直跟蹤下去會(huì)發(fā)現(xiàn)在 cache_grow() 函數(shù)中使用了kmem_getpages() 函數(shù)來(lái)分配一個(gè)物理,kmem_getpages() 函數(shù)中調(diào)用的alloc_pages_node() 最終是使用 __alloc_pages() 來(lái)返回一個(gè)struct page 結(jié)構(gòu),而這個(gè)結(jié)構(gòu)正是系統(tǒng)用來(lái)描述物理頁(yè)面的。這樣也就證實(shí)了上面所說(shuō)的,slab 是在物理頁(yè)面基礎(chǔ)上實(shí)現(xiàn)的。kmalloc() 分配的是物理地址。

  __get_free_page() 是頁(yè)面分配器提供給調(diào)用者的最底層的內(nèi)存分配函數(shù)。它分配連續(xù)的物理內(nèi)。__get_free_page() 函數(shù)本身是基于 buddy 實(shí)現(xiàn)的。在使用 buddy 實(shí)現(xiàn)的物理內(nèi)存管理中最小分配粒度是以頁(yè)為單位的。關(guān)于以上論述,我們可以查看__get_free_page() 的實(shí)現(xiàn),可以看到 __get_free_page() 函數(shù)只是一個(gè)非常簡(jiǎn)單的封狀,它的整個(gè)函數(shù)實(shí)現(xiàn)就是無(wú)條件的調(diào)用 __alloc_pages() 函數(shù)來(lái)分配物理內(nèi)存,上面記錄 kmalloc()實(shí)現(xiàn)時(shí)也提到過(guò)是在調(diào)用_alloc_pages() 函數(shù)來(lái)分配物理頁(yè)面的前提下進(jìn)行的 slab 管理。那么這個(gè)函數(shù)是如何分配到物理頁(yè)面又是在什么區(qū)域中進(jìn)行分配的?回答這個(gè)問(wèn)題只能看下相關(guān)的實(shí)現(xiàn)??梢钥吹皆?__alloc_pages() 函數(shù)中,多次嘗試調(diào)用get_page_from_freelist() 函數(shù)從 zonelist 中取得相關(guān) zone,并從其中返回一個(gè)可用的 struct page 頁(yè)面(這里的有些調(diào)用分支是因?yàn)闃?biāo)志不同)。至此,可以知道一個(gè)物理頁(yè)面的分配是從 zonelist(一個(gè) zone 的結(jié)構(gòu)數(shù)組)中的 zone 返回的。那么 zonelist/zone 是如何與物理頁(yè)面關(guān)聯(lián),又是如何初始化的呢?繼續(xù)來(lái)看 free_area_init_nodes() 函數(shù),此函數(shù)在系統(tǒng)初始化時(shí)由 zone_sizes_init() 函數(shù)間接調(diào)用的,zone_sizes_init()

  函數(shù)填充了三個(gè)區(qū)域:ZONE_DMA,ZONE_NORMAL,ZONE_HIGHMEM。并把他們作為參數(shù)調(diào)用 free_area_init_nodes(),在這個(gè)函數(shù)中會(huì)分配一個(gè) pglist_data 結(jié)構(gòu),此結(jié)構(gòu)中包含了zonelist/zone結(jié)構(gòu)和一個(gè) struct page 的物理頁(yè)結(jié)構(gòu),在函數(shù)最后用此結(jié)構(gòu)作為參數(shù)調(diào)用了 free_area_init_node() 函數(shù),在這個(gè)函數(shù)中首先使用 calculate_node_totalpages() 函數(shù)標(biāo)記 pglist_data 相關(guān)區(qū)域,然后調(diào)用 alloc_node_mem_map() 函數(shù)初始化 pglist_data結(jié)構(gòu)中的 struct page 物理頁(yè)。最后使用free_area_init_core()函數(shù)關(guān)聯(lián) pglist_data 與 zonelist??梢?jiàn)__get_free_page()是從buddy systems分配的頁(yè)框?,F(xiàn)在通以上分析已經(jīng)明確了__get_free_page() 函數(shù)分配物理內(nèi)存的流程。但這里又引出了幾個(gè)新問(wèn)題,那就是此函數(shù)分配的物理頁(yè)面是如何映射的?映射到了什么位置?到這里不得不去看下與 VMM 相關(guān)的引導(dǎo)代碼。

  在看 VMM 相關(guān)的引導(dǎo)代碼前,先來(lái)看一下virt_to_phys() 與phys_to_virt 這兩個(gè)函數(shù)。顧名思義,即是虛擬地址到物理地址和物理地址到虛擬地址的轉(zhuǎn)換。函數(shù)實(shí)現(xiàn)十分簡(jiǎn)單,前者調(diào)用了__pa( address ) 轉(zhuǎn)換虛擬地址到物理地址,后者調(diào)用 __va( addrress ) 將物理地址轉(zhuǎn)換為虛擬地址。再看下 __pa __va 這兩個(gè)宏到底做了什么。

  #define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)

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

  通過(guò)上面可以看到僅僅是把地址加上或減去 PAGE_OFFSET,而PAGE_OFFSET 在 x86 下定義為0xC0000000。這里又引出了疑問(wèn),在 linux 下寫(xiě)過(guò) driver 的人都知道,在使用 kmalloc() 與__get_free_page() 分配完物理地址后,如果想得到正確的物理地址需要使用 virt_to_phys() 進(jìn)行轉(zhuǎn)換。那么為什么要有這一步呢?我們不分配的不就是物理地址么?怎么分配完成還需要轉(zhuǎn)換?如果返回的是虛擬地址,那么根據(jù)如上對(duì) virt_to_phys() 的分析,為什么僅僅對(duì) PAGE_OFFSET 操作就能實(shí)現(xiàn)地址轉(zhuǎn)換呢?虛擬地址與物理地址之間的轉(zhuǎn)換不需要查頁(yè)表么?代著以上諸多疑問(wèn)來(lái)看 VMM 相關(guān)的引導(dǎo)代碼。

  直接從 start_kernel() 內(nèi)核引導(dǎo)部分來(lái)查找VMM 相關(guān)內(nèi)容??梢钥吹降谝粋€(gè)應(yīng)該關(guān)注的函數(shù)是 setup_arch(),在這個(gè)函數(shù)當(dāng)中使用paging_init() 函數(shù)來(lái)初始化和映射硬件頁(yè)表(在初始化前已有 8M內(nèi)存被映射,在這里不做記錄),而 paging_init() 則是調(diào)用的pagetable_init() 來(lái)完成內(nèi)核物理地址的映射以及相關(guān)內(nèi)存的初始化。在pagetable_init() 函數(shù)中,首先是一些PAE/PSE/PGE 相關(guān)判斷與設(shè)置,然后使用 kernel_physical_mapping_init() 函數(shù)來(lái)實(shí)現(xiàn)內(nèi)核物理內(nèi)存的映射。在這個(gè)函數(shù)中可以很清楚的看到,pgd_idx 是以PAGE_OFFSET 為啟始地址進(jìn)行映射的,也就是說(shuō)循環(huán)初始化所有物理地址是以 PAGE_OFFSET 為起點(diǎn)的。繼續(xù)觀察我們可以看到在 PMD 被初始化后,所有地址計(jì)算均是以 PAGE_OFFSET 作為標(biāo)記來(lái)遞增的。分析到這里已經(jīng)很明顯的可以看出,物理地址被映射到以 PAGE_OFFSET開(kāi)始的虛擬地址空間。這樣以上所有疑問(wèn)就都有了答案。kmalloc() 與__get_free_page() 所分配的物理頁(yè)面被映射到了 PAGE_OFFSET 開(kāi)始的虛擬地址,也就是說(shuō)實(shí)際物理地址與虛擬地址有一組一一對(duì)應(yīng)的關(guān)系,正是因?yàn)橛辛诉@種映射關(guān)系,對(duì)內(nèi)核以 PAGE_OFFSET 啟始的虛擬地址的分配也就是對(duì)物理地址的分配(當(dāng)然這有一定的范圍,應(yīng)該在 PAGE_OFFSET

  與 VMALLOC_START 之間,后者為vmalloc() 函數(shù)分配內(nèi)存的啟始地址)。這也就解釋了為什么 virt_to_phys() 與phys_to_virt() 函數(shù)的實(shí)現(xiàn)僅僅是加/減 PAGE_OFFSET 即可在虛擬地址與物理地址之間轉(zhuǎn)換,正是

  因?yàn)榱擞辛诉@種映射,且固定不變,所以才不用去查頁(yè)表進(jìn)行轉(zhuǎn)換。這也同樣回答了開(kāi)始的問(wèn)題,即 kmalloc() / __get_free_page() 分配的是物理地址,而返回的則是虛擬地址(雖然這聽(tīng)上去有些別扭)。正是因?yàn)橛辛诉@種映射關(guān)系,所以需要將它們的返回地址減去 PAGE_OFFSET 才可以得到真正的物理地址。

  虛擬地址和物理地址的概念

  CPU通過(guò)地址來(lái)訪問(wèn)內(nèi)存中的單元,地址有虛擬地址和物理地址之分,如果CPU沒(méi)有MMU(Memory Management Unit,內(nèi)存管理單元),或者有MMU但沒(méi)有啟用,CPU核在取指令或訪問(wèn)內(nèi)存時(shí)發(fā)出的地址將直接傳到CPU芯片的外部地址引腳上,直接被內(nèi)存芯片(以下稱為物理內(nèi)存,以便與虛擬內(nèi)存區(qū)分)接收,這稱為物理地址(Physical Address,以下簡(jiǎn)稱PA),如下圖所示。

  物理地址示意圖

  如果CPU啟用了MMU,CPU核發(fā)出的地址將被MMU截獲,從CPU到MMU的地址稱為虛擬地址(Virtual Address,以下簡(jiǎn)稱VA),而MMU將這個(gè)地址翻譯成另一個(gè)地址發(fā)到CPU芯片的外部地址引腳上,也就是將虛擬地址映射成物理地址,如下圖所示[1]。

  虛擬地址示意圖

  MMU將虛擬地址映射到物理地址是以頁(yè)(Page)為單位的,對(duì)于32位CPU通常一頁(yè)為4K。例如,虛擬地址0xb700 1000~0xb700 1fff是一個(gè)頁(yè),可能被MMU映射到物理地址0x2000~0x2fff,物理內(nèi)存中的一個(gè)物理頁(yè)面也稱為一個(gè)頁(yè)框(Page Frame)。

  內(nèi)核也不能直接訪問(wèn)物理地址.但因?yàn)閮?nèi)核的虛擬地址和物理地址之間只是一個(gè)差值0xc0000000的區(qū)別,所以從物理地址求虛擬地址或從虛擬地址求物理地址很容易,+-這個(gè)差就行了

  物理地址(physical address)

  用于內(nèi)存芯片級(jí)的單元尋址,與處理器和CPU連接的地址總線相對(duì)應(yīng)。

  ——這個(gè)概念應(yīng)該是這幾個(gè)概念中最好理解的一個(gè),但是值得一提的是,雖然可以直接把物理地址理解成插在機(jī)器上那根內(nèi)存本身,把內(nèi)存看成一個(gè)從0字節(jié)一直到最大空量逐字節(jié)的編號(hào)的大數(shù)組,然后把這個(gè)數(shù)組叫做物理地址,但是事實(shí)上,這只是一個(gè)硬件提供給軟件的抽像,內(nèi)存的尋址方式并不是這樣。所以,說(shuō)它是“與地址總線相對(duì)應(yīng)”,是更貼切一些,不過(guò)拋開(kāi)對(duì)物理內(nèi)存尋址方式的考慮,直接把物理地址與物理的內(nèi)存一一對(duì)應(yīng),也是可以接受的。也許錯(cuò)誤的理解更利于形而上的抽像。

  虛擬內(nèi)存(virtual memory)

  這是對(duì)整個(gè)內(nèi)存(不要與機(jī)器上插那條對(duì)上號(hào))的抽像描述。它是相對(duì)于物理內(nèi)存來(lái)講的,可以直接理解成“不直實(shí)的”,“假的”內(nèi)存,例如,一個(gè)0x08000000內(nèi)存地址,它并不對(duì)就物理地址上那個(gè)大數(shù)組中0x08000000 - 1那個(gè)地址元素;

  之所以是這樣,是因?yàn)楝F(xiàn)代操作系統(tǒng)都提供了一種內(nèi)存管理的抽像,即虛擬內(nèi)存(virtual memory)。進(jìn)程使用虛擬內(nèi)存中的地址,由操作系統(tǒng)協(xié)助相關(guān)硬件,把它“轉(zhuǎn)換”成真正的物理地址。這個(gè)“轉(zhuǎn)換”,是所有問(wèn)題討論的關(guān)鍵。

  有了這樣的抽像,一個(gè)程序,就可以使用比真實(shí)物理地址大得多的地址空間。(拆東墻,補(bǔ)西墻,銀行也是這樣子做的),甚至多個(gè)進(jìn)程可以使用相同的地址。不奇怪,因?yàn)檗D(zhuǎn)換后的物理地址并非相同的。

  ——可以把連接后的程序反編譯看一下,發(fā)現(xiàn)連接器已經(jīng)為程序分配了一個(gè)地址,例如,要調(diào)用某個(gè)函數(shù)A,代碼不是call A,而是call 0x0811111111 ,也就是說(shuō),函數(shù)A的地址已經(jīng)被定下來(lái)了。沒(méi)有這樣的“轉(zhuǎn)換”,沒(méi)有虛擬地址的概念,這樣做是根本行不通的。

  Linux下獲取虛擬地址對(duì)應(yīng)的物理地址的方法

  * /proc/pid/pagemap. This file lets a userspace process find out which

  physical frame each virtual page is mapped to. It contains one 64-bit

  value for each virtual page, containing the following data (from

  fs/proc/task_mmu.c, above pagemap_read):

  * Bits 0-54 page frame number (PFN) if present

  * Bits 0-4 swap type if swapped

  * Bits 5-54 swap offset if swapped

  * Bit 55 pte is soft-dirty (see Documentation/vm/soft-dirty.txt)

  * Bits 56-60 zero

  * Bit 61 page is file-page or shared-anon

  * Bit 62 page swapped

  * Bit 63 page present

  If the page is not present but in swap, then the PFN contains an

  encoding of the swap file number and the page's offset into the

  swap. Unmapped pages return a null PFN. This allows determining

  precisely which pages are mapped (or in swap) and comparing mapped

  pages between processes.

  接下來(lái),我們根據(jù)上述描述,給出獲取虛擬地址對(duì)應(yīng)的物理地址的代碼

  #include <stdio.h>

  #include <stdint.h>

  #include <sys/types.h>

  #include <sys/stat.h>

  #include <fcntl.h>

  #include <unistd.h>

  #define page_map_file "/proc/self/pagemap"

  #define PFN_MASK ((((uint64_t)1)<<55)-1)

  #define PFN_PRESENT_FLAG (((uint64_t)1)<<63)

  int mem_addr_vir2phy(unsigned long vir, unsigned long *phy)

  {

  int fd;

  int page_size=getpagesize();

  unsigned long vir_page_idx = vir/page_size;

  unsigned long pfn_item_offset = vir_page_idx*sizeof(uint64_t);

  uint64_t pfn_item;

  fd = open(page_map_file, O_RDONLY);

  if (fd<0)

  {

  printf("open %s failed", page_map_file);

  return -1;

  }

  if ((off_t)-1 == lseek(fd, pfn_item_offset, SEEK_SET))

  {

  printf("lseek %s failed", page_map_file);

  return -1;

  }

  if (sizeof(uint64_t) != read(fd, &pfn_item, sizeof(uint64_t)))

  {

  printf("read %s failed", page_map_file);

  return -1;

  }

  if (0==(pfn_item & PFN_PRESENT_FLAG))

  {

  printf("page is not present");

  return -1;

  }

  *phy = (pfn_item & PFN_MASK)*page_size + vir % page_size;

  return 0;

  }

  如果擔(dān)心vir地址對(duì)應(yīng)的頁(yè)面不在內(nèi)存中,可以在調(diào)用mem_addr_vir2phy之前,先訪問(wèn)一下此地址。

  例如, int a=*(int *)(void *)vir;

  如果擔(dān)心Linux的swap功能將進(jìn)程的頁(yè)面交換到硬盤(pán)上從而導(dǎo)致頁(yè)面的物理地址變化,可以關(guān)閉swap功能。

  下面兩個(gè)C庫(kù)函數(shù)可以阻止Linux將當(dāng)前進(jìn)程的部分或全部頁(yè)面交換到硬盤(pán)上。

  int mlock(const void *addr, size_t len);

  int mlockall(int flags);

  看過(guò)“虛擬地址物理地址linux ”的人還看了:

1.linux虛擬地址怎么映射物理地址

2.物理地址與虛擬地址映射

3.物理地址與虛擬地址怎么轉(zhuǎn)換

4.Linux關(guān)于虛擬內(nèi)存

5.物理地址和邏輯地址的區(qū)別

604436