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\n", &x);
return ;
}

上面的程序打印出了逻辑地址,按照逻辑地址到线性地址的转换方式,我们此时要从段寄存器中获取段选择符。我们知道局部变量是存放在桟区的,所以我们可以从堆栈寄存器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, );
regs->fs = ;
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, , 0xfffff),
[GDT_ENTRY_KERNEL_CS] = GDT_ENTRY_INIT(0xa09b, , 0xfffff),
[GDT_ENTRY_KERNEL_DS] = GDT_ENTRY_INIT(0xc093, , 0xfffff),
[GDT_ENTRY_DEFAULT_USER32_CS] = GDT_ENTRY_INIT(0xc0fb, , 0xfffff),
[GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f3, , 0xfffff),
[GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xa0fb, , 0xfffff),
#else
[GDT_ENTRY_KERNEL_CS] = GDT_ENTRY_INIT(0xc09a, , 0xfffff),
[GDT_ENTRY_KERNEL_DS] = GDT_ENTRY_INIT(0xc092, , 0xfffff),
[GDT_ENTRY_DEFAULT_USER_CS] = GDT_ENTRY_INIT(0xc0fa, , 0xfffff),
[GDT_ENTRY_DEFAULT_USER_DS] = GDT_ENTRY_INIT(0xc0f2, , 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, , 0xffff),
/* 16-bit code */
[GDT_ENTRY_PNPBIOS_CS16] = GDT_ENTRY_INIT(0x009a, , 0xffff),
/* 16-bit data */
[GDT_ENTRY_PNPBIOS_DS] = GDT_ENTRY_INIT(0x0092, , 0xffff),
/* 16-bit data */
[GDT_ENTRY_PNPBIOS_TS1] = GDT_ENTRY_INIT(0x0092, , ),
/* 16-bit data */
[GDT_ENTRY_PNPBIOS_TS2] = GDT_ENTRY_INIT(0x0092, , ),
/*
* 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, , 0xffff),
/* 16-bit code */
[GDT_ENTRY_APMBIOS_BASE+] = GDT_ENTRY_INIT(0x009a, , 0xffff),
/* data */
[GDT_ENTRY_APMBIOS_BASE+] = GDT_ENTRY_INIT(0x4092, , 0xffff), [GDT_ENTRY_ESPFIX_SS] = GDT_ENTRY_INIT(0xc092, , 0xfffff),
[GDT_ENTRY_PERCPU] = GDT_ENTRY_INIT(0xc092, , 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) << ), \
.b = (((base) & 0xff0000) >> ) | (((flags) & 0xf0ff) << ) | \
((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 ;
}

在进程切换的过程中,会将进程页面目录的基地址加载到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
}

Linux在IA-32体系结构下的地址映射的更多相关文章

  1. 32位Windows7上8G内存使用感受+xp 32位下使用8G内存 (转)

    32位Windows7上8G内存使用感受+xp 32位下使用8G内存 博客分类: Windows XPWindowsIE企业应用软件测试  我推荐做开发的朋友:赶快加入8G的行列吧....呵呵..超爽 ...

  2. Tomcat Can't load AMD 64-bit .dll on a IA 32

    Java.lang.UnsatisfiedLinkError: C:\apache\apache-tomcat-7.0.14\bin\tcnative-1.dll: Can't load AMD 64 ...

  3. linux内核学习之三:linux中的"32位"与"64位"

    在通用PC领域,不论是windows还是linux界,我们都会经常听到"32位"与"64位"的说法,类似的还有"x86"与"x86 ...

  4. linux中的"32位"与"64位"

    linux内核学习之三:linux中的"32位"与"64位" 在通用PC领域,不论是windows还是linux界,我们都会经常听到"32位" ...

  5. 【故障•监听】TNS-12518、TNS-00517和 Linux Error:32:Broken pipe

    [故障|监听]TNS-12518.TNS-00517和 Linux Error:32:Broken pipe 1.1  BLOG文档结构图 1.2  前言部分 1.2.1  导读和注意事项 各位技术爱 ...

  6. Linux驱动之中断处理体系结构简析

    S3C2440中的中断处理最终是通过IRQ实现的,在Linux驱动之异常处理体系结构简析已经介绍了IRQ异常的处理过程,最终分析到了一个C函数asm_do_IRQ,接下来继续分析asm_do_IRQ, ...

  7. linux gcc 区分32位或64位编译 && 请问arm存储,是以小端格式还是以大端格式?

    linux gcc 区分32位或64位编译   Linux系统下程序如何区分是64位系统还是32位系统 经过对include的翻查,最后确定gcc以__i386__来 进行32位编码,而以__x86_ ...

  8. 查看linux机器是32位还是64位的方法

    file /sbin/init 或者 file /bin/ls/sbin/init: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dyna ...

  9. 如何查看linux系统是32位还是64位

    1.#uname -a 如果有x86_64就是64位的,没有就是32位的 这是64位的  # uname -a  Linux desktop 2.6.35-23-generic #37-Ubuntu ...

随机推荐

  1. 在Windows Azure上搭建SSTP VPN

    在国内,VPN是用来干嘛的大家都懂的.很久之前我尝试用Azure的Virtual Network搞VPN结果惨败了.最近微博上有基友写了篇文章亲测可行,原文在这里.可惜是英文的.所以我的这篇文章仅仅是 ...

  2. 你不知道的JavaScript-- 事件流与事件处理

    转载:http://blog.csdn.net/i10630226/article/details/48970971 1. 事件处理 1.1. 绑定事件方式 (1)行内绑定 语法: //最常用的使用方 ...

  3. python中关闭文件

    1.关闭文件,通过f.write把内容写入文件会覆盖之前文件中的内容

  4. hadoop学习笔记:hadoop文件系统浅析

    1.什么是分布式文件系统? 管理网络中跨多台计算机存储的文件系统称为分布式文件系统. 2.为什么需要分布式文件系统了? 原因很简单,当数据集的大小超过一台独立物理计算机的存储能力时候,就有必要对它进行 ...

  5. struts2视频学习笔记 22-23(基于XML配置方式实现对action的所有方法及部分方法进行校验)

    课时22 基于XML配置方式实现对action的所有方法进行校验   使用基于XML配置方式实现输入校验时,Action也需要继承ActionSupport,并且提供校验文件,校验文件和action类 ...

  6. fr

    8.3 credit sales(bad debt , ar) method1:direct write off method2:allowance method for bad debt allow ...

  7. Oracle11g中ORA-01790

    问题源于群里有人问如何让查询的结果值+1,方法其实很简单,直接在SQL语句中+1就可以,如果有空可以用NVL处理. 但是测试的时候我使用了UNION ALL(测试的字段是varchar2类型),结果报 ...

  8. jQuery.Autocomplete实现自动完成功能(详解)

    1.jquery.autocomplete参考地址 http://bassistance.de/jquery-plugins/jquery-plugin-autocomplete/ http://do ...

  9. 二分图 最大权匹配 km算法

    这个算法的本质还是不断的找增广路: KM算法的正确性基于以下定理:若由二分图中所有满足A[i]+B[j]=w[i,j]的边(i,j)构成的子图(称做相等子图)有完备匹配,那么这个完备匹配就是二分图的最 ...

  10. css默认样式

    html, address,blockquote,body, dd, div,dl, dt, fieldset, form,frame, frameset,h1, h2, h3, h4,h5, h6, ...