mmap 匿名映射及Linux@X86_64 平台验证/dev/mem节点内存映射
mmap函数原型:
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
addr:推荐映射定义,如果为NULL,内核自动分配,如果flags设置了MAP_FIXED且addr合法,则映射addr开始的地址并返回。
length: length是映射到调用进程地址空间的字节数,它从被映射文件开头offset个字节开始算起,一般为PAGE整数倍,不足一内存页按一内存页处理.
prot:映射读写属性等等,期望的内存保护标志,不能与文件的打开模式冲突.
flags:私有,共享,匿名等等
MAP_SHARED //与其它所有映射这个对象的进程共享映射空间。对共享区的写入,相当于输出到文件。直到msync()或者munmap()被调用,文件实际上不会被更新。
MAP_PRIVATE //建立一个写入时拷贝的私有映射。内存区域的写入不会影响到原文件。这个标志和以上标志是互斥的,只能使用其中一个。
MAP_FIXED:映射addr开始的地址,如果试下存在VMA,则删除原来的在做映射
fd:对于file backend映射,需要指定文件fd,如果为匿名映射,则为-1.
offset:文件内字节从偏移处开始映射
根据内核代码,offset参数的单位是字节,内核在调用ksys_mmap_pgoff前会将其单位转换为page。内核中mmap函数的vm_pgoff来源于off >> PAGE_SHIFT,在mmap实现中可以根据此映射fd文件的不同区域。
映射属性:
映射建立后,会在进程的地址空间中创建一个struct vm_area_struct对象,来描述虚拟地址空间的信息,信息包括起始地址,区域大小,映射属性FLAG等等,FLAG包括:
VM_IO: Memory Maped I/O or similary.(MMIO).
VM_DONTCOPY: 在fork子进程的时候不要拷贝这个部分。
VM_DONTEXPAND: 不能用mremap 函数扩展映射区域。
VM_DONTDUMP:在coredump时不保存这个部分。
VM_PFNMAP: 这个比较重要,它表示这个映射针对的是PURE PFN,纯物理空间,没有struct page 对其进行管理。所以有些情况下,使用受到限制,通常设备的IO映射以及PCI BAR空间的映射,都会带有这个标志。比如AMD KFD GPU驱动中在IO映射时就设置了这个标志:
/dev/mem设备节点可以将物理内存全部映射到用户态,这里实践一把。
配置:
为了启用/dev/mem设备节点,并且BYPASS调如下的检查逻辑,需要设置内核如下的配置单:
CONFIG_DEVMEM=y
# CONFIG_STRICT_DEVMEM is not set
# CONFIG_X86_PAT is not set
配置CONFIG_X86_PAT的目的是DISABLE如下的检查。
然后参照如下博客进行编译升级
Ubuntu18.04 Linux内核编译升级_papaofdoudou的博客-CSDN博客_ubuntu18.04升级内核
/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("%s line %d.failure to open dev mem\n", __func__, __LINE__);
return (-1);
}
map_base = mmap(NULL, 0x200000*512, PROT_READ, MAP_SHARED, fd, 0x100000*0);
if (map_base == 0)
{
printf("NULL pointer!\n");
}
else
{
printf("Successfull!\n");
}
unsigned int* addr;
unsigned int content;
unsigned int i = 1;
for (; i; ++i)
{
addr = (unsigned int*)(map_base + i*4);
content = addr[0];
if(content == 0x51258d48)
{
printf("found\n");
break;
}
if(content == 0x488d2551)
{
printf("found\n");
break;
}
printf("address: 0x%p content 0x%x, i %d \t\t\n", addr, (unsigned int)content, i);
//map_base[i] = 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);
}
运行:
经过验证,和用hexdump工具直接读取/dev/mem节点获取的内容是相同的。
以上奇奇怪怪的数据可能和post_alloc_hook中对分配的页面做posion操作有关,post_alloc_hook->kernel_poison_pages->poison_pages->poison_page->memset(addr, PAGE_POISON, PAGE_SIZE);
分析内和代码,mmap的几种方式
其中,关键变量mmap_base的初始化在:
根据面向对象的思想,自己定义的成员也应该由自己定义的函数处理,所以,这里定义了一个会回调函数get_unmapped_area来查找空闲的没有经过映射的区域,里所应当,里面会根据mmap_base去查找这样的区域。
而且貌似这里有两种映射方案mmap_is_legacy判断是否是传统方案,我们上面提供的图是最新方案,topdown,顾名思义,从上到下分配,向下分配的。
而另一种方案如下图,是向上分配的:
调用分配的地点:
mmap方式在libc库的实现中也有用处,用户常常使用的malloc函数,会根据分配size的大小情况走mmap分配或者brk分配方式,具体分析可见下面博客。
musl libc库的编译以及malloc实现简析_papaofdoudou的博客-CSDN博客_musl-gcc
匿名映射:
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#define PAGE_SIZE 4096
int main(int argc, char *argv[])
{
void *maddr;
maddr = mmap(NULL, 3 * PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if(maddr == (void*)-1) {
printf("%s line %d, faild to map buffer, error %s.\n", __func__, __LINE__, strerror(errno));
return -1;
}
printf("%s line %d maddr = %p errno %s.\n", __func__, __LINE__, maddr, strerror(errno));
unsigned int *p = (unsigned int *)maddr;
*p = 0xdeadbeef;
while(1)
{
*p = *p + 1;
sleep(1);
printf("%s line %d, *p = 0x%08x, p = %p.\n", __func__, __LINE__, *p, p);
}
return 0;
}
二次映射
调用mmap两次,第一次进行匿名映射,占据vma空间,第二次使用第一次返回的地址做MAP_FIXED固定映射,得到的返回地址和第一次的返回地址相同,代码如下:
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define FILE_SIZE 0x4000
int main(void)
{
int f;
int i;
unsigned char *mapped;
void *maddr;
maddr = mmap(NULL, FILE_SIZE, /* PROT_READ | PROT_WRITE */ PROT_NONE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
if(maddr == (void*)-1) {
printf("%s line %d, faild to map buffer, error %s.\n", __func__, __LINE__, strerror(errno));
return -1;
}
printf("%s line %d maddr = %p errno %s.\n", __func__, __LINE__, maddr, strerror(errno));
f = open("record.dat", O_RDWR);
if(f < 0) {
perror("fatal error, open file failure.\n");
return -1;
}
mapped = (unsigned char*)mmap(maddr, FILE_SIZE, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_SHARED, f, 0);
if(mapped == (unsigned char*)-1) {
printf("%s line %d, faild to map buffer, error %s.\n", __func__, __LINE__, strerror(errno));
return -1;
}
if(mapped != maddr) {
printf("%s line %d, mapped and maddr is not identical.\n", __func__, __LINE__);
} else {
printf("%s line %d, mapped and maddr is identical.\n", __func__, __LINE__);
}
printf("%s line %d, mapped = %p, maddr = %p.\n", __func__, __LINE__, mapped, maddr);
for(i = 0; i < 16; i ++) {
printf("0x%02x ", mapped[i]);
}
printf("\n");
msync((void *)mapped, FILE_SIZE, MS_ASYNC);
munmap((void *)mapped, FILE_SIZE);
close(f);
return 0;
}
$ ./a.out
main line 24 maddr = 0x7f64de281000 errno Success.
main line 41, mapped and maddr is identical.
main line 44, mapped = 0x7f64de281000, maddr = 0x7f64de281000.
0x00 0x01 0x02 0x03 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c 0x0d 0x0e 0x0f
/dev/mem的映射逻辑
/dev/mem 映射的是Linux全系统内存,并不仅仅是映射的物理内存,而是包括 brom, sram, iomem,dram 在内的所有地址空间.
虚拟内存映射过量怎么办?
虚拟内存过量提交,是指所有进程提交的虚拟内存的总和超过物理内存的容量,内核中的检测函数为__vm_enough_memory,如果申请的VM过量,则此函数返回-ENOMEM,新的VMA将无法创创建成功。
此函数的调用HOOK在内核所有的VMA创建流程中,比如进程创建,共享内存创建以及mmap中。
另外,可以通过proc文件系统节点来设置过量提交的策略,内核支持三种策略,可自行查阅。
$ cat /proc/sys/vm/overcommit_memory
0
用户态使用/dev/mem节点开发应用的例子,下图的例子来源于VIP的测试用例:
而phys_buffer_addr 恰好来源的就是环境变量中的物理地址设置:
export PHYSICAL_ADDR=0x48000000
export PHYSICAL_SIZE=0x200000
PCI BAR空间的 /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("%s line %d.failure to open dev mem\n", __func__, __LINE__);
return (-1);
}
map_base = mmap(NULL, 64*1024, PROT_READ, MAP_SHARED, fd, 0xa4100000);
if (map_base == 0)
{
printf("NULL pointer!\n");
}
else
{
printf("map successfull!\n");
}
unsigned char* addr;
unsigned int i;
addr = (unsigned char*)(map_base);
for (i = 0; i < 256; i ++)
{
if(i % 16 == 0) {
printf("\n 0x%p:", addr + i);
}
printf("0x%02x ", addr[i]);
}
printf("\n");
close(fd);
munmap(map_base, 64*1024);
return (1);
}
BIOS ROM映射:
结束
相关文章
- linux下让irb实现代码自动补全的功能
- Linux内核的内存屏障
- Linux设置http代理
- [uart]设置linux 串口的block方式
- Linux vi下常用实用命令
- Linux LED字符设备驱动 地址映射 ioremap 函数 iounmap 函数 I/O 内存访问函数
- 【Linux 内核 内存管理】物理分配页 ⑥ ( get_page_from_freelist 快速路径调用函数源码分析 | 检查内存区域水线 | 判定节点回收 | 判定回收距离 | 回收分配页 )
- 【Linux 内核 内存管理】引导内存分配器 bootmem ② ( bootmem_data 结构体源码分析 | bootmem_data 与内存节点 pglist_data 的关联 )
- 【Linux 内核 内存管理】物理内存组织结构 ⑥ ( 物理页 page 简介 | 物理页 page 与 MMU 内存管理单元 | 内存节点 pglist_data 与 物理页 page 联系 )
- 【Linux 内核 内存管理】mmap 系统调用源码分析 ① ( mmap 与 mmap2 系统调用 | Linux 内核中的 mmap 系统调用源码 )
- 【Linux 内核 内存管理】Linux 内核堆内存管理 ① ( 堆内存管理 | 内存描述符 mm_struct 结构体 | mm_struct 结构体中的 start_brk、brk 成员 )
- L43.linux命令每日一练 -- 第七章 Linux用户管理及用户信息查询命令 -- chage和chpasswd
- linux基本功系列之uniq命令实战
- unix & linux oralce用户 内存使用情况分析
- Linux下进程占用内存了解
- linux 使用split分割大文件
- 渗透测试===kali linux的安装
- Linux:内存
- Linux:库函数:libc: glibc
- Linux系统文件权限管理
- Linux 关于内存最疑惑的几个问题
- Linux 内存管理 页回收和swap机制