linux下逻辑地址转换成线性地址的流程
1. 使用段选择符中的偏移值 在GDT或LDT中定位相应的段描述符。
2. 利用段描述符检验段的方位权限和范围,以便确定该段是可访问的并且偏
移量位于段的段界限内。
3. 把段的偏移量加到段的基地址上最后形成一个线性地址。
1. 使用段选择符中的偏移值,在GDT或LDT中定位相应的段描述符。
段描述符表是段描述符的一个数组,每个段描述符占8个字节并且段描述符表是可变的。段描述符表可分为全
局描述符表(GDT)和局部描述符表(LDT)。
虚拟地址空间被分成大小相等的两部分,一部分是由GDT映射到线性地址空间,另一部分是由LDT映射到线性地址空间。当发生任务切换时,LDT会切换到新的任务的LDT,而GDT并不会改变。因此,GDT所映射的一般虚拟地址空间是系统所共有的。
GDT:Global descriptor table全局描述符表。所谓全局,是因为它里面有内核所有的段描述符信息。表中并没有直接存放相应的段,而是存放了指向相应段的段描述符。具体的,可以通过段寄存器中的段选择符,也就是索引,即可以在GDT中查找到相应的段描述符。
GDT本身不是段,它只是线性地址空间的一个数据结构。每个GDT的线性基址和它的大小限制都必须通过lgdt指令加载到GDTR寄存器中。
源码cpu_init中,对于GDT的初始化:
int ;
int ;
/*
* Initialize the per-CPU GDT with the
boot GDT,
* and set up the GDT descriptor:
*/
();
/
/*
* Current gdt points %fs at the
"master" per-cpu area: after this,
* it's on the real one.
*/
void (int
)这里的cpu变量指的是某个cpu如果系统是多处理器的话,这里会对每个cpu都有相应的gdt初始化。
{
struct
;
. = (long)();
. = - 1;
(&);
/* Reload the per-cpu base */
();
}
其中,描述GDT的线性基址和大小限制的数据结构即为desc_ptr
/
struct {
unsigned short ;
unsigned long ;
} (()) ;
详细查看load_gdt。也就是加载GDT的宏:
#define()()
static void (const
struct *)
{
asm volatile("lgdt %0"::"m" (*));
}
详细看下GDT_SIZE:
#define ( * 8)
/*
* The GDT has 32 entries
*/
#define 32
也就是GDT有32项。每项占8个字节。其中,包括18个段描述符和14个空的、未使用、保留的项。
详细的布局见图:
图中,可以看到段选择符这里就起到了索引的作用。通过它可以在GDT表中查找相应的段描述符的信息。
可以看到图中有LDT的描述项。LDT。也就是local descriptor table。GDT必须包含LDT的项。如果有多个LDT,那么GDT中必须有单独的多个来描述相应的LDT。
为什么会有局部描述符表?我的理解是,在纯粹的linux程序的环境下,是可以通过GDT统一管理,而不需要其他的支持就可以了。但是当需要提供对于某些其他非linux环境下的程序,他们在linux环境下运行,可能单单靠GDT是不够的。可能还需要专门的描述符表来针对这类程序提供支持。于是就有了局部描述符表。
图中,比较熟悉的还有内核数据段描述符、内核代码段描述符,用户数据段描述符、内核代码段描述符。以及TSS:任务状态段。2. 利用段描述符检验段的访位权限和范围,以便确定该段是可访问的并且偏移量位于段的段界限内。
每个段由三个部分组成:
(1)段基地址: 指定段在线性地址空间中的起始地址。
(2)段限长: 是虚拟地址空间中段内最大可用偏移位置。
(3)段属性:指定段的特性。例如该段是否可读可写等。
以此来判断段的访问权限和范围。
3.把段的偏移量加到段的基地址上最后形成一个线性地址。