ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、视频、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
https://www.cnblogs.com/Ph-one/p/6844184.html # 相关函数 虚拟 转 物理地址 virt_to_phys( *addr ); 物理 转 虚拟地址 phys_to_virt( *addr ); 如: ``` unsigned long pProtectVA; phys_addr_t ProtectPA; gM4U_ProtectVA = pProtectVA; ProtectPA = virt_to_phys((void *)pProtectVA); ``` Linux内核里提供的/dev/mem驱动,为我们读写内存物理地址,提供了一个渠道。下面讲述2种利用mem设备文件进行物理地址读写的方法,一种是设备驱动的方法,另一种是系统调用的方法。 首先我们看下mem这个设备文件,/dev/mem是linux下的一个字符设备,源文件是~/drivers/char/mem.c,这个设备文件是专门用来读写物理地址用的。里面的内容是所有物理内存的地址以及内容信息。通常只有root用户对其有读写权限。 # 1.设备驱动的方法 下面是mem.c文件里定义的file_operations结构,提供了llseek,read,write,mmap以及open等方法。 ``` static const struct file_operations zero_fops = { .llseek = zero_lseek, .write = write_zero, .read_iter = read_iter_zero, .write_iter = write_iter_zero, .mmap = mmap_zero, .get_unmapped_area = get_unmapped_area_zero, #ifndef CONFIG_MMU .mmap_capabilities = zero_mmap_capabilities, #endif }; ``` 因此我们可以通过一般驱动的使用方法,将内存完全当作一个设备来对对待。应用程序如下: ``` #include <stdio.h> #include <fcntl.h> int main(void) { int fd; char *rdbuf; char *wrbuf = "butterfly"; int i; fd = open("/dev/mem",O_RDWR); if(fd < 0) { printf("open /dev/mem failed."); } read(fd,rdbuf,10); for(i = 0;i < 10;i++) { printf("old mem[%d]:%c\n",i,*(rdbuf + i)); } lseek(fd,5,0); write(fd,wrbuf,10); lseek(fd,0,0);//move f_ops to the front read(fd,rdbuf,10); for(i = 0;i < 10;i++) { printf("new mem[%d]:%c\n",i,*(rdbuf + i)); } return 0; } ``` 执行结果如下:将内存最开始10个字节的内容进行替换。 ``` [root@VOIP-IPCAM app]# ./memtest old mem[0]:b old mem[1]:u old mem[2]:t old mem[3]:t old mem[4]:e old mem[5]:r old mem[6]:f old mem[7]:l old mem[8]:y old mem[9]:! new mem[0]:b new mem[1]:u new mem[2]:t new mem[3]:t new mem[4]:e new mem[5]:b new mem[6]:u new mem[7]:t new mem[8]:t new mem[9]:e ``` # 2.系统调用的方法 细心的你可能会发现,既然你前面说了这个文件里存放的就是内存的地址及内容信息,那我可不可以直接查看到呢,答案是:可以的。linux内核的开发者为我 们提供了一个命令hexedit,通过它就可以将/dev/mem的内容显示出来(如果你使用cat /dev/mem将会看到乱码) 让人欣慰的是,这个文件可 以直接修改,按下tab键进入修改模式,修改过程中修改内容会以粗体显示,按下F2保存后粗体消失。 既然内存的地址以及内容信息全部被保存在mem这个设备文件里,那么我们可以想到通过另外一种方式来实现对物理地址的读写了。那就是将mem设备文件和 mmap系统调用结合起来使用,将文件里的物理内存地址映射到进程的地址空间,从而实现对内存物理地址的读写。下面谈一下mmap系统调用。 mmap的函数原型为:void *mmap(void *start,size_t length,int prot,int flags,int fd,off_t offset),该函数定义在/usr/include/sys/mman.h中,使用时要包含:#include<sys /mman.h>,mmap()用来将某个文件中的内容映射到进程的地址空间,对该空间的存取即是对该文件内容的读写。参数说明如下: start:指向欲映射到的地址空间的起始地址,通常设为null或者0.表示让系统融自动选定地址,映射成功后该地址会返回。 length:表示映射的文件内容的大小,以字节为单位。 prot:表示映射区域的保护方式,有如下四种组合: --PROT_EXEC 映射区域可执行 , --PROT_READ 映射区域可读 , --PROT_WRITE 映射区域可写, --PROT_NONE 映射区域不能被访问 flags:映射区域的一些特性,主要有: --MAP_FIXED 如果映射不成功则出错返回, --MAP_SHARED 对映射区域的写入数据会写回到原来的文件 --MAP_PRIVATE 对映射区域的写入数据不会写回原来的文件 --MAP_ANONYMOUS --MAP_DENYWRITE 只允许对映射区域的写入操作,其他对文件直接写入的操作将被拒绝 --MAP_LOCKED 锁定映射区域 在调用mmap()时,必须要指定MAP_SHARED或MAP_PRIVATE。 fd:open()返回的文件描述符。 offset:为被映射文件的偏移量,表示从文件的哪个地方开始映射,一般设置为0,表示从文件的最开始位置开始映射。offset必须是分页大小(4096字节)的整数倍。 ``` #include <stdio.h> #include <fcntl.h> #include <sys/mman.h>//mmap head file int main (void) { int i; int fd; char *start; char *buf = "butterfly!"; //open /dev/mem with read and write mode fd = open ("/dev/mem", O_RDWR); if (fd < 0) { printf("cannot open /dev/mem."); return -1; } //map physical memory 0-10 bytes start = (char *)mmap(0, 10, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if(start < 0) { printf("mmap failed."); return -1; } //Read old value for (i = 0; i < 10; i++) { printf("old mem[%d]:%c\n", i, *(start + i)); } //write memory memcpy(start, buf, 10); //Read new value for (i = 0;i < 10;i++) { printf("new mem[%d]:%c\n", i,*(start + i)); } munmap(start, 10); //destroy map memory close(fd); //close file return 0; } ``` 程序执行结果如下: ``` [root@VOIP-IPCAM app]# ./rwphy old mem[0]:b old mem[1]:u old mem[2]:t old mem[3]:t old mem[4]:e old mem[5]:b old mem[6]:u old mem[7]:t old mem[8]:t old mem[9]:e new mem[0]:b new mem[1]:u new mem[2]:t new mem[3]:t new mem[4]:e new mem[5]:r new mem[6]:f new mem[7]:l new mem[8]:y new mem[9]:! ``` # Linux下/dev/mem和/dev/kmem的区别: /dev/mem: 物理内存的全镜像。可以用来访问物理内存。 /dev/kmem: kernel看到的虚拟内存的全镜像。可以用来访问kernel的内容。 作用: /dev/mem用来访问物理IO设备,比如X用来访问显卡的物理内存,或嵌入式中访问GPIO。用法一般就是open,然后mmap,接着可以使用map之后的地址来访问物理内存。这 其实就是实现用户空间驱动的一种方法。 /dev/kmem后者一般可以用来查看kernel的变量,或者用作rootkit之类的。参考1和2描述了用来查看kernel变量这个问题。