关键词:/dev/mem、/dev/kmem、mmap、__va、__pa、remap_pfn_range等等。
在日常工作中常有直接操作寄存器或者某一物理地址的需求,busybox中提供了devmem。通过它可以读写物理内存。
它的实现借助mmap和/dev/mem,通过mmap将/dev/mem物理地址映射到用户空间,devmem就可以像操作虚拟地址一样进行读写。
hexdump同样也可以类似devmem的功能。
如果需要在用户空间获取内核某个变量值,可以使用devkmem通过/dev/kmem进行。
下面分别介绍这三种工具。
1. devmem操作物理地址,它是如何做到的?
用户空间是无法直接操作物理地址的;但是日常工作中常需要对某一物理地址进行读写,尤其是寄存器。
devmem可以实现这个功能。那么devmem做了什么?/dev/mem在内核中优势如何实现的呢?
1.1 devmem工具使用
devmem使用介绍如下:
BusyBox v1.27.2 (2019-04-16 17:00:28 CST) multi-call binary.
Usage: devmem ADDRESS [WIDTH [VALUE]]
Read/write fromphysical address
ADDRESS Address to act upon
WIDTH Width (8/16/...)
VALUE Data to be written
devmem的能力有限,只能处理最大64字节的数目。
下面向0xfc20700这个地址写入32位数据0x12345678:
devmem 0xfc20700 32 0x12345678
然后从0xfc20700读取进行验证。
devmem 0xfc20700 32
0x12345678
1.2 devmem工具分析
从下面的代码可知,devmem解析参数,然后将地址转换成页面对齐的地址。mmap将/dev/mem的输入地址偏移的页面映射到用户空间,然后读取数值。
int devmem_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;int devmem_main(int argc UNUSED_PARAM, char **argv)
{void *map_base, *virt_addr;
uint64_t read_result;
uint64_t writeval= writeval; /*for compiler*/off_t target;
unsigned page_size, mapped_size, offset_in_page;intfd;
unsigned width= 8 * sizeof(int); /*ADDRESS*/
if (!argv[1])
bb_show_usage();
errno= 0;
target= bb_strtoull(argv[1], NULL, 0); /*allows hex, oct etc*/---------------第一个参数是地址
/*WIDTH*/
if (argv[2]) {------------------------------------------------------------------第二个参数,在写的情况下,需要知道写数据的位宽。if (isdigit(argv[2][0]) || argv[2][1])
width= xatou(argv[2]);else{static const char bhwl[] ALIGN1 = "bhwl";static const uint8_t sizes[] ALIGN1 ={8 * sizeof(char),8 * sizeof(short),8 * sizeof(int),8 * sizeof(long),0 /*bad*/};
width= strchrnul(bhwl, (argv[2][0] | 0x20)) -bhwl;
width=sizes[width];
}/*VALUE*/
if (argv[3])-----------------------------------------------------------------第三个参数,待写入数值。
writeval= bb_strtoull(argv[3], NULL, 0);
}else { /*argv[2] == NULL*/
/*make argv[3] to be a valid thing to fetch*/argv--;
}if(errno)
bb_show_usage();/*one of bb_strtouXX failed*/fd= xopen("/dev/mem", argv[3] ? (O_RDWR | O_SYNC) : (O_RDONLY |O_SYNC));-------根据第三个参数确定是以只读形式打开,还是以读写形式打开。/dev/mem代表整个内核空间。
mapped_size= page_size =getpagesize();
offset_in_page= (unsigned)target & (page_size - 1);-----------------------------对地址进行也对齐。if (offset_in_page + width >page_size) {----------------------------------------如果跨页,则mapped_size变成两个页面。/*This access spans pages.
* Must map two pages to make it possible:*/mapped_size*= 2;
}
map_base=mmap(NULL,
mapped_size,
argv[3] ? (PROT_READ |PROT_WRITE) : PROT_READ,
MAP_SHARED,
fd,
target& ~(off_t)(page_size - 1));---------------------------------------将/dev/mem文件的从target的页对齐偏移开始,映射mapped_size块大小内存。映射结果是map_base。if (map_base ==MAP_FAILED)
bb_perror_msg_and_die("mmap");//printf("Memory mapped at address %p.\n", map_base);
virt_addr= (char*)map_base +offset_in_page;if (!argv[3]) {switch(width) {case 8:
read_result= *(volatile uint8_t*)virt_addr;break;case 16:
read_result= *(volatile uint16_t*)virt_addr;break;case 32:
read_result= *(volatile uint32_t*)virt_addr;break;case 64:
read_result= *(volatile uint64_t*)virt_addr;break;default:
bb_error_msg_and_die("bad width");
}//printf("Value at address 0x%"OFF_FMT"X (%p): 0x%llX\n",//target, virt_addr,//(unsigned long long)read_result);
/*Zero-padded output shows the width of access just done*/printf("0x%0*llX\n", (width >> 2), (unsigned long long)read_result);------------读取数据并打印。
}else{switch(width) {case 8:*(volatile uint8_t*)virt_addr =