当前位置: 首页 > news >正文

[Linux内存管理-分页机制]—把一个虚拟地址转换为物理地址

    由于内核在不同的CPU上运行,甚至包括目前的64位机器。Linux内核提供了4级页表的管理机制,它可以兼容各种架构的CPU。

    一个虚拟地址会被分为5个部分:页全局目录PGD(Page Global Directory),页上级目录PUD (Page Upper Directory),页中间目录PMD(Page Middle Directory,页表PT (Page Table)以及 偏移量offset,其中的表项叫页表项PTE(Page table entry)。也就是说一个线性地址中除去偏移量,分别存放了4级目录表项的索引值。

   具体的线性地址翻译成物理地址的过程是:

     (1)首先从进程地址描述符中(mm_struct)中读取pgd字段的内容。它就是页全局目录的起始地址;

     (2)然后,页全局目录起始地址+页全局目录索引---->页上级目录的起始地址;

     (3)页上级目录+页上级目录索引---->页中间目录的起始地址;

     (4)页中间目录的起始地址+页中间目录的索引---->页表起始地址;

     (5)页表起始地址+索引---->页表项;

     (6)从页表项中取出物理页的基址,加上偏移量可以得到最终的物理地址。


以2级页表管理机制做一个原理性的说明,4级页表管理类似:

2级页表管理机制原理:



对于4级页表管理机制:

那么线性地址被分成五部分:



页表:

页表项的集合形成了页表。在一级页表内,页表项连续存放。在虚拟地址转为为物理地址过程中,没访问一次页表就需要访问一次内存。


页表项:

每个页表项的信息分为两部分:页框基地址和属性,对于我们查找物理地址来说有用的部分是页表基地址。




内核相关部分代码分析:

PAGE_SZIE  

/* page.h:PAGE_SHIFT determines the page size */  
#define PAGE_SHIFT	12
#define PAGE_SIZE	(_AC(1, UL) << PAGE_SHIFT)
#define PAGE_MASK	(~(PAGE_SIZE-1))
其中PAGE_SIZE表明了一个page的大小(2^12字节,4K大小)。我们用的是PAGE_MASK为 11111111111111111111000000000000(20位“1”,12位’0‘)实际上能够达到取物理地址基址和屏蔽页内地址的作用。

PGD

/*pgtable-2level.h*/
#define PGDIR_SHIFT	22
#define PGDIR_SIZE	(1UL << PGDIR_SHIFT)
#define PGDIR_MASK	(~(PGDIR_SIZE-1))

#define PTRS_PER_PGD<span style="white-space:pre">	</span>1024

/*pgtable.h*/
#define pgd_index(address)	(((address) >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))
/* to find an entry in a page-table-directory */
#define pgd_offset(mm, addr)	((mm)->pgd + pgd_index(addr))

从以上代码可以看出:完成转换步骤中的(1)(2)从mm_struct中获取了PGD起始地址;又从线性地址的高10位作为PGD的offset;从而获得了PUD 的其实地址。

说明:从源代码可以看出,对于不同平台 以及64位平台,每级页表的偏移量定义都是不相同的,主要用PGD_SHIFT 控制。但是计算原理都一样。

PUD

#define pud_index(address) (((address) >> PUD_SHIFT) & (PTRS_PER_PUD-1))
#define pud_offset(pgd, address) ((pud_t *) pgd)

PMD

/*pgtable.h*/
#define pmd_index(address) (((address) >> PMD_SHIFT) & (PTRS_PER_PMD-1))
#define pmd_offset(pud, address) ((pmd_t *) pud + pmd_index(address))

PTE

/*pgtable.h*/
#define pte_index(address) (((address) >> PAGE_SHIFT) & (PTRS_PER_PTE-1))
#define pmd_deref(pmd) (pmd_val(pmd) & _SEGMENT_ENTRY_ORIGIN)
#define pte_offset(pmd, addr) ((pte_t *) pmd_deref(*(pmd)) + pte_index(addr))
#define pte_offset_kernel(pmd, address) pte_offset(pmd,address)

类型检查:

/*
 * These are used to make use of C type-checking..
 */
typedef struct { unsigned long	pte;	} pte_t;
typedef struct { unsigned long	ste[64];} pmd_t;
typedef struct { pmd_t		pue[1]; } pud_t;
typedef struct { pud_t		pge[1];	} pgd_t;
typedef struct { unsigned long	pgprot;	} pgprot_t;
typedef struct page *pgtable_t;

#define pte_val(x)	((x).pte)
#define pmd_val(x)	((x).ste[0])
#define pud_val(x)	((x).pue[0])
#define pgd_val(x)	((x).pge[0])

其实pte、pgd、pud、pmd都是unsigned Long类型,但是Linux为了实现更严格的类型检查。用作指针的时候用pgd_t * ,当取地址的时候用pgd_val(x)。



内核模块实现虚拟地址转换为物理地址代码:

/*get_physic_addr.c */ 

内核版本:Linux-2.6.35

#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/mm.h>
#include<linux/mm_types.h>
#include<asm/pgtable.h>
#include<linux/vmalloc.h>
#include<linux/sched.h>




static  unsigned long  vaddr_to_paddr(struct mm_struct *mm,unsigned long vaddr)
{
    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *pte;


    unsigned long paddr=0;
    unsigned long page_addr=0;
    unsigned long page_offset=0;
//    struct mm_struct *mm=current->mm;


    pgd=pgd_offset(mm,vaddr);  /*获得addr对应的pgd的地址*/
    if(pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
            goto out; 


    pud=pud_offset(pgd,vaddr);
    if(pud_none(*pud) || unlikely(pud_bad(*pud)))
            goto out; 


    pmd=pmd_offset(pud,vaddr);
    if(pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
            goto out; 


    
    pte=pte_offset_kernel(pmd,vaddr);
    if(pte_none(*pte))
        goto out;
   
    page_addr=pte_val(*pte) & PAGE_MASK;
    page_offset= vaddr & ~PAGE_MASK;
    paddr=page_addr | page_offset;
    printk("page_addr=0x%lx,page_offset=0x%lx\n",page_addr,page_offset);
    printk("vaddr=%lx,paddr=%lx\n",vaddr,paddr);


out:
    return paddr;
}






static int __init get_physic_init(void)
{
    printk("<1>get_physic_addr Modules running.....\n");
    
    unsigned long vaddr=0;


    vaddr=(unsigned long)vmalloc(1000*sizeof(char));
    if(vaddr==0){
        printk("vmalloc failed...\n");
        return 0;
    }
    vaddr_to_paddr(current->mm,vaddr);


    vfree((void*)vaddr);
    return 0;
}


static void __exit get_physic_exit(void)
{
    printk("<1>get_phyisc_addr Modules exit\n");


}




module_init(get_physic_init);
module_exit(get_physic_exit);


MODULE_LICENSE("GPL");



/*Makefile*/

obj-m:=get_physic_addr.o


CURRENT_PATH=$(shell pwd)
LINUX_KERNEL_PATH=/usr/src/kernels/$(shell uname -r)

all:
        make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
clean:
        make -C $(LINUX_KERNEL_PATH) m=$(CURRENT_PATH) clean



结果:


学习参考:http://edsionte.com/techblog/archives/3435


相关文章:

  • C#也能动态生成Word文档并填充数据
  • Epoll实现服务器高并发
  • Linux中实现线程池
  • 《BREW进阶与精通——3G移动增值业务的运营、定制与开发》连载之18---商业模式...
  • 凤巢能否成功关键还看用户体验
  • 谁能不查字典把这文章念出来我就服谁
  • Android开发指南-框架主题-意图和意图过滤器
  • XML相关资源
  • Android开发指南-框架主题-基础知识
  • 目前的一些感悟
  • Android开发指南-框架主题-用户界面
  • 部分生僻字
  • SQLServer中的Scanf和Printf
  • Java Swing入门基础 (转)
  • 【三字经全文】
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • Bootstrap JS插件Alert源码分析
  • CAP理论的例子讲解
  • chrome扩展demo1-小时钟
  • codis proxy处理流程
  • javascript数组去重/查找/插入/删除
  • Linux编程学习笔记 | Linux IO学习[1] - 文件IO
  • Linux链接文件
  • Python 使用 Tornado 框架实现 WebHook 自动部署 Git 项目
  • TCP拥塞控制
  • 如何利用MongoDB打造TOP榜小程序
  • gunicorn工作原理
  • 回归生活:清理微信公众号
  • 如何正确理解,内页权重高于首页?
  • 专访Pony.ai 楼天城:自动驾驶已经走过了“从0到1”,“规模”是行业的分水岭| 自动驾驶这十年 ...
  • ​iOS安全加固方法及实现
  • ​MPV,汽车产品里一个特殊品类的进化过程
  • ​你们这样子,耽误我的工作进度怎么办?
  • ### Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTr
  • #NOIP 2014# day.1 T2 联合权值
  • $var=htmlencode(“‘);alert(‘2“); 的个人理解
  • (3)nginx 配置(nginx.conf)
  • (env: Windows,mp,1.06.2308310; lib: 3.2.4) uniapp微信小程序
  • (LeetCode) T14. Longest Common Prefix
  • (Oracle)SQL优化技巧(一):分页查询
  • (Redis使用系列) SpringBoot 中对应2.0.x版本的Redis配置 一
  • (编程语言界的丐帮 C#).NET MD5 HASH 哈希 加密 与JAVA 互通
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (十五)使用Nexus创建Maven私服
  • (一)u-boot-nand.bin的下载
  • (原創) 未来三学期想要修的课 (日記)
  • (转)IIS6 ASP 0251超过响应缓冲区限制错误的解决方法
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • **PHP分步表单提交思路(分页表单提交)
  • .FileZilla的使用和主动模式被动模式介绍
  • .net 8 发布了,试下微软最近强推的MAUI
  • .NET Framework 4.6.2改进了WPF和安全性
  • .NET 命令行参数包含应用程序路径吗?
  • .net 使用ajax控件后如何调用前端脚本