linux进程虚拟转物理,用户态进程如何得到虚拟地址对应的物理地址?

一般我们不需要从用户态得到进程虚拟地址对应的物理地址,因为一般来说用户进程是完全不关心物理地址的。

少数应用场景下,用户可能会关心,比如在用户态做DMA的场景(如DPDK之类的)。还有一些场景,比如想调试剖析每一页的内存占用情况,是否swap出去了等。

从用户态得到虚拟地址对应的物理地址,我们不可能去walk进程的page table,也没有权限。不过还好内核给我们提供了一个接口,叫pagemap,而且,这个接口与硬件的体系架构无关。在/proc/pid/下面有个文件叫pagemap,它会每个page,生成了一个64bit的描述符,来描述虚拟地址这一页对应的物理页帧号或者SWAP里面的便宜,详见文档:

linux/Documentation/admin-guide/mm/pagemap.rst

这64bit的描述如下:

c186a3da041c761d709ba60f5d5b1ee5.png

不同的体系架构的MMU不同,页表格式也不同,但是pagemap这个接口与具体页表的格式无关,可以说都被抽象化了。

下面我们忽略swap的影响(假设关闭了swap或者page一直是pin的状态),从DPDK抄一段虚拟地址转换为物理地址的代码:

1. #define phys_addr_t uint64_t

2. #define PFN_MASK_SIZE 8

3. .

4. phys_addr_t

5. rte_mem_virt2phy(const void *virtaddr)

6. {

7. int fd, retval;

8. uint64_t page, physaddr;

9. unsigned long virt_pfn;

10. int page_size;

11. off_t offset;

12. .

13. /* standard page size */

14. page_size = getpagesize();

15. .

16. fd = open("/proc/self/pagemap", O_RDONLY);

17. if (fd < 0) {

18. ...

19. }

20. .

21. virt_pfn = (unsigned long)virtaddr / page_size;

22. offset = sizeof(uint64_t) * virt_pfn;

23. if (lseek(fd, offset, SEEK_SET) == (off_t) -1) {

24. . ...

25. return -1;

26. }

27. .

28. retval = read(fd, &page, PFN_MASK_SIZE);

29. close(fd);

30. ...

31. .

32. /*

33. * the pfn (page frame number) are bits 0-54 (see

34. * pagemap.txt in linux Documentation)

35. */

36. if ((page & 0x7fffffffffffffULL) == 0)

37. return -1;

38. .

39. physaddr = ((page & 0x7fffffffffffffULL) * page_size)

40. + ((unsigned long)virtaddr % page_size);

41. .

42. return physaddr;

43. }

最后的一步是关键的计算过程:

1. physaddr = ((page & 0x7fffffffffffffULL) * page_size)

2. + ((unsigned long)virtaddr % page_size);

page & 0x7fffffffffffffULL取得了页帧号(PFN),乘以页的size得到这页起始的物理地址,之后加上virtaddr % page_size的页内偏移,得到最终的物理地址。

我们来实操一下调用上面的函数完成地址转化:

1. int main(int argc, char *argv[])

2. {

3. uint8_t *p = malloc(1024 * 1024);

4. .

5. *(p + 4096) = 10;

6. printf("virt:%p phys:%p\n", p + 4096, rte_mem_virt2phy(p + 4096));

7. .

8. *(p + 2 * 4096) = 10;

9. printf("virt:%p phys:%p\n", p + 2 * 4096, rte_mem_virt2phy(p + 2 * 4096));

10. }

运行结果如下:

1. ~$ sudo ./a.out

2. virt:0x7f81e402a010 phys:0x2b601010

3. virt:0x7f81e402b010 phys:0x3ceec010

内核态实现pagemap proc接口的代码位于:

fs/proc/task_mmu.c

其中比较核心的函数是把PTE转换为pagemap_entry的过程,有兴趣的童鞋可以仔细阅读下:

5760a1672728542be63b0a621487f4c4.png

特别留意画红线的位置,可以知道pagemap里面的那些flag是怎么被置上的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值