在Linux系统中,物理地址是硬件内存的实际位置,而虚拟地址是内核或进程通过内存管理单元(MMU)映射后的逻辑地址,查看物理地址在驱动开发、内存调试、硬件交互等场景中至关重要,本文将详细介绍Linux内核中查看物理地址的方法及相关工具。
虚拟地址与物理地址的转换基础
Linux内核通过页表管理虚拟地址与物理地址的映射,在x86架构中,内核空间的高地址(如3GB~4GB)是线性映射区域,即虚拟地址与物理地址存在固定偏移(通常为PAGE_OFFSET);而在ARM等架构中,线性映射的规则可能因内核版本和配置而异,理解这种映射关系是查看物理地址的前提。
内核提供了多种函数和接口实现地址转换,主要分为两类:内核空间地址转换(适用于驱动、内核模块)和用户空间间接访问(通过特殊文件或工具)。
内核空间中查看物理地址的方法
直接转换函数
内核提供了多个宏和函数用于虚拟地址到物理地址的转换,适用于不同的内存区域:
-
virt_to_phys()
:将内核线性映射区域的虚拟地址转换为物理地址。
原理:线性映射区域中,虚拟地址 = 物理地址 + PAGE_OFFSET(x86架构下PAGE_OFFSET通常为0xC0000000),因此物理地址 = 虚拟地址 – PAGE_OFFSET。
示例(内核模块中):#include <asm/page.h> unsigned long virt_addr = 0xC0400000; // 内核虚拟地址 unsigned long phys_addr = virt_to_phys((void*)virt_addr); printk("Physical address: 0x%lxn", phys_addr);
注意:仅适用于线性映射区域,对ioremap映射的设备地址无效。
-
phys_to_virt()
:物理地址转虚拟地址(与virt_to_phys()
互逆)。 -
page_to_phys()
:页描述符转换为物理地址,内核中内存以页为单位管理,通过页描述符(struct page*
)可获取其物理地址:#include <asm/page.h> struct page *page = virt_to_page(virt_addr); // 虚拟地址对应的页 unsigned long phys_addr = page_to_phys(page);
-
dma_map_single()
:用于DMA场景,将内核虚拟地址映射为DMA物理地址,返回的地址是设备可见的物理地址:#include <linux/dma-mapping.h> void *virt_addr = kmalloc(size, GFP_KERNEL); dma_addr_t dma_addr = dma_map_single(dev, virt_addr, size, DMA_TO_DEVICE);
/proc/iomem
:查看物理内存布局
/proc/iomem
文件记录了系统物理内存的分配情况,包括保留区域(如BIOS、设备寄存器)、可用内存等,通过读取该文件可快速定位特定物理地址的归属:
cat /proc/iomem
输出示例:
00000000-00000fff : Reserved
00001000-0009fbff : System RAM
00001000-0009fbff : Kernel code
0009fc00-000fffff : Kernel data
f0000000-ffffffff : PCI Bus
System RAM”为可用物理内存,“PCI Bus”为设备映射的内存区域。
/proc/meminfo
:内存使用统计
/proc/meminfo
虽不直接显示物理地址,但提供了内存总量、可用量等信息,辅助判断物理内存状态:
cat /proc/meminfo
内核模块中的物理地址获取
在编写字符设备驱动时,若需操作设备的物理内存(如显存、寄存器),可通过ioremap()
将物理地址映射到内核虚拟地址,再通过上述函数转换:
#include <linux/io.h> void __iomem *virt_addr = ioremap(phys_addr, size); // 物理地址转内核虚拟地址 // 操作virt_addr(如读写字节) iowrite8(0x12, virt_addr); iounmap(virt_addr); // 释放映射
用户空间查看物理地址的方法
用户空间程序无法直接访问物理地址(受MMU保护),但可通过以下间接方式获取信息:
/dev/mem
设备文件
/dev/mem
提供了对物理内存的字符设备接口,需root权限且开启CONFIG_STRICT_DEVMEM
内核选项(默认开启,仅允许访问特定区域)。
示例(读取物理地址0x100000处的数据):
#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main() { int fd = open("/dev/mem", O_RDONLY); if (fd < 0) { perror("open /dev/mem"); return 1; } unsigned char data; lseek(fd, 0x100000, SEEK_SET); // 定位到物理地址0x100000 read(fd, &data, 1); // 读取1字节 printf("Data at 0x100000: 0x%02xn", data); close(fd); return 0; }
限制:现代Linux内核可能限制对内核内存区域的访问,仅允许访问设备保留的物理地址。
/proc/kcore
/proc/kcore
是内核内存的镜像文件,格式与ELF core dump类似,可通过gdb
或readelf
分析,间接获取物理地址信息:
gdb /proc/kcore (gdb) p/x *(unsigned long*)0xC0400000 # 查看内核虚拟地址对应的物理内存
注意:/proc/kcore
内容庞大,需结合符号表(/proc/kallsyms
)使用。
sysfs
内存信息
/sys/devices/system/memory/
目录下按内存块(memory block)组织,每个块包含起始物理地址信息:
ls /sys/devices/system/memory/ # 输出:memory0, memory1, ... cat /sys/devices/system/memory/memory0/phys_index # 内存块索引 cat /sys/devices/system/memory/memory0/start_phys_index # 起始物理索引(需乘以PAGE_SIZE)
调试工具:crash
与kgdb
crash
工具
crash
是强大的内核调试工具,可分析物理内存、内核数据结构:
crash /var/crash/vmcore /usr/lib/debug/boot/vmlinux-$(uname -r) (crash) phys 0xC0400000 # 查看物理地址0xC0400000的内容 (crash) map 0xC0400000 # 查看虚拟地址的内存映射
kgdb
+ gdb
kgdb
是内核调试器,需通过串口或网络连接,配合gdb
可实时查看物理地址:
gdb /usr/lib/debug/boot/vmlinux-$(uname -r) (gdb) target remote /dev/ttyS0 # 连接kgdb服务器 (gdb) p/x $phys_addr # 查看物理地址寄存器
注意事项
- 权限与安全性:直接操作物理地址可能导致系统崩溃,需谨慎使用root权限。
- 架构差异:x86、ARM等架构的线性映射规则不同,转换函数需适配架构。
- 内核配置:部分功能(如
/dev/mem
访问)依赖内核选项,需确保CONFIG_DEVMEM
或CONFIG_STRICT_DEVMEM
开启。 - DMA地址:设备DMA使用的物理地址可能经过IOMMU转换,需通过
dma_map_*
系列函数处理。
相关问答FAQs
Q1:用户空间程序如何直接读取物理内存?
A:用户空间可通过/dev/mem
设备文件读取物理内存,但需满足以下条件:
- 以root权限运行;
- 内核开启
CONFIG_DEVMEM
选项(默认开启); - 仅允许访问非内核保留区域(如设备寄存器),访问内核内存区域会被拒绝。
示例代码见上文“用户空间查看物理地址的方法”中的/dev/mem
示例。
Q2:为什么在内核模块中使用virt_to_phys()
转换后得到的物理地址不正确?
A:可能的原因包括:
- 地址不在线性映射区域:
virt_to_phys()
仅适用于内核线性映射的虚拟地址(如kmalloc分配的内存),对ioremap()
映射的设备地址无效,后者需通过ioremap_cache()
或ioremap_wc()
获取的虚拟地址,其物理地址即为ioremap()
传入的参数; - NUMA架构影响:在NUMA系统中,物理地址分配与节点相关,需结合
virt_to_page()
和page_to_phys()
结合节点信息转换; - 高内存(HighMem):32位系统的高内存区域(如ZONE_HIGHMEM)的虚拟地址可能无法直接转换为物理地址,需通过
kmap()
临时映射后再转换。
通过以上方法,可灵活实现Linux内核中物理地址的查看与调试,适用于开发、测试及运维中的各类场景。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/16967.html