1. 程式人生 > 實用技巧 >Linux中通過/dev/mem操控實體地址

Linux中通過/dev/mem操控實體地址

https://developer.aliyun.com/article/374848

/dev/mem是實體記憶體的全映像,可以用來訪問實體記憶體,用mmap來訪問實體記憶體以及外設的IO資源,是實現使用者空間驅動的一種方法

我們先用hexedit來看下/dev/mem,hexedit /dev/mem 可以實體記憶體的資訊,當然肉眼是無法看的畢竟是16進位制。

00000000   53 FF 00 F0  53 FF 00 F0  53 FF 00 F0  53 FF 00 F0  S...S...S...S...
00000010   53 FF 00 F0  53 FF 00 F0  CC E9 00 F0  53 FF 00 F0  S...S.......S...
00000020   A5 FE 00 F0  87 E9 00 F0  53 FF 00 F0  46 E7 00 F0  ........S...F...
00000030   46 E7 00 F0  46 E7 00 F0  57 EF 00 F0  53 FF 00 F0  F...F...W...S...
00000040   22 00 00 C0  4D F8 00 F0  41 F8 00 F0  FE E3 00 F0  "...M...A.......
00000050   39 E7 00 F0  59 F8 00 F0  2E E8 00 F0  D4 EF 00 F0  9...Y...........

不過可以用mmap將/dev/mem 映射出來,然後可以對其讀寫可以實現使用者空間的核心操作。先來說下mmap函式,

void *mmap(void *addr, size_t length, int prot, int flags,

                  int fd, off_t offset);

共6個引數含義分別如下:

l addr如果為null,那麼有核心選擇一個對映的地址,如果不為null,那核心會把引數當做對映的提示(對映的地址就在所提示的附近,不會百分百確保的)
l  length表示對映長度
l prot表示對對映的保護, 可以是可執行,可讀,可寫或不可訪問,PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
l flag表示是否對其他程序可見,MAP_SHARED表示其他程序可見。
l fd需要對映的檔案描述符
l offset指向fd的編譯
接下去我們用mmap來對映/dev/mem,編寫程式碼如下:

#include<stdio.h>
#include<unistd.h>
#include<sys/mman.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

int main ()
{

  unsigned char *map_base;
  FILE *f;
  int n, fd;

  fd = open ("/dev/mem", O_RDWR | O_SYNC);
  if (fd == -1)
    {
      printf ("open /dev/mem fail!\n");
      return (-1);
    }
 

  map_base = mmap (NULL, 0xff, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0x20000);
  if (map_base == 0)
   {
      printf ("NULL pointer!\n");
    }
  else
    {
      printf ("map Successfull!\n");
    }

  unsigned long addr;
  unsigned char content;

  int i = 0;
  for (; i < 0xf; ++i)
  {
      addr = (unsigned long) (map_base + i);
      content = map_base[i];
      printf ("address: 0x%lx   value: 0x%x\t\t", addr,(unsigned int) content);

      map_base[i] = (unsigned char) i;
      content = map_base[i];
      printf ("address: 0x%lx   value: 0x%x\t\t", addr, (unsigned int) content);

      map_base[i] = (unsigned char) i;
      content = map_base[i];
      printf ("address: 0x%lx   new value: 0x%x\n", addr, (unsigned int) content);

    }

  close (fd);
  munmap (map_base, 0xff);
  return (1);

}


幾天研究了下/dev/mem,發現功能很神奇,通過mmap可以將實體地址對映到使用者空間的虛擬地址上,在使用者空間完成對裝置暫存器的操作,於是上網搜了一些/dev/mem的資料。網上的說法也很統一,/dev/mem是實體記憶體的全映像,可以用來訪問實體記憶體,一般用法是open("/dev/mem",O_RDWR|O_SYNC),接著就可以用mmap來訪問實體記憶體以及外設的IO資源,這就是實現使用者空間驅動的一種方法。
使用者空間驅動聽起來很酷,但是對於/dev/mem,我覺得沒那麼簡單,有2個地方引起我的懷疑:
(1)網上資料都說/dev/mem是實體記憶體的全映象,這個概念很含糊,/dev/mem到底可以完成哪些地址的虛實對映?
(2)/dev/mem看似很強大,但是這也太危險了,黑客完全可以利用/dev/mem對kernel程式碼以及IO進行一系列的非法操作,後果不可預測,難道核心開發者們沒有意識到這點嗎?

網上資料說法都很泛泛,只對mem裝置的使用進行說明,沒有對這些問題進行深究。要搞清這一點,我覺得還是從/dev/mem驅動開始下手。

參考核心版本:3.4.55
參考平臺:powerpc/arm

mem驅動在drivers/char/mem.c,mmap是系統呼叫,產生軟中斷進入核心後呼叫sys_mmap,最終會呼叫到mem驅動的mmap實現函式。

來看下mem.c中的mmap實現:

  1. staticintmmap_mem(structfile*file,structvm_area_struct*vma)
  2. {
  3. size_tsize=vma->vm_end-vma->vm_start;
  4. if(!valid_mmap_phys_addr_range(vma->vm_pgoff,size))
  5. return-EINVAL;
  6. if(!private_mapping_ok(vma))
  7. return-ENOSYS;
  8. if(!range_is_allowed(vma->vm_pgoff,size))
  9. return-EPERM;
  10. if(!phys_mem_access_prot_allowed(file,vma->vm_pgoff,size,
  11. &vma->vm_page_prot))
  12. return-EINVAL;
  13. vma->vm_page_prot=phys_mem_access_prot(file,vma->vm_pgoff,
  14. size,
  15. vma->vm_page_prot);
  16. vma->vm_ops=&mmap_mem_ops;
  17. /*Remap-pfn-rangewillmarktherangeVM_IOandVM_RESERVED*/
  18. if(remap_pfn_range(vma,
  19. vma->vm_start,
  20. vma->vm_pgoff,
  21. size,
  22. vma->vm_page_prot)){
  23. return-EAGAIN;
  24. }
  25. return0;
  26. }

vma是核心記憶體管理很重要的一個結構體,
其結構成員中start end代表要對映到的使用者空間虛擬地址範圍,使用者空間的動態對映是以PAGE_SIZE也就是4K為一頁,
vma_pgoff是要對映的實體地址,vma_page_prot代表該頁的許可權。


這些成員的賦值是在呼叫具體驅動的mmap實現函式之前,在sys_mmap中進行的。
在mmap_mem最後呼叫remap_pfn_range,該函式完成指定實體地址與使用者空間虛擬地址頁表的建立。
remap_pfn_range引數中vma->vm_pgoff即代表要對映的實體地址,並沒有範圍限制僅能夠操作記憶體。
mmap系統呼叫的函式定義如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr指定要對映到的虛擬地址,寫NULL則有sys_mmap來分配該虛擬地址。
mmap引數與mem_mmap引數對應關係如下:
prot ===> vma->vma_page_prot
offset ===> vma->vma_pgoff
length ===> size


從剛才分析的mem_mmap流程來看,可以得出一個簡單的結論:
mem_mmap可以對映整個處理器的地址空間,而不單單是記憶體。這裡要說明的是,地址空間不等於記憶體空間。站在處理器角度看,地址空間指處理器總線上的所有可定址空間,除了記憶體,還有外設的IO空間,以及其他匯流排對映過來的mem(如PCI)
我的理解,mem_mmap完全可以對映0-0xffffffff的所有實體地址(填TLB頁表完成對映),但前提是保證該實體地址是真實有效的,也就是處理器訪問該匯流排實體地址可以獲取有效資料。
所以現在看來mmap /dev/mem,只要確定我們處理器的地址空間分佈,就可以將我們需要的地址對映到使用者空間進行操作。
如果地址不是一個有效實體地址(處理器地址空間分佈中該地址沒用),mmap建立該實體地址與使用者空間虛擬地址的對映,填TLB,CPU經過TLB翻譯後去訪問該不存在的實體地址訪問就有可能導致CPU掛掉。

這也就解釋了我第一個疑問,但是kernel的安全機制不會允許使用者這麼肆無忌憚的操作。接著來看remap_pfn_range之前mmap_mem如何進行防護。

這幾天研究了下/dev/mem,發現功能很神奇,通過mmap可以將實體地址對映到使用者空間的虛擬地址上,在使用者空間完成對裝置暫存器的操作,於是上網搜了一些/dev/mem的資料。網上的說法也很統一,/dev/mem是實體記憶體的全映像,可以用來訪問實體記憶體,一般用法是open("/dev/mem",O_RDWR|O_SYNC),接著就可以用mmap來訪問實體記憶體以及外設的IO資源,這就是實現使用者空間驅動的一種方法。
使用者空間驅動聽起來很酷,但是對於/dev/mem,我覺得沒那麼簡單,有2個地方引起我的懷疑:
(1)網上資料都說/dev/mem是實體記憶體的全映象,這個概念很含糊,/dev/mem到底可以完成哪些地址的虛實對映?
(2)/dev/mem看似很強大,但是這也太危險了,黑客完全可以利用/dev/mem對kernel程式碼以及IO進行一系列的非法操作,後果不可預測,難道核心開發者們沒有意識到這點嗎?

網上資料說法都很泛泛,只對mem裝置的使用進行說明,沒有對這些問題進行深究。要搞清這一點,我覺得還是從/dev/mem驅動開始下手。

參考核心版本:3.4.55
參考平臺:powerpc/arm

mem驅動在drivers/char/mem.c,mmap是系統呼叫,產生軟中斷進入核心後呼叫sys_mmap,最終會呼叫到mem驅動的mmap實現函式。

來看下mem.c中的mmap實現:

  1. staticintmmap_mem(structfile*file,structvm_area_struct*vma)
  2. {
  3. size_tsize=vma->vm_end-vma->vm_start;
  4. if(!valid_mmap_phys_addr_range(vma->vm_pgoff,size))
  5. return-EINVAL;
  6. if(!private_mapping_ok(vma))
  7. return-ENOSYS;
  8. if(!range_is_allowed(vma->vm_pgoff,size))
  9. return-EPERM;
  10. if(!phys_mem_access_prot_allowed(file,vma->vm_pgoff,size,
  11. &vma->vm_page_prot))
  12. return-EINVAL;
  13. vma->vm_page_prot=phys_mem_access_prot(file,vma->vm_pgoff,
  14. size,
  15. vma->vm_page_prot);
  16. vma->vm_ops=&mmap_mem_ops;
  17. /*Remap-pfn-rangewillmarktherangeVM_IOandVM_RESERVED*/
  18. if(remap_pfn_range(vma,
  19. vma->vm_start,
  20. vma->vm_pgoff,
  21. size,
  22. vma->vm_page_prot)){
  23. return-EAGAIN;
  24. }
  25. return0;
  26. }

vma是核心記憶體管理很重要的一個結構體,
其結構成員中start end代表要對映到的使用者空間虛擬地址範圍,使用者空間的動態對映是以PAGE_SIZE也就是4K為一頁,
vma_pgoff是要對映的實體地址,vma_page_prot代表該頁的許可權。


這些成員的賦值是在呼叫具體驅動的mmap實現函式之前,在sys_mmap中進行的。
在mmap_mem最後呼叫remap_pfn_range,該函式完成指定實體地址與使用者空間虛擬地址頁表的建立。
remap_pfn_range引數中vma->vm_pgoff即代表要對映的實體地址,並沒有範圍限制僅能夠操作記憶體。
mmap系統呼叫的函式定義如下:
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
addr指定要對映到的虛擬地址,寫NULL則有sys_mmap來分配該虛擬地址。
mmap引數與mem_mmap引數對應關係如下:
prot ===> vma->vma_page_prot
offset ===> vma->vma_pgoff
length ===> size


從剛才分析的mem_mmap流程來看,可以得出一個簡單的結論:
mem_mmap可以對映整個處理器的地址空間,而不單單是記憶體。這裡要說明的是,地址空間不等於記憶體空間。站在處理器角度看,地址空間指處理器總線上的所有可定址空間,除了記憶體,還有外設的IO空間,以及其他匯流排對映過來的mem(如PCI)
我的理解,mem_mmap完全可以對映0-0xffffffff的所有實體地址(填TLB頁表完成對映),但前提是保證該實體地址是真實有效的,也就是處理器訪問該匯流排實體地址可以獲取有效資料。
所以現在看來mmap /dev/mem,只要確定我們處理器的地址空間分佈,就可以將我們需要的地址對映到使用者空間進行操作。
如果地址不是一個有效實體地址(處理器地址空間分佈中該地址沒用),mmap建立該實體地址與使用者空間虛擬地址的對映,填TLB,CPU經過TLB翻譯後去訪問該不存在的實體地址訪問就有可能導致CPU掛掉。

這也就解釋了我第一個疑問,但是kernel的安全機制不會允許使用者這麼肆無忌憚的操作。接著來看remap_pfn_range之前mmap_mem如何進行防護。

首先是valid_mmap_phys_addr_range,檢查該實體地址是否是一個有效的mmap地址,如果平臺定義了ARCH_HAS_VALID_PHYS_ADDR_RANGE則會實現該函式,
arm中定義並實現了該函式,在arch/arm/mm/mmap.c中,如下:

  1. /*
  2. *Wedon'tusesupersectionmappingsformmap()on/dev/mem,which
  3. *meansthatwecan'tmapthememoryareaabovethe4Gbarrierinto
  4. *userspace.
  5. */
  6. intvalid_mmap_phys_addr_range(unsignedlongpfn,size_tsize)
  7. {
  8. return!(pfn+(size>>PAGE_SHIFT)>0x00100000);
  9. }

該函式確定mmap的範圍是否超過4G,超過4G則為無效實體地址,這種情況使用者空間一般不會出現。
而對於powerpc,平臺沒有定義ARCH_HAS_VALID_PHYS_ADDR_RANGE,所以valid_mmap_phys_addr_range在mem.c中定義為空函式,返回1 表示該實體地址一直有效。
實體地址有效,不會返回-EINVAL,繼續往下走。

接下來是private_mapping_ok,對於有MMU的CPU,實現如下:

  1. staticinlineintprivate_mapping_ok(structvm_area_struct*vma)
  2. {
  3. return1;
  4. }

MMU的許可權管理可以支援私有對映,所以該函式一直成功。

接下來是一個最為關鍵的檢查函式range_is_allowed,定義如下:

[cpp]view plaincopy
  1. #ifdefCONFIG_STRICT_DEVMEM
  2. staticinlineintrange_is_allowed(unsignedlongpfn,unsignedlongsize)
  3. {
  4. u64from=((u64)pfn)<<PAGE_SHIFT;
  5. u64to=from+size;
  6. u64cursor=from;
  7. while(cursor<to){
  8. if(!devmem_is_allowed(pfn)){
  9. printk(KERN_INFO
  10. "Program%striedtoaccess/dev/membetween%Lx->%Lx.\n",
  11. current->comm,from,to);
  12. return0;
  13. }
  14. cursor+=PAGE_SIZE;
  15. pfn++;
  16. }
  17. return1;
  18. }
  19. #else
  20. staticinlineintrange_is_allowed(unsignedlongpfn,unsignedlongsize)
  21. {
  22. return1;
  23. }
  24. #endif

可以看出如果不開啟CONFIG_STRICT_DEVMEM,range_is_allowed是返回1,表示該實體地址範圍是被允許的。檢視kconfig檔案(在相應平臺目錄下,如arch/arm/Kconfig.debug中)找到CONFIG_STRICT_DEVMEM說明如下

  1. configSTRICT_DEVMEM
  2. def_booly
  3. prompt"Filteraccessto/dev/mem"
  4. help
  5. Thisoptionrestrictsaccessto/dev/mem.Ifthisoptionis
  6. disabled,youallowuserspaceaccesstoallmemory,including
  7. kernelanduserspacememory.Accidentalmemoryaccessislikely
  8. tobedisastrous.
  9. Memoryaccessisrequiredforexpertswhowanttodebugthekernel.
  10. Ifyouareunsure,sayY.

該選項menuconfig時在kernel hacking目錄下。
根據說明可以理解,CONFIG_STRICT_DEVMEM是嚴格的對/dev/mem訪問檢查,如果關掉該選項,使用者就可以通過mem裝置訪問所有地址空間(根據對我提出的第一個問題理解,這裡memory應該理解為地址空間)。該選項對於除錯核心有幫助。
如果開啟該選項,核心就會對mem裝置訪問加以檢查,檢查函式就是range_is_allowed。


range_is_allowed函式對要檢查的實體地址範圍以4K頁為單位,一頁一頁的呼叫devmem_is_allowed,如果不允許,則會進行列印提示,並返回0,表示該實體地址範圍不被允許。

來看devmem_is_allowed.該函式是平臺相關函式,不過arm跟powerpc的實現相差不大,以arm的實現為例。在arch/arm/mm/mmap.c中。

[cpp]view plaincopy
  1. /*
  2. *devmem_is_allowed()checkstoseeif/dev/memaccesstoacertain
  3. *addressisvalid.Theargumentisaphysicalpagenumber.
  4. *Wemimicx86herebydisallowingaccesstosystemRAMaswellas
  5. *device-exclusiveMMIOregions.Thiseffectivelydisableread()/write()
  6. *on/dev/mem.
  7. */
  8. intdevmem_is_allowed(unsignedlongpfn)
  9. {
  10. if(iomem_is_exclusive(pfn<<PAGE_SHIFT))
  11. return0;
  12. if(!page_is_ram(pfn))
  13. return1;
  14. return0;
  15. }

首先iomem_is_exclusive檢查該實體地址是否被獨佔保留,實現如下:

  1. #ifdefCONFIG_STRICT_DEVMEM
  2. staticintstrict_iomem_checks=1;
  3. #else
  4. staticintstrict_iomem_checks;
  5. #endif
  6. /*
  7. *checkifanaddressisreservedintheiomemresourcetree
  8. *returns1ifreserved,0ifnotreserved.
  9. */
  10. intiomem_is_exclusive(u64addr)
  11. {
  12. structresource*p=&iomem_resource;
  13. interr=0;
  14. loff_tl;
  15. intsize=PAGE_SIZE;
  16. if(!strict_iomem_checks)
  17. return0;
  18. addr=addr&PAGE_MASK;
  19. read_lock(&resource_lock);
  20. for(p=p->child;p;p=r_next(NULL,p,&l)){
  21. /*
  22. *Wecanprobablyskiptheresourceswithout
  23. *IORESOURCE_IOattribute?
  24. */
  25. if(p->start>=addr+size)
  26. break;
  27. if(p->end<addr)
  28. continue;
  29. if(p->flags&IORESOURCE_BUSY&&
  30. p->flags&IORESOURCE_EXCLUSIVE){
  31. err=1;
  32. break;
  33. }
  34. }
  35. read_unlock(&resource_lock);
  36. returnerr;
  37. }

如果打開了CONFIG_STRICT_DEVMEM,iomem_is_exclusive遍歷iomem_resource連結串列,檢視要檢查的實體地址所在resource的flags,如果是bug或者exclusive,則返回1,表明該實體地址是獨佔保留的。

據我瞭解,iomem_resource是來表徵核心iomem資源的連結串列。

對於外設的IO資源,kernel中使用platform device機制來註冊平臺裝置(platform_device_register)時呼叫insert_resource將該裝置相應的io資源插入到iomem_resource連結串列中。
如果我要對某外設的IO資源進行保護,防止使用者空間訪問,可以將其resource的flags置位exclusive即可。

不過我檢視我平臺支援包裡的所有platform device的resource,flags都沒有置位exclusive或者busy。如果我對映的實體地址範圍是外設的IO,檢查可以通過。

對於記憶體的mem資源,如何註冊到iomem_resource連結串列中,核心程式碼中我還沒找到具體的位置,不過iomem在proc下有相應的表徵檔案,可以cat /proc/iomem。
根據我的實際操作測試,記憶體資源也都沒有exclusive,所以如果我對映地址是記憶體,檢查也可以通過。


所以這裡iomem_is_exclusive檢查一般是通過的,接下來看page_is_ram,看devmem_is_range的邏輯,如果地址是ram地址,則該地址不被允許。page_is_ram也是平臺函式,檢視powerpc的實現如下。

  1. intpage_is_ram(unsignedlongpfn)
  2. {
  3. #ifndefCONFIG_PPC64/*XXXfornow*/
  4. returnpfn<max_pfn;
  5. #else
  6. unsignedlongpaddr=(pfn<<PAGE_SHIFT);
  7. structmemblock_region*reg;
  8. for_each_memblock(memory,reg)
  9. if(paddr>=reg->base&&paddr<(reg->base+reg->size))
  10. return1;
  11. return0;
  12. #endif
  13. }
  14. max_pfn賦值在在do_init_bootmem中,如下.
  15. void__initdo_init_bootmem(void)
  16. {
  17. unsignedlongstart,bootmap_pages;
  18. unsignedlongtotal_pages;
  19. structmemblock_region*reg;
  20. intboot_mapsize;
  21. max_low_pfn=max_pfn=memblock_end_of_DRAM()>>PAGE_SHIFT;
  22. total_pages=(memblock_end_of_DRAM()-memstart_addr)>>PAGE_SHIFT;

max_pfn代表了核心lowmem的頁個數,lowmem在核心下靜態線性對映,系統啟動之初完成對映之後不會改動,讀寫效率高,核心程式碼都是跑在lowmem。
lowmem大小我們可以通過cmdline的“mem=”來指定。

這裡就明白瞭如果要對映的實體地址在lowmem範圍內,也是不允許被對映的。

這樣range_is_allowed就分析完了,exclusive的iomem以及lowmem範圍內的實體地址是不允許被對映的。

接下來phys_mem_access_prot_allowed實現為空返回1,沒有影響。

phys_mem_access_prot確定我們對映頁的許可權,該函式也是平臺函式,以powerpc實現為例,如下:

  1. pgprot_tphys_mem_access_prot(structfile*file,unsignedlongpfn,
  2. unsignedlongsize,pgprot_tvma_prot)
  3. {
  4. if(ppc_md.phys_mem_access_prot)
  5. returnppc_md.phys_mem_access_prot(file,pfn,size,vma_prot);
  6. if(!page_is_ram(pfn))
  7. vma_prot=pgprot_noncached(vma_prot);
  8. returnvma_prot;
  9. }

如果有平臺實現的phys_mem_access_prot,則呼叫之。如果沒有,對於不是lowmem範圍內的實體地址,許可權設定為uncached。

以上的檢查完畢,最後呼叫remap_pfn_range完成頁表設定。

所以如果開啟CONFIG_STRICT_DEVMEM,mem驅動會對mmap要對映的實體地址進行範圍和位置的檢查然後才進行對映,檢查條件如下:
(1)對映範圍不能超過4G。
(2)該實體地址所在iomem不能exclusive.
(3)該實體地址不能處在lowmem中。

所以說對於網上給出的各種利用/dev/mem來操作記憶體以及暫存器的文章,如果操作範圍在上述3個條件內,核心必須關閉CONFIG_STRICT_DEVMEM才行。

這樣對於mem裝置我的2個疑問算是解決了。檢視mem.c時我還看到了另外一個有趣的裝置kmem,這個裝置mmap的是哪裡的地址,網上的說法是核心虛擬地址,這個說法我不以為然,這裡記錄下我的想法。

如果核心開啟CONFIG_KMEM,則會建立kmem裝置,它與mem裝置主要差別在mmap的實現上,kmem的mmap實現如下:

  1. #ifdefCONFIG_DEVKMEM
  2. staticintmmap_kmem(structfile*file,structvm_area_struct*vma)
  3. {
  4. unsignedlongpfn;
  5. /*Turnakernel-virtualaddressintoaphysicalpageframe*/
  6. pfn=__pa((u64)vma->vm_pgoff<<PAGE_SHIFT)>>PAGE_SHIFT;
  7. /*
  8. *RED-PEN:onsomearchitecturesthereismoremappedmemorythan
  9. *availableinmem_mapwhichpfn_validchecksfor.Perhapsshouldadda
  10. *newmacrohere.
  11. *
  12. *RED-PEN:vmallocisnotsupportedrightnow.
  13. */
  14. if(!pfn_valid(pfn))
  15. return-EIO;
  16. vma->vm_pgoff=pfn;
  17. returnmmap_mem(file,vma);
  18. }
  19. #endif

引起我注意的是__pa,完成核心虛擬地址到實體地址的轉換,最後呼叫mmap_mem,簡單一看kmem的確是對映的核心虛擬地址。
但是搞清楚__pa的實現,我就不這麼認為了。以powerpc為例,在arch/powerpc/include/asm/page.h,定義如下:

  1. #define__va(x)((void*)(unsignedlong)((phys_addr_t)(x)+VIRT_PHYS_OFFSET))
  2. #define__pa(x)((unsignedlong)(x)-VIRT_PHYS_OFFSET)
  3. ....
  4. #defineVIRT_PHYS_OFFSET(KERNELBASE-PHYSICAL_START)

核心中定義了4個變數來表示核心一些基本的實體地址和虛擬地址,如下:
KERNELBASE 核心的起始虛擬地址,我的是0xc0000000
PAGE_OFFSET 低端記憶體的起始虛擬地址,一般是0xc0000000
PHYSICAL_START 核心的起始實體地址,我的是0x80000000
MEMORY_START 低端記憶體的起始實體地址,我的是0x80000000

核心在啟動過程中對於lowmem的靜態對映,就是以上述的實體地址和虛擬地址的差值進行線性對映的。
所以__pa __va轉換的是線性對映的記憶體部分,也就是lowmem。
所以kmem對映的是lowmem,如果我的cmdline引數中mem=512M,這就意味著通過kmem的mmap我最多可以訪問核心地址空間開始的512M記憶體。
對於超過lowmem範圍,訪問highmem,如果使用__pa訪問,由於highmem是動態對映的,其對映關係不是線性的那麼簡單了,根據__pa獲取的實體地址與我們想要的核心虛擬地址是不對應的。