精华内容
下载资源
问答
  • 2020-01-03 20:08:27

    十进制转换为二进制

    0000    0

    0001    1

    0010   20011   3
    0100    4   0101      50110   60111     7
    1000     81001      9 1010   101011     11

     

    1100     121101      131110    141111     15

     

    更多相关内容
  • 这里,我们讲解一下Linux是如何将虚拟地址转换成物理地址的 一、地址转换 在进程中,我们不直接对物理地址进行操作,CPU在运行时,指定的地址要经过MMU转换后才能访问到真正的物理内存。 地址转换的过程分为两部分...

    这里,我们讲解一下Linux是如何将虚拟地址转换成物理地址的

    一、地址转换

    在这里插入图片描述

    在进程中,我们不直接对物理地址进行操作,CPU在运行时,指定的地址要经过MMU转换后才能访问到真正的物理内存。

    地址转换的过程分为两部分,分段和分页。

    分段机制简单的来说是将进程的代码、数据、栈分在不同的虚拟地址段上,从而避免进程间的互相影响。分段之前的地址我们称之为逻辑地址,它有两部分组成,高位的段选择符和低位的段内偏移。在分段时先用段选择符在相应的段描述符表中找到段描述符,也就是某一个段的基地址,再加上段内偏移量就得到了对应的线性地址,线性地址也称之为虚拟地址。

    而在实际的应用中,Linux为了增加可移植性并没有完整的使用分段机制,它让所有的段都指向相同的段地址范围,段的基地址都为0,这样逻辑地址和线性地址在数值上就相同了

    所以,这里我们分析的重点在分页,也就是由线性地址到物理地址的转换过程。

    二、Linux页表

    在这里插入图片描述

    Linux为了兼容32位和64位CPU,它需要一个统一的页面地址模型,目前常用的是4级页表模型。

    PGD 页全局目录

    PUD 页上级目录

    PMD 页中间目录

    PT 页表

    根据不同的需要,其中的某些页表可能未被使用。线性地址中每一部分的索引的大小会根据具体的计算机体系结构做相应的改变。举个例子来说,对于没有启用物理地址扩展功能的32位系统来说,两级页表就足够了,那么Linux会让线性地址中的页上级目录和页中间目录索引这两位置为0,从根本上就取消了这两个字段,但是这两个页目录在指针序列中的位置仍然被保留下来。也就是说寻址的过程中不能跳过页上级目录和页中间目录直接由页全局目录到页表,内核会将这两个页目录的表项都置为1

    三、Linux线性地址

    在这里插入图片描述

    由于64位处理器硬件的限制,它的地址线只有48条,所以线性地址实际使用的也只有48位

    在Linux中使用的4级页表结构,它的线性地址划分如上图所示。页全局目录的索引、页上级目录的索引、页中间目录的索引、页表的索引分别占了9位,最后页内偏移占了12位,共计48位,剩下的高位都是保留,留作以后扩展使用。

    在这种情况下,页面的大小都为4kb,每一个页表项大小为8bit,整个页表可以映射的空间是256TB。

    而新的Intel芯片的硬件规定可以进行5级的页表管理。所以在4.15的内核中,Linux已经在页全局目录和页上级目录之间又增加了一个新的页目录,叫做p4d页目录。这个页目录同32位中的情况一样,现在还未被使用,它的页目录项只有一个,线性地址中也没有它的索引位。

    这里有一个很重要的寄存器,CR3寄存器,它是一系列CPU控制寄存器之一,它用来保存当前进程的页全局目录的地址,寻页的开始就是从页全局目录开始的。那么页全局目录的地址又在哪呢?

    内核在创建一个进程时就会为它分配页全局目录,在进程描述符task_struct结构中有一个指向mm_struct结构的指针mm,而mm_struct结构是用来描述进程的虚拟地址空间的,在mm _struct中有个字段PGD,就是用来保存该进程的页全局目录的(物理)地址的。所以在进程切换的时候,操作系统通过访问task_struct结构,再访问mm_struct结构,最终找到PGD字段取得新进程的页全局目录的地址,填充到CR3寄存器中就完成页表的切换

    以上表项在page.h中定义

    四、模块编程举例

    好了了解了这些之后,我们在实际的系统中来看看寻页的过程是如何完成的

    结合上面的介绍,我们编写一个内核模块,把一个给定的虚地址转换为内存的物理地址:

    这个内核模块的主要功能是在内核中先申请一个页面,然后利用内核提供的函数按照寻页的步骤一步步查询各级页目录,最终找到对应的物理地址。这些步骤就相当于我们手动模拟了MMU单元的寻页过程

    paging_lowmem.c:

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/mm.h>
    #include <linux/mm_types.h>
    #include <linux/sched.h>
    #include <linux/export.h>
    #include <linux/delay.h>
    
    
    static unsigned long cr0,cr3;
    
    static unsigned long vaddr = 0;
    
    
    static void get_pgtable_macro(void)  //打印页机制中的一些重要参数
    {
        cr0 = read_cr0();
        cr3 = read_cr3_pa();
         
        printk("cr0 = 0x%lx, cr3 = 0x%lx\n",cr0,cr3);
        
        //这些宏是用来指示线性地址中相应字段所能映射的区域大小的对数的
        printk("PGDIR_SHIFT = %d\n", PGDIR_SHIFT);  
        printk("P4D_SHIFT = %d\n",P4D_SHIFT);
        printk("PUD_SHIFT = %d\n", PUD_SHIFT);
        printk("PMD_SHIFT = %d\n", PMD_SHIFT);
        printk("PAGE_SHIFT = %d\n", PAGE_SHIFT);   //指示page offset字段,映射的是一个页面的大小,一个页面大小是4k,转换成以2为底的对数就是12,其他的宏类似
     
     //下面的这些宏是用来指示相应的页目录表中的项的个数的,这些宏都是为了方便寻页时进行位运算的
        printk("PTRS_PER_PGD = %d\n", PTRS_PER_PGD);
        printk("PTRS_PER_P4D = %d\n", PTRS_PER_P4D);
        printk("PTRS_PER_PUD = %d\n", PTRS_PER_PUD);
        printk("PTRS_PER_PMD = %d\n", PTRS_PER_PMD);
        printk("PTRS_PER_PTE = %d\n", PTRS_PER_PTE);
        printk("PAGE_MASK = 0x%lx\n", PAGE_MASK);   //page_mask,页内偏移掩码,用来屏蔽掉page offset字段
    }
     
    static unsigned long vaddr2paddr(unsigned long vaddr)  //线性地址到物理地址转换
    {
        //首先为每个目录项创建一个变量将它们保存起来
        pgd_t *pgd;
        p4d_t *p4d;
        pud_t *pud;
        pmd_t *pmd;
        pte_t *pte;
        
        unsigned long paddr = 0;
        unsigned long page_addr = 0;
        unsigned long page_offset = 0;
        
        pgd = pgd_offset(current->mm,vaddr);  //第一个参数是当前进程的mm_struct结构(我们申请的线性地址空间是内核,所以应该查内核页表,又因为所有的进程都共享同一个内核页表,所以可以用当前进程的mm_struct结构来进行查找)
        printk("pgd_val = 0x%lx, pgd_index = %lu\n", pgd_val(*pgd),pgd_index(vaddr));
        if (pgd_none(*pgd)){
            printk("not mapped in pgd\n");
            return -1;
        }
    
        p4d = p4d_offset(pgd, vaddr);  //查找到的页全局目录项pgd作为下级查找的参数传入到p4d_offset中
        printk("p4d_val = 0x%lx, p4d_index = %lu\n", p4d_val(*p4d),p4d_index(vaddr));
        if(p4d_none(*p4d))
        { 
            printk("not mapped in p4d\n");
            return -1;
        }
    
        pud = pud_offset(p4d, vaddr);
        printk("pud_val = 0x%lx, pud_index = %lu\n", pud_val(*pud),pud_index(vaddr));
        if (pud_none(*pud)) {
            printk("not mapped in pud\n");
            return -1;
        }
     
        pmd = pmd_offset(pud, vaddr);
        printk("pmd_val = 0x%lx, pmd_index = %lu\n", pmd_val(*pmd),pmd_index(vaddr));
        if (pmd_none(*pmd)) {
            printk("not mapped in pmd\n");
            return -1;
        }
     
        pte = pte_offset_kernel(pmd, vaddr);  //与上面略有不同,这里表示在内核页表中查找,在进程页表中查找是另外一个完全不同的函数   这里最后取得了页表的线性地址
        printk("pte_val = 0x%lx, ptd_index = %lu\n", pte_val(*pte),pte_index(vaddr));
    
        if (pte_none(*pte)) {
            printk("not mapped in pte\n");
            return -1;
        }
        //从页表的线性地址中取出该页表所映射页框的物理地址
        page_addr = pte_val(*pte) & PAGE_MASK;    //取出其高48位
        //取出页偏移地址,页偏移量也就是线性地址中的低12位
        page_offset = vaddr & ~PAGE_MASK;
        //将两个地址拼接起来,就得到了想要的物理地址了
        paddr = page_addr | page_offset;
        printk("page_addr = %lx, page_offset = %lx\n", page_addr, page_offset);
        printk("vaddr = %lx, paddr = %lx\n", vaddr, paddr);
        return paddr;
    }
    
    static int __init v2p_init(void)    //内核模块的注册函数
    {
        unsigned long vaddr = 0 ;
        printk("vaddr to paddr module is running..\n");
        get_pgtable_macro();
        printk("\n");
        vaddr = __get_free_page(GFP_KERNEL);   //在内核的ZONE_NORMAL中申请了一块页面,GFP_KERNEL标志指示优先从内核的ZONE_NORMAL中申请页框
        if (vaddr == 0) {
            printk("__get_free_page failed..\n");
            return 0;
        }
        sprintf((char *)vaddr, "hello world from kernel");   //在地址中写入hello
        printk("get_page_vaddr=0x%lx\n", vaddr);
        vaddr2paddr(vaddr);
        ssleep(600);
        return 0;
    }
    static void __exit v2p_exit(void)    //内核模块的卸载函数
    {
        printk("vaddr to paddr module is leaving..\n");
        free_page(vaddr);   //将申请的线性地址空间释放掉
    }
    
    
    module_init(v2p_init);
    module_exit(v2p_exit);
    MODULE_LICENSE("GPL"); 
    
    

    Makefile文件如下:

    obj-m:= paging_lowmem.o
    PWD:= $(shell pwd)
    KERNELDIR:= /home/shupeiyao/linux-5.14.17
    
    all:
    	make -C $(KERNELDIR)  M=$(PWD) modules
    clean:
    	@rm -rf *.o *.mod.c *.mod.o *.ko *.order *.symvers .*.cmd .tmp_versions
    
    
    

    make之后
    将模块插入

    在这里插入图片描述

    用dmesg命令查看

    在这里插入图片描述

    我们可以看到PGD_SHIFT和PUD_SHIFT都是39,这也就意味着在线性地址中P4D这个字段是空的,我们也可以看到P4D的页目录项是1,这就和我们之前讲的一样,虽然Linux现在使用的5级页表模型,但是实际上使用的页表只有4个。

    PAGE_MASK是一个低12位都为0,其余位都为1的一个64位的数

    我们申请的线性地址是get_page_vaddr

    我们依次查找了它的页全局目录项的线性地址、页四级目录项的线性地址、页上级目录项的线性地址、页中间目录项的地址,最后得到了页表项的物理地址

    最后我们将线性地址vaddr转换成了物理地址paddr

    我们可以看到物理地址paddr最高位是8,转换到二进制就是最高位63位是1,这是一个x86平台上用来标识该物理页框是不能用来执行代码保护的一个保护位的,这里我们不去管它,其物理页框的物理地址就是 c184000

    好了,到这里我们就完成了从虚拟地址到物理地址的转换了

    如果以上内容对您有帮助,麻烦点赞收藏或者关注一波哦~

    展开全文
  • 虚拟地址转换为物理地址

    千次阅读 2014-07-24 19:10:47
    Linux采用页表的概念来管理虚拟空间,内核在处理虚拟地址时都必须将其转换为物理地址,然后处理器才能够访问。虚拟地址可以通过Linux的页表操作宏逐层查找到物理地址,简单来说需要将虚拟地址分段,每段地址都作为...

    应用程序只能提供一个虚拟地址,也可以通过如下方法获取物理地址,当然得调用驱动。

    Linux采用页表的概念来管理虚拟空间,内核在处理虚拟地址时都必须将其转换为物理地址,然后处理器才能够访问。虚拟地址可以通过Linux的页表操作宏逐层查找到物理地址,简单来说需要将虚拟地址分段,每段地址都作为索引指向页表,最后一级页表指向物理地址。
     Linux在2.6.11以后版本为了兼容各种处理器,采用四级页表结构:
     PGD:Page Global Directory,页全局目录,是顶级页表。
     PUD:Page Upper Directory,页上级目录,是第二级页表
     PMD:Page Middle Derectory,页中间目录,是第三级页表。
     PTE:Page Table Entry,页面表,最后一级页表,指向物理页面。
     可以通过数据结构mm_struct访问PGD找到物理页面,如图4-8,根据页表寻找物理地址的流程见4-9。
     
    图  Linux采用的4级页面
     

     简化的转换代码如下:
     static int vir2phy(unsigned long va) 
     {     
      struct task_struct *pcb_tmp;     
      pcb_tmp = current;     
      pgd_tmp = pgd_offset(pcb_tmp->mm,va);     
      pud_tmp = pud_offset(pgd_tmp,va);     
      pmd_tmp = pmd_offset(pud_tmp,va);     
      pte_tmp = pte_offset_kernel(pmd_tmp,va);      
      pa = (pte_val(*pte_tmp) & PAGE_MASK) |(va & ~PAGE_MASK);     
      return pa; 
     }

    pgd_offset(mm, addr) 接收内存描述符地址mm和线性地址addr作为参数。这个宏产生地址addr在页全局目录中相应表项的线性地址;
    通过内存描述符mm内的一个指针可以找到这个页全局目录。

    pud_offset(pgd, addr) 参数为指向页全局目录项的指针pgd和线性地址addr。这个宏产生页上级目录中目录项addr对应的线性地址。在两级或三级分页系统中,该宏产生pgd,即一个页全局目录项的地址。

    pmd_offset(pud, addr) 接收指向页上级目录项的指针pud和线性地址addr作为参数。这个宏产生目录项addr在页中间目录中的偏移地址。在两级或三级分页系统中,它产生pud,即页全局目录项的地址。

    pte_offset_kernel(dir, addr) 线性地址addr在页中间目录dir中有一个对应的项,该宏就产生这个对应项,即页表的线性地址。另外,该宏只在主内核页表上使用。

    展开全文
  • 虚拟地址与物理地址转换

    千次阅读 2021-05-10 23:30:38
    虚拟地址空间那篇文章中我们通过虚拟地址空间简单地介绍了虚拟地址空间,知道了应用程序中使用的是虚拟地址,需要通过MMU转换成物理地址,本文将详细介绍虚拟地址如何转换成物理地址。 页、页框、页表 linux操作...
  • 本文介绍保护方式下的段定义以及由段选择子及段内偏移构成的二维虚拟地址如何被转换为一维线性地址。 段定义和虚拟地址到线性地址转换 段是实现虚拟地址到线性地址转换机制的基础。在保护方式下,每个段由如下...
  • 虚拟地址到物理地址转换步骤

    千次阅读 2020-08-24 14:52:14
    已知一个虚拟地址0x01AF5518, 则转换的过程如下: 注意: 这里讨论的以Windows下普通模式分页的情况, 也就是2级页表的情况 1.首先把虚拟地址拆分成3个部分(低12位, 中10位, 高10位), 换成2进制如下:  -> 0000 0001...
  • 虚拟地址转物理地址

    2022-05-21 15:27:43
    操作系统启动过程中,如何完成物理地址虚拟地址转换虚拟地址又是如何翻译成物理地址的? 以qemu-system-riscv64为例,opensbi会将内核搬到内存0x80200000的位置,如果内核的.ld链接文件也是以0x80200000开始,...
  • Linux 虚拟地址到物理地址转换

    万次阅读 2019-03-08 13:58:14
    地址转换过程 概念 虚拟地址和物理地址的概念 CPU通过地址来访问内存中的单元,地址虚拟地址和物理地址之分,如果CPU没有MMU(Memory Management Unit,内存管理单元),或者有MMU但没有启用,CPU核在取指令或...
  • 用户态进程的虚拟地址如何转换成物理地址用户态进程的虚拟地址如何转换成物理地址?mmapmmap基础概念mmap内存映射原理mmap详解UMA和NUMA:mmap优点总结mmap相关函数 用户态进程的虚拟地址如何转换成物理地址? 区分...
  • 一个程序发出的虚拟地址虚拟页面号和页内偏移值两部分组成,组成见下: 4.2 分页内存管理是如何解决交换内存管理中的两个问题的? 1.空间浪费:通过将内存空间划分成大小一样的页面,并且将其作为内存分配的基本...
  • 内存由大量word或array组成,每个word或array都有与之关联的地址。现在,CPU的工作是从基于内存的程序计数器中...逻辑地址尤其由MMU或地址转换单元进行转换。该过程的输出是适当的物理地址或代码/数据在RAM中的位置。
  • 实验四 页式虚拟存储管理中地址转换和页式中断 FIFO 一实验目的 深入了解页式存储管理如何实现地址转换进一步认识页式虚拟存储管理中如何处理缺页中断以及页面置换算法 二实验主要内容 编写程序完成页式虚拟存储管理...
  • 页表就是用于将虚拟地址转换为物理地址转换关系表。访问虚拟地址时,计算机通过页表找到对应的实际物理地址访问。 我们在上一节介绍了内存管理模块概图, 怎么完成从pgd 到 page的转化呢? linux 内核code是...
  • 虚拟地址转换成物理地址

    万次阅读 2009-10-10 14:54:00
    有两个方法将一个虚拟地址转换成一个物理地址:使用 !vtop 扩展和使用 !pte 扩展。在Windows NT 4.0中还可以使用 !vpdd 扩展。使用 !vtop 进行地址转换假设你正在调试一台正在运行MyApp.exe进程的目标计算机,而且你...
  • Linux虚拟地址和物理地址转换

    千次阅读 2018-06-27 23:46:43
  • 虚拟地址转换成物理地址

    千次阅读 2011-05-24 16:29:00
    虚拟存储器的用户空间共有32个页面,每页1K,主存16K,假定某时刻系统为用户的第0、1、2、3页分配的物理块号为5、10、4、7,而该用户作业的长度为6页, 将十六进制的虚拟地址0A5CH、103CH、1A5CH转换成...
  • 精品课件
  • 说明: GDT:全局描述符表 LDT:局部描述符表 TI:标志位,为0表示描述符在GDT,为1表示描述符在LDT
  • Linux内存管理(二):ARMv8 地址转换

    千次阅读 2020-08-30 23:05:13
    arm64 地址转换
  • 操作系统页式虚拟存储管理中地址转换和缺页中断55 解析
  • Linux系统中的物理存储空间和虚拟存储空间的地址范围分别都是从0x00000000到0xFFFFFFFF,共4GB,但物理存储空间与虚拟存储空间布局完全不同 Linux运行在虚拟存储空间... 第二阶段的分页机制把线性地址转换成物理地址
  • 计算机中的存储层次结构都是在考虑性价比的前提条件下尽量满足程序员的这一需求,其中虚拟地址的产生在其中起到了至关重要的作用:它允许程序员不用考虑物理内存的使用情况而任意使用整个内存空间(CPU地址总线决定...
  • 在支持PAE(物理地址扩展)的X86系统上,虚拟地址可以分为几个部分,作为偏移量索引到3个表中: a.PDPT(Page Directory Pointer Table页目录指针表) b.PD(Page Directory页目录) c.PT(Page Table页表) d.PTE...
  • 编程实现LRU算法,模拟实现虚拟存储器的地址变换过程。
  • 虚拟地址到物理地址转换

    千次阅读 2017-05-18 21:46:48
    CPU将一个虚拟内存空间中的地址虚拟地址转换为物理地址,需要进行两步:首先将给定一个逻辑地址(其实是段内偏移量,这个一定要理解!!!),CPU要利用其段式内存管理单元,先将这个逻辑地址转换成一个线程地址...
  • 由于直接分析 linux arm32 mmu版 的启动代码会涉及到内存直接物理映射模式到开启虚拟地址映射模式的转换,这需要对 ARM32 中的虚拟地址实现机制有足够的了解才行,本文通过分析Arm Cortex-A 系列内存管理单元来分析...
  • 虚拟内存与地址转换

    千次阅读 2014-02-28 18:14:27
    虚拟内存无处不在:搞偏底层一点语言的同学可能经常碰到segment fault这类异常...本文主要讲虚拟内存如何把主存当做磁盘上地址空间的快取,以及虚拟内存上的地址到实际物理地址转换 一.虚拟内存用作高速缓存  

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 370,107
精华内容 148,042
关键字:

虚拟地址 转换