zoukankan      html  css  js  c++  java
  • Linux在IA-32体系结构下的地址映射

    1.概览

    2.逻辑地址到线性地址

    逻辑地址到线性地址的映射在IA-32体系结构中又被称为段式映射。如上图所示,段式映射我们首先需要获取逻辑地址和段选择符,段选择符用于获取GDT中段的基地址,将逻辑地址作为偏移和段基地址相加获得线性地址。如图为详细的逻辑地址到线性地址的映射过程:

    • 根据指令的性质来确定使用哪一个段寄存器;
    • 根据段寄存器内容,找到相应的地址段描述符结构,段描述符结构一般放在GDT,LDT,TR或IDT中,描述表的起始地址保存在GDTR,LDTR,TR和IDTR寄存器中;
    • 从地址描述结构中找到段的基地址;
    • 将指令发出的地址作为位移,与段描述符中规定的段长度比较,看是否越界;
    • 根据指令的性质和段描述符中的权限来看权限是否合适;
    • 将指令中发出的地址作为位移,与基地址相加得到线性地址;

    段选择符在段寄存器中,例如CS,DS。段描述符在内存管理寄存器中,如GDTR,LDTR,IDTR和TR。段选择符内容如下

    段描述符内容如下:

    在C语言中我们访问一个局部变量的地址将其打印出来,此时这个地址即为逻辑地址,那么这个地址到线性地址的转换过程为什么样的。

    #include<stdio.h>
    
    int main()
    {
        unsigned long x = 0z01234567;
        printf("the x address is 0x%x
    ", &x);
        return 0;
    }

     

    上面的程序打印出了逻辑地址,按照逻辑地址到线性地址的转换方式,我们此时要从段寄存器中获取段选择符。我们知道局部变量是存放在桟区的,所以我们可以从堆栈寄存器SS获取段选择符。内核创建一个线程时会先将段寄存器设置好,IA-32架构的实现代码位于arch/x86/kernel/process_32.c:200行处

    void
    start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
    {
        set_user_gs(regs, 0);
        regs->fs        = 0;
        regs->ds        = __USER_DS;
        regs->es        = __USER_DS;
        regs->ss        = __USER_DS;
        regs->cs        = __USER_CS;
        regs->ip        = new_ip;
        regs->sp        = new_sp;
        regs->flags        = X86_EFLAGS_IF;
        /*
         * force it to the iret return path by making it look as if there was
         * some work pending.
         */
        set_thread_flag(TIF_NOTIFY_RESUME);
    }

    从代码中我们可以看到,内核只使用了两个段,分别为代码段(CS)和数据段(DS),并且每个进程的CS和DS都相同,只有EIP和ESP不同。此时从SS段寄存器中获取段选择符,__USER_DS的值定义在arch/x86/include/asm/segment.h中:

    #define GDT_ENTRY_DEFAULT_USER_DS 15
    #define GDT_ENTRY_DEFAULT_USER_CS 14
    #define __USER_DS (GDT_ENTRY_DEFAULT_USER_DS*8+3) #define __USER_CS (GDT_ENTRY_DEFAULT_USER_CS*8+3)

    此时SS的二进制为:0000 0000 0111 1011。通过上面的段选择符结构图,高13bit为index,此时index值为15,第3bit为0,表示使用GDT全局描述表。此时我们就能够使用GDT表中索引为15处的地址为段基地址加上偏移地址得到线性地址了。GDT表的位置上面已经说了是由GDTR寄存器存储的,在kernel中GDTR定义在aarch/x86/kernel/cpu/common.c中 

    DEFINE_PER_CPU_PAGE_ALIGNED(struct gdt_page, gdt_page) = { .gdt = {
    #ifdef CONFIG_X86_64
        /*
         * We need valid kernel segments for data and code in long mode too
         * IRET will check the segment types  kkeil 2000/10/28
         * Also sysret mandates a special GDT layout
         *
         * TLS descriptors are currently at a different place compared to i386.
         * Hopefully nobody expects them at a fixed place (Wine?)
         */
        [GDT_ENTRY_KERNEL32_CS]        = GDT_ENTRY_INIT(0xc09b, 0, 0xfffff),
        [GDT_ENTRY_KERNEL_CS]        = GDT_ENTRY_INIT(0xa09b, 0, 0xfffff),
        [GDT_ENTRY_KERNEL_DS]        = GDT_ENTRY_INIT(0xc093, 0, 0xfffff),
        [GDT_ENTRY_DEFAULT_USER32_CS]    = GDT_ENTRY_INIT(0xc0fb, 0, 0xfffff),
        [GDT_ENTRY_DEFAULT_USER_DS]    = GDT_ENTRY_INIT(0xc0f3, 0, 0xfffff),
        [GDT_ENTRY_DEFAULT_USER_CS]    = GDT_ENTRY_INIT(0xa0fb, 0, 0xfffff),
    #else
        [GDT_ENTRY_KERNEL_CS]        = GDT_ENTRY_INIT(0xc09a, 0, 0xfffff),
        [GDT_ENTRY_KERNEL_DS]        = GDT_ENTRY_INIT(0xc092, 0, 0xfffff),
        [GDT_ENTRY_DEFAULT_USER_CS]    = GDT_ENTRY_INIT(0xc0fa, 0, 0xfffff),
        [GDT_ENTRY_DEFAULT_USER_DS]    = GDT_ENTRY_INIT(0xc0f2, 0, 0xfffff),
        /*
         * Segments used for calling PnP BIOS have byte granularity.
         * They code segments and data segments have fixed 64k limits,
         * the transfer segment sizes are set at run time.
         */
        /* 32-bit code */
        [GDT_ENTRY_PNPBIOS_CS32]    = GDT_ENTRY_INIT(0x409a, 0, 0xffff),
        /* 16-bit code */
        [GDT_ENTRY_PNPBIOS_CS16]    = GDT_ENTRY_INIT(0x009a, 0, 0xffff),
        /* 16-bit data */
        [GDT_ENTRY_PNPBIOS_DS]        = GDT_ENTRY_INIT(0x0092, 0, 0xffff),
        /* 16-bit data */
        [GDT_ENTRY_PNPBIOS_TS1]        = GDT_ENTRY_INIT(0x0092, 0, 0),
        /* 16-bit data */
        [GDT_ENTRY_PNPBIOS_TS2]        = GDT_ENTRY_INIT(0x0092, 0, 0),
        /*
         * The APM segments have byte granularity and their bases
         * are set at run time.  All have 64k limits.
         */
        /* 32-bit code */
        [GDT_ENTRY_APMBIOS_BASE]    = GDT_ENTRY_INIT(0x409a, 0, 0xffff),
        /* 16-bit code */
        [GDT_ENTRY_APMBIOS_BASE+1]    = GDT_ENTRY_INIT(0x009a, 0, 0xffff),
        /* data */
        [GDT_ENTRY_APMBIOS_BASE+2]    = GDT_ENTRY_INIT(0x4092, 0, 0xffff),
    
        [GDT_ENTRY_ESPFIX_SS]        = GDT_ENTRY_INIT(0xc092, 0, 0xfffff),
        [GDT_ENTRY_PERCPU]        = GDT_ENTRY_INIT(0xc092, 0, 0xfffff),
        GDT_STACK_CANARY_INIT
    #endif
    } };

    GDT_ENTRY_INIT定义在arch/x86/kernel/cpu/desc_defs.h中

    #define GDT_ENTRY_INIT(flags, base, limit) { { { 
            .a = ((limit) & 0xffff) | (((base) & 0xffff) << 16), 
            .b = (((base) & 0xff0000) >> 16) | (((flags) & 0xf0ff) << 8) | 
                ((limit) & 0xf0000) | ((base) & 0xff000000), 
        } } }

    GDT_ENTRY_DEFAULT_USER_DS为15时,在GDT表中对应的地址为GDT_ENTRY_INIT(0xc0f2, 0, 0xfffff),此时基地址base为0,segment limit为0xfffff,线性地址等于GDT中的基地址加上逻辑地址,基地址为0,所以在linux kernel中线性地址和逻辑地址是相等的。

    3.线性地址到物理地址 待补充

    将线性地址最终映射到物理地址的过程称为页式映射。从线性地址到物理地址的映射过程为:

    • 从CR3寄存器中获取页面目录的基地址;
    • 以线性地址dir位段作为下标,在目录中取得相应页面表的基地址;
    • 以线性地址中的page位段作为下标,在所得到的页面目录中获取相应的页面描述项;
    • 将页面描述项中给出的页面基地址与线性地址中的offset位段相加得到物理地址;

    线性地址到物理地址的映射过程如下图所示:

    每个进程都有自己的地址空间,不同的进程就有不同的CR3寄存器,CR3寄存器的值一般保存在进程控制块中,例如task_struct结构体中,32bit时CR3寄存器页面项如图:

    从上面描述的过程中可知,我们首先要获得CR3寄存器的值,内核在创建进程时会分配页面目录,页面目录地址保存在task_struct结构体中,task_struct结构体中有一个mm_struct结构体中有一个pgd字段用来存储CR3寄存器的值,此段代码位于kernel/fork.c中

    static inline int mm_alloc_pgd(struct mm_struct *mm)
    {
        mm->pgd = pgd_alloc(mm);
        if (unlikely(!mm->pgd))
            return -ENOMEM;
        return 0;
    }

    在进程切换的过程中,会将进程页面目录的基地址加载到CR3寄存器,代码位于arch/x86/include/asm/mmu_context.h中

    static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next,
                     struct task_struct *tsk)
    {
        unsigned cpu = smp_processor_id();
    
        if (likely(prev != next)) {
    #ifdef CONFIG_SMP
            this_cpu_write(cpu_tlbstate.state, TLBSTATE_OK);
            this_cpu_write(cpu_tlbstate.active_mm, next);
    #endif
            cpumask_set_cpu(cpu, mm_cpumask(next));
    
            /* Re-load page tables */
            load_cr3(next->pgd);
            trace_tlb_flush(TLB_FLUSH_ON_TASK_SWITCH, TLB_FLUSH_ALL);
    
            /* Stop flush ipis for the previous mm */
            cpumask_clear_cpu(cpu, mm_cpumask(prev));
    
            /* Load the LDT, if the LDT is different: */
            if (unlikely(prev->context.ldt != next->context.ldt))
                load_LDT_nolock(&next->context);
        }
    #ifdef CONFIG_SMP
          else {
            this_cpu_write(cpu_tlbstate.state, TLBSTATE_OK);
            BUG_ON(this_cpu_read(cpu_tlbstate.active_mm) != next);
    
            if (!cpumask_test_cpu(cpu, mm_cpumask(next))) {
                /*
                 * On established mms, the mm_cpumask is only changed
                 * from irq context, from ptep_clear_flush() while in
                 * lazy tlb mode, and here. Irqs are blocked during
                 * schedule, protecting us from simultaneous changes.
                 */
                cpumask_set_cpu(cpu, mm_cpumask(next));
                /*
                 * We were in lazy tlb mode and leave_mm disabled
                 * tlb flush IPI delivery. We must reload CR3
                 * to make sure to use no freed page tables.
                 */
                load_cr3(next->pgd);
                trace_tlb_flush(TLB_FLUSH_ON_TASK_SWITCH, TLB_FLUSH_ALL);
                load_LDT_nolock(&next->context);
            }
        }
    #endif
    }
  • 相关阅读:
    jquery实现选项卡(两句即可实现)
    常用特效积累
    jquery学习笔记
    idong常用js总结
    织梦添加幻灯片的方法
    LeetCode "Copy List with Random Pointer"
    LeetCode "Remove Nth Node From End of List"
    LeetCode "Sqrt(x)"
    LeetCode "Construct Binary Tree from Inorder and Postorder Traversal"
    LeetCode "Construct Binary Tree from Preorder and Inorder Traversal"
  • 原文地址:https://www.cnblogs.com/elvalad/p/4273397.html
Copyright © 2011-2022 走看看