Linux讀寫物理內存


一、基礎知識

1.打開設備文件:
    mem是一個字符設備文件,是計算機主存的一個映像。通常只有root用戶對其有讀寫權限。因此只有root用戶能進行這些操作。
    如果要打開設備文件/dev/mem,需要系統調用open()函數,作用是打開一個文件或設備,其函數原型為:
    int open(const char *path, int flags);
    返回值:如果操作成功則返回一個文件描述符,否則返回-1
    形  參:
            path      被打開文件的路徑即文件名描述。
            flags     文件的訪問模式描述,可常用的選項見下:
                        O_RDONLY        只讀方式
                        O_WRONLY        只寫方式
                        O_RDWR          可讀寫方式
    說  明:此函數用於打開文件或者設備
    頭文件:#include <fcntl.h> #include <stat.h>  定義在/usr/include/fcntl.h中
2.讀取內存映像:
    內存映像其實在內存中創建一個與外存中文件完全相同的映像。用戶可以將整個文件映射到內存中,也可以將文件的一部分映射到內存中。
    使用操作內存的方法對文件進行操作。系統會將內存映像文件所做的改動反映到真實文件中去。
    在內存映像I/O的實現過程中需要用到一些系統調用:
    首先是創建內存映像文件的系統調用mmap()函數,其函數原型為:
    void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
    返回值:成功時,返回值為指向內存映像起始地址的指針,當調用失敗時,返回值為-1
    形  參:
            start      一個void指針,表示希望將文件映射到此指針指向的位置,通常為NULL。
            length     定義內存映像文件所占用的內存空間大小,以字節計。
            prot       內存映像文件的安全屬性,注意和open函數中的flags屬性保持一致。它的可使用的選項如下:
                          Flags                          含義
                        PROT_EXEC       被映像內存可能含義機器碼,可被執行
                        PROT_NONE       映像內存不允許訪問
                        PROT_READ       映像內存可讀
                        PROT_WRITE      映像內存可寫
            flags      內存映像的標志,選項如下:
                          Flags                                   含義
                        MAP_FIXED       指定映射起始地址,如果由start和len指定的內存區重疊於現存的映射空間,重疊部分將會被丟棄。
                                        如果指定的起始地址不可用,操作將會失敗。並且起始地址必須落在頁的邊界上。
                        MAP_SHARED      與其它所有映射這個對象的進程共享映射空間。對共享區的寫入,相當於輸出到文件。
                                        直到msync()或者munmap()被調用,文件實際上不會被更新。 
                        MAP_PRIVATE     建立一個寫入時拷貝的私有映射。內存區域的寫入不會影響到原文件。
                                        這個標志和以上標志是互斥的,只能使用其中一個。 
                        MAP_NORESERVE   不要為這個映射保留交換空間。當交換空間被保留,對映射區修改的可能會得到保證。
                                        當交換空間不被保留,同時內存不足,對映射區的修改會引起段違例信號。
                        MAP_LOCKED      鎖定映射區的頁面,從而防止頁面被交換出內存。 
                        MAP_GROWSDOWN   用於堆棧,告訴內核VM系統,映射區可以向下擴展。
                        MAP_ANONYMOUS   匿名映射,映射區不與任何文件關聯。
                        MAP_ANON        MAP_ANONYMOUS的別稱,不再被使用。 
                        MAP_FILE        兼容標志,被忽略。 
                        MAP_32BIT       將映射區放在進程地址空間的低2GB,MAP_FIXED指定時會被忽略。
                                        當前這個標志只在x86-64平台上得到支持。
                        MAP_POPULATE    為文件映射通過預讀的方式准備好頁表。隨后對映射區的訪問不會被頁違例阻塞。 
                        MAP_NONBLOCK    僅和MAP_POPULATE一起使用時才有意義。不執行預讀,只為已存在於內存中的頁面建立頁表入口。
            fd         要映射的文件的描述符。
            offset     所映射的數據內容 距離文件頭的偏移量。
    說  明:此函數用於將一個文件或它的一部分映射到內存中。
    頭文件:#include <sys/mman.h> #include <sys/types.h>  函數定義在/usr/include/sys/mman.h
3.撤銷內存映像的修改
    另外我們使用完內存映像文件后,要用系統調用函數munmap()函數來撤銷,其函數原型為:
    int munmap(void *start, size_t length);
    返回值:成功時,返回值為0;調用失敗時返回值為 -1,並將errno設置為相應值。
    形  參:
            start      要撤銷的內存映像文件的起始地址。
            length     要撤銷的內存映像文件的大小。
    說  明:當進程結束或利用exec相關函數來執行其他程序時,映射內存會自動解除,但關閉對應的文件描述詞時不會解除映射。
    頭文件:#include <sys/mman.h>
4.將內存映像的改動保存到外存中
    最后,如果我們要將內存映像的改動保存到外存中,還需要系統調用msync()函數,其函數原型為:
    int msync(const void *start,size_t length,int flags);
    返回值:成功時,返回值為0;調用失敗時返回值為 -1,並將errno設置為相應值。
    形  參:
            start      要保存到外存的那些源文件的起始地址。
            length     表示內存映像文件的大小。
            flags      設置了函數的相應操作,其具體選項如下:
                          Flags                      含義
                        MS_ASYNC           調用一個寫操作並返回
                        MS_INVALIDATE      映像到相同文件的內存映像數據更新
                        MS_SYNC            完成寫操作后函數返回
    說  明:進程在映射空間的對共享內容的改變並不直接寫回到磁盤文件中,往往在調用munmap()后才執行該操作。
            可以通過調用msync()函數來實現磁盤文件內容與共享內存區中的內容一致,即同步操作。
    頭文件:#include <sys/mman.h>
5.通過/dev/mem設備文件和mmap系統調用,可以將線性地址描述的物理內存映射到進程的地址空間,然后就可以直接訪問這段內存了。

二、一個例子

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <sys/mman.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <fcntl.h>
  7. int main (int args, char* arg[])
  8. {
  9. int i;
  10. int fd;
  11. char* mem;
  12. char *buff = "HELLO";
  13. //open /dev/mem with read and write mode
  14. if((fd = open ("/dev/mem", O_RDWR)) < 0)
  15. {
  16. perror ( "open error");
  17. return -1;
  18. }
  19.  
  20. //map physical memory 0-10 bytes
  21. mem = mmap ( 0, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
  22. if (mem == MAP_FAILED)
  23. {
  24. perror ( "mmap error:");
  25. return 1;
  26. }
  27.  
  28. //Read old value
  29. for (i = 0; i < 5; i++)
  30. {
  31. printf("\nold mem[%d]:%d", i, mem[i]);
  32. }
  33.  
  34. printf(The value is 0x%x\n, *((int *)mem));
  35.  
  36. //write memory
  37. memcpy(mem, buff, 5);
  38. //Read new value
  39. for (i = 0; i<5 ; i++)
  40. {
  41. printf("\nnew mem[%d]:%c", i, mem[i]);
  42. }
  43.  
  44. printf("\n");
  45. munmap (mem, 10); //destroy map memory
  46. close (fd); //close file
  47. return 0;
  48. }
     
     
     
    使用 hexedit /dev/mem 可以顯示所有物理內存中的信息。 運用mmap將/dev/mem map出來,然后直接對其讀寫可以實現用戶空間的內核操作。

    以下是我寫的一個sample

        #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)
            {
                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("Successfull!\n");
            }
         
            unsigned long addr;
            unsigned char content;
         
            int i = 0;
            for (;i < 0xff; ++i)
            {
                addr = (unsigned long)(map_base + i);
                content = map_base[i];
                printf("address: 0x%lx   content 0x%x\t\t", addr, (unsigned int)content);
         
                map_base[i] = (unsigned char)i;
                content = map_base[i];
                printf("updated address: 0x%lx   content 0x%x\n", addr, (unsigned int)content);
            }
         
            close(fd);
         
            munmap(map_base, 0xff);
         
            return (1);
        }


    上面的例子將起始地址0x20000(物理地址), 長度為0xff映射出來。 然后就可以像普通數組一樣操作內存。

    下面是輸出結果

    address: 0x7f3f95391000   content 0x0           updated address: 0x7f3f95391000   content 0x0
    address: 0x7f3f95391001   content 0x0           updated address: 0x7f3f95391001   content 0x1
    address: 0x7f3f95391002   content 0x0           updated address: 0x7f3f95391002   content 0x2
    address: 0x7f3f95391003   content 0x0           updated address: 0x7f3f95391003   content 0x3
    address: 0x7f3f95391004   content 0x0           updated address: 0x7f3f95391004   content 0x4
    。。。

    我的測試機器是64位機。 該例子將物理地址0x20000映射到了虛擬地址0x7f3f95391000。

    首先將當前地址下的內容輸出, 然后寫入新值。

    可以通過 hexedit /dev/mem 驗證新值已經寫入。


    如果想在用戶態處理kernel分配的地址可以這么做。 首先用virt_addr = get_free_pages(GFP_KERNEL, order)分配內存,通過phy_addr = __pa(virt_addr)得到物理地址,然后在用戶態將/dev/mem用mmap 映射出來, offset就是phy_addr, length設為 2^order。 此時就可以在用戶態讀寫內核分配的內存了。

    注:該操作需要有root權限。
     ————————————————
    版權聲明:本文為CSDN博主「zhanglei4214」的原創文章,遵循CC 4.0 by-sa版權協議,轉載請附上原文出處鏈接及本聲明。
    原文鏈接:https://blog.csdn.net/zhanglei4214/article/details/6653568


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM