• 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(" old mem[%d]:%d", i, mem[i]);
    32. }
    33.  
    34. printf(The value is 0x%x , *((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(" new mem[%d]:%c", i, mem[i]);
    42. }
    43.  
    44. printf(" ");
    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! ");
              }
              else
              {
                  printf("Successfull! ");
              }
           
              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 ", addr, (unsigned int)content);
           
                  map_base[i] = (unsigned char)i;
                  content = map_base[i];
                  printf("updated address: 0x%lx   content 0x%x ", 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
  • 相关阅读:
    VS.NET 2010 新功能 [整理]
    Sculpture 基于MDD的集成开发平台
    更换VS.NET 2010的皮肤 [Visual Studio Blog]
    SQL Server BI Step by Step SSIS 7 (End) 事务,错误输出,事件处理,日志记录
    和我一起学CSLA.NET设计模型及数据访问
    Visual Studio DSL 入门 2
    SQL Server BI Step by Step SSIS 5 通过Email发送查询结果
    SQL Server BI Step by Step SSRS 1Reporting Service 2008的新特性
    SQL Server BI Step by Step SSRS 2SQL Server 2008 Reporting Services实现匿名访问报表
    和我一起学CSLA.NET先忽悠下
  • 原文地址:https://www.cnblogs.com/idyllcheung/p/11430599.html
Copyright © 2020-2023  润新知