linux0.11 80386段

最近在看linux0.11源码,对其中的进程调度查了一些资料,《80386汇编语言精要》这本书写的非常好,对理解帮助很大,建议大家看一下,主要是两方面的知识:

  1. GDT\IDT与运行有什么关系?
  2. 80386有三种运行模式:实模式,保护模式,兼容8086模式
  3. linux0.11中具体是如何实现的

GDT\IDT与运行有什么关系?

先弄清楚以下这几个寄存器的作用(作用就是为了寻址)。

  • GDT:该表地址存在GDTR寄存器里

全局描述符表 GDT ( Global Descriptor Table) , 除了任务门、中断门和陷井门描述符外 , 包含着系统中所有任务都可用的那些描述符。它的第一个 8 字节位置 没有使用。其中可以存放如下几种不同的描述符:

  • 门:为什么task切换要使用软中断

门是用来控制访问在目标码段的入口点。有调用门、任务门、中断门和陷井门四种。所谓中断就是用于处理外部发生的突发事件, 陷井是一种特殊的中断, 用户定的软件中断其实就是陷井。调用门主要用于将程序控制转换到一个更高的特权级(数字低) , 任务门用于切换任务, 它只能涉及任务状态段。中断门和陷井门用于中断处理, 其中的地址是指向中断或陷井处理子程序起点的指针, 中断门禁止中断( IF = 0) , 而陷井门并不禁止。

  • IDT:该表地址存在IDTR寄存器里

中断描述符表 IDT ( Interrupt Descriptor Table) , 可以包含 256 个描述符 , 每个描述符为 8 个字节。IDT 中只能包含任务门、中断门和陷井门描述符 , 虽然它最长也可以为 64K 字节 , 但只能存取 2K 字节以内的描述符 , 即 256 个。它最少为 256 个字节 ,因为 Intel 公司保留了 32 个中断描述符供自己使用。规定这些数字都是为了和早期的机器兼容。

  • LDT:

局部描述符表 LDT ( Local Descriptor Table) , 包含了与一个给定任务有关的描述符 , 每一个任务都有一个各自的 LDT。有了 LDT, 就可以使给定任务的码、数据与别的任务相隔离。 LDT 中的描述符都在 GDT 中 , 所以 , 不同的任务可以有相同的描述符 , 这样就可以共享全局数据和代码。段描述符:就是存在GDT中的项

  • TSS:在 GDT

所谓任务状态段 TSS ( Task State Segment) , 就是一个特殊的固定格式的段 , 它包含了一个任务和与之相链接的允许嵌套的任务的所有状态信息!这个段在task切换中非常重要,我后面再分析。当任务状态段描述符所描述的任务正在执行时, 它就是类型B, 否则就置成类型9TSS 就由任务状态寄存器TR 来标识,TR有16位可见+64位不可见,16位可见就是段选择子,相当于GDT中的索引,TR通过可见的16位找到TSS。执行IRET 时, 控制返回到原来的任务, 当前的任务状态则被保存到TSS 中, 并从原有任务的TSS 中恢复原有任务的状态。

  • 段选择子:(放在段寄存器里,80386中有6个(即CS,SS,DS,ES,FS,GS)16位的段寄存器)

在实模式下 , 段寄存器存储的是真实的段地址 , 在保护模式下 , 16 位的段寄存器无法放下 32 位段地址 , 因此 , 它们被称为选择器 , 即起选择描符述表中的描述符的作用。这样 , 就把描述符中的 32 位段地址做为实际的段地址。

80386有三种运行模式:实模式,保护模式,兼容8086模式。

兼容8086模式很好理解!每种模式下寄存器的内容均有不同的意义,实模式与保护模式主要为了通过硬件的设计添加错误检查。你可以设计的程序只运行在实模式(实模式是16位,保护模式是32位)所有的地址都是硬件地址,随便一个指针错误都会造成死机,当然结果就是系统很容易崩溃!linux中setup.s最后几句就是进入保护模式。

# Well, now's the time to actually move into protected mode. To make
# things as simple as possible, we do no register set-up or anything,
# we let the gnu-compiled 32-bit programs do that. We just jump to
# absolute address 0x00000, in 32-bit protected mode.
    #mov    $0x0001, %ax    # protected mode (PE) bit
    #lmsw    %ax        # This is it!
    mov    %cr0, %eax    # get machine status(cr0|MSW)    
    bts    $0, %eax    # turn on the PE-bit 
    mov    %eax, %cr0    # protection enabled

进入保护模式后,软件使用逻辑地址访问时,硬件可以通过段描述符检查是否溢出。

例如:若给定一个逻辑地址是 a:b ,根据逻辑地址的a(段选择符)的T1位确定是选择GDT还是LDT。

  1. a若是T1位选择GDT,根据GDTR找到GDT的基址,根据a的 3~15位确定它的段描述符X在GDT中的位置(GDTR即基址+a的3-15bit即相对位置):确定段描述符X,再根据段描述符提取出其中包含的段基址信息,段基址+b(段内偏移),最终确定线性地址。

  2. a若是T1位选择LDT,根据GDTR找到GDT的基址,根据LDTR的高13位确定它的LDTX描述符在GDT中的位置(GDTR基址+LDTR13bit即相对位置):确定LDTX描述符。LDTX描述符可以确定LDT的基址(LDTX描述符确定LDT表在内存中的起始位置),再根据段选择符a确定的相对位置,可以确定LDT中的私有段描述符Y。接下来同上面的:再根据段描述符提取出其中包含的段基址信息,段基址+b(段内偏移),最终确定线性地址。

linux0.11中具体是如何实现的?

在setup.s中有加载idt与gdt,指令如下:

    lidt    idt_48        # load idt with 0,0
    lgdt    gdt_48        # load gdt with whatever appropriate

lidt与lgdt操作数是48位,6 字节操作数装入全局描述符表寄存器IDTR与GDTR。

setup.s代码中的定义如下:需要注意的是此时系统还处于实模式16位操作。

gdt:
    .word    0,0,0,0        # dummy

    .word    0x07FF        # 8Mb - limit=2047 (2048*4096=8Mb)
    .word    0x0000        # base address=0
    .word    0x9A00        # code read/exec
    .word    0x00C0        # granularity=4096, 386

    .word    0x07FF        # 8Mb - limit=2047 (2048*4096=8Mb)
    .word    0x0000        # base address=0
    .word    0x9200        # data read/write
    .word    0x00C0        # granularity=4096, 386

idt_48:
    .word    0            # idt limit=0
    .word    0,0            # idt base=0L

gdt_48:
    .word    0x800            # gdt limit=2048, 256 GDT entries
    .word   512+gdt, 0x9        # gdt base = 0X9xxxx, 

然后通过以下指令进入保护模式并跳转。跳转指令是ljmp。

    #mov    $0x0001, %ax    # protected mode (PE) bit
    #lmsw    %ax        # This is it!
    mov    %cr0, %eax    # get machine status(cr0|MSW)    
    bts    $0, %eax    # turn on the PE-bit 
    mov    %eax, %cr0    # protection enabled
                
                # segment-descriptor        (INDEX:TI:RPL)
    .equ    sel_cs0, 0x0008 # select for code segment 0 (  001:0 :00) 
    ljmp    $sel_cs0, $0    # jmp offset 0 of code segment 0 in gdt

进入保护模式之后第一件事是什么,当然是设置堆栈。这个时候再回头看head.s的开头就更清楚了,之前的boot分析其实还不是很懂这里为什么要lss两次。如果第一次lss不设置,那么这个堆栈寄存器esp根本就没有初始化。

startup_32:
    movl $0x10,%eax
    mov %ax,%ds
    mov %ax,%es
    mov %ax,%fs
    mov %ax,%gs
    lss stack_start,%esp
    call setup_idt
    call setup_gdt
    movl $0x10,%eax        # reload all the segment registers
    mov %ax,%ds        # after changing gdt. CS was already
    mov %ax,%es        # reloaded in 'setup_gdt'
    mov %ax,%fs
    mov %ax,%gs
    lss stack_start,%esp
    xorl %eax,%eax
1:    incl %eax        # check that A20 really IS enabled
    movl %eax,0x000000    # loop forever if it isn't
    cmpl %eax,0x100000
    je 1b

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值