linux0.11内核分析-setup

章节目录
上一节讲解了bootsect,由bootsect加载setup进入内存,最后jmpi 0,SETUPSEG跳转到setup程序处。

INITSEG  = 0x9000   ! we move boot here - out of the way
SYSSEG   = 0x1000   ! system loaded at 0x10000 (65536).
SETUPSEG = 0x9020   ! this is the current segment
    mov ax,#INITSEG ! this is done in bootsect already, but...
    mov ds,ax
    mov ah,#0x03    ! read cursor pos
    xor bh,bh
    int 0x10        ! save it in known place, con_init fetches
    mov [0],dx      ! it from 0x90000.

先把ds设置成 0x9000,这个段地址在加载bootsect时候讲过,就是bootsect从0x07c00地址复制到 0x90000地址,
bios 10号中断,功能ah=0x03表示读取光标,
输入参数是bh,
返回参数ch=扫描开始线,cl=扫描结束线
dh=行号,dl=列号
mov [0],dx如同mov ds:[0],dx,保存了行号和列号,也就是说0x90000处保存了行号和列号。

mov ah,#0x88
int 0x15
mov [2],ax

bios 15号中断,功能号ah=0x88表示获取扩展内存的大小(KB)
返回参数:ax=从0x100000(1MB)处开始的扩展内存大小(KB)
如果出错则cf=1,ax=出错码


    mov ah,#0x0f
    int 0x10
    mov [4],bx      ! bh = display page
    mov [6],ax      ! al = video mode, ah = window width

bios 10号中断,功能号ah=0x0f表示获取显卡显示模式
返回参数:ah=字符列数,al=显示模式,hb=当前显示页
0x9000:0x0004存放当前显示页
0x9000:0x0006存放显示模式
0x9000:0x0007存放字符列数

    mov ah,#0x12
    mov bl,#0x10
    int 0x10
    mov [8],ax
    mov [10],bx
    mov [12],cx

检查显示方式(EGA/VGA)并获取参数
bios 10号中断
ah=0x12 ,bl = 0x10

返回参数:
bh=显示状态, 0x00-彩色模式,I/O端口=0x3dX,0x01-单色模式,I/O端口=0x3bX
bl = 安装的显示内存,0x00-64K,0x01-128K,0x02-192K,0x03-256K
cx=显卡特性参数

程序执行后
0x9000:0x000A = 安装的显示内存
0x9000:0x000B = 显示状态
0x9000:0x000C = 显卡特性参数

! Get hd0 data

    mov ax,#0x0000
    mov ds,ax
    lds si,[4*0x41]
    mov ax,#INITSEG
    mov es,ax
    mov di,#0x0080
    mov cx,#0x10
    rep
    movsb

获取第一个硬盘的信息,这里用到41号中断。
bios中断向量表中的每个中断向量大小是4字节。这4字节描述了一个中断处理例程(程序)的段基址和段内偏移地址。因为中断向量表的长度为1024字节,故该表最多容纳256个中断向量处理程序。计算机启动之初,中断向量表中的中断例程是由BIOS建立的,它从物理内存地址0x0000处初始化并在中断向量表中添加各种处理例程。
[4*0x41]这个就表示41号中断的物理偏移地址,段地址就是ds。
lds si,[4*0x41] 取4个字节,前2个字节放在si寄存器中,后2个字节放在ds寄存器中。
rep movsb在前一节已经讲过类似的,这里不再详说。
movsb的功能是将ds:si指向的内存单元中的字节送入es:di中,注意这里是字节,这样就是把ds:si地址的数据复制到0x9000:0x0080地址,复制10个字节数据。

! Check that there IS a hd1 :-)

    mov ax,#0x01500
    mov dl,#0x81
    int 0x13
    jc  no_disk1
    cmp ah,#3
    je  is_disk1
no_disk1:
    mov ax,#INITSEG
    mov es,ax
    mov di,#0x0090
    mov cx,#0x10
    mov ax,#0x00
    rep
    stosb

检查系统是否存在第2个硬盘,如果不存在则第2个表清0。
利用13号中断
功能号:ah = 0x15
输入:dl = 驱动号(0x8X是硬盘:0x80指第1个硬盘,0x81第2个硬盘)
输出:ah=类型码,00-没有这个盘,CF置位;01-软驱,没有change-line支持,03-软驱(或者其他可移动设备),有change-line支持;03-硬盘。

is_disk1:

! now we want to move to protected mode ...

    cli         ! no interrupts allowed !

! first we move the system to it's rightful place

    mov ax,#0x0000
    cld         ! 'direction'=0, movs moves forward
do_move:
    mov es,ax       ! destination segment
    add ax,#0x1000
    cmp ax,#0x9000
    jz  end_move
    mov ds,ax       ! source segment
    sub di,di
    sub si,si
    mov     cx,#0x8000
    rep
    movsw
    jmp do_move

执行到标号is_disk1处,就要进入保护模式了。
cli关中断,此时不再响应中断信号,因为要把system复制到0x0000位置,会覆盖bios中断。
cld cld用来操作方向标志位DF(Direction Flag)。cld使DF 复位,即是让DF=0,std使DF置位,即DF=1.这两个指令用于串操作指令中。通过执行cld或std指令可以控制方向标志DF,决定内存地址是增大(DF=0,向高地址增加)还是减小(DF=1,向地地址减小)。

在实模式下,寻址一个内存地址主要是使用段和偏移值,段值被存放在段寄存器中,并且段的最大长度被固定为64KB,段内偏移地址存放在任意一个可用寻址的寄存器中,因此根据段寄存器和偏移寄存器中的值,就可以算出实际内存的地址。

实模式下寻址

在保护模式下,段寄存器中存放的不再是寻址段的基地址,而是一个一个索引,称为段选择符,由段选择符从全局描述符表或者局部描述符表中找到8个字节长的段描述符,从而确定关于这个段的全部描述信息。


保护模式下寻址
段选择符

RPL(RequestedPrivilege Level): 请求特权级,表示将要访问的特权级,取值范围0~3。
TI(TableIndicator): TI=0指示从全局描述符表GDT中读取描述符;TI=1指示从局部描述符表LDT中读取描述符。
index: 索引,指出要访问描述符在段描述符表中的顺序号,index占13位,因为顺序号的范围是0~8191,每个段描述符表中最多有8192个描述符。
有一个特殊的选择子称为空(Null)选择子,它的Index=0,TI=0,而RPL字段可以为任意值。空选择子有特定的用途,当用空选择子进行存储访问时会引起异常。空选择子是特别定义的,它不对应于全局描述符表GDT中的第0个描述符,因此处理器中的第0个描述符总不被处理器访问,一般把它置成全0。但当TI=1时,Index为0的选择子不是空选择子,它指定了当前任务局部描述符表LDT中的第0个描述符。

因此。在进入保护模式之前,必须首先设置好将要用到的段描述符,然后使用指令lgdt把描述符表的基地址告知CPU(gdt表的基地址存入gdtr寄存器),再将机器状态字的保护模式标志置位即可进入32位保护运行模式。

end_move:
    mov ax,#SETUPSEG    ! right, forgot this at first. didn't work :-)
    mov ds,ax
    !idt刚被加载,一条数据也没有
    lidt    idt_48      ! load idt with 0,0
    lgdt    gdt_48      ! load gdt with whatever appropriate

! that was painless, now we enable A20

    call    empty_8042
    mov al,#0xD1        ! command write
    out #0x64,al
    call    empty_8042
    mov al,#0xDF        ! A20 on
    out #0x60,al
    call    empty_8042

IDTR(中断描述符表寄存器)和IDT(中断描述符表)的关系

图片来源英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A 第196页
idt limit是描述符表的长度值(字节)。
idt base Address 是描述符表的32位线性基地址。
中断描述符表中的每一个表项(8字节)指出发生中断时需要调用的代码信息,与中断向量有些相似,但要包含更多的信息。

图片来源英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A 第103页

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

此时此刻内核尚未真正运行起来,还没有进程,所以现在创建的GDT表的第一项为空,第二项为内核代码段描述符,第三项为内核数据段描述符,其余项皆为空。
idt表虽然已经设置,实为一张空表,原因是目前已经关中断,无需调用中断服务程序。

打开A20,意味着CPU可以进行32位寻址,最大寻址空间为4GB。
CPU 在保护模式下,int 0x00~0x1F 被 Intel 保留作为内部(不可屏蔽)中断和异常中断。如果不对 8259A 进行重新编程,int 0x00~0x1F 将被覆盖。例如,IRQ0 (时钟中断)为 8 号(int 0x08)中断,但在保护模式下此中断号是 Intel 保留的“Double Fault”(双重故障)。因此,必须对 8259A 编程将原来的 IRQ0x00~IRQ0x0F 对应的中断号重新分布,即在保护模式下,IRQ0x00~IRQ0x0F 的中断号是 int 0x20~int 0x2F。

mov al,#0x11        ! initialization sequence
   out #0x20,al        ! send it to 8259A-1
   .word   0x00eb,0x00eb       ! jmp $+2, jmp $+2
   out #0xA0,al        ! and to 8259A-2
   .word   0x00eb,0x00eb
   mov al,#0x20        ! start of hardware int's (0x20)
   out #0x21,al
   .word   0x00eb,0x00eb
   mov al,#0x28        ! start of hardware int's 2 (0x28)
   out #0xA1,al
   .word   0x00eb,0x00eb
   mov al,#0x04        ! 8259-1 is master
   out #0x21,al
   .word   0x00eb,0x00eb
   mov al,#0x02        ! 8259-2 is slave
   out #0xA1,al
   .word   0x00eb,0x00eb
   mov al,#0x01        ! 8086 mode for both
   out #0x21,al
   .word   0x00eb,0x00eb
   out #0xA1,al
   .word   0x00eb,0x00eb
   mov al,#0xFF        ! mask off all interrupts for now
   out #0x21,al
   .word   0x00eb,0x00eb
   out #0xA1,al
! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! 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 ax,#0x0001  ! protected mode (PE) bit
    lmsw    ax      ! This is it!
    jmpi    0,8     ! jmp offset 0 of segment 8 (cs)

这个时候,正式进入保护模式,CR0控制寄存器的PE置位。
段寄存器cs的值是8,不过此时不再是基地址,而是保护模式下的段选择符,段选择符是16位的数据,8用二进制表示就是0b0000 0000 0000 1000,第0到1位表示特权级别,第2位的0表示使用全局描述符表,第3位的1表示索引,所以jmpi 0,8就是请求特权级0,使用全局描述符表中的第一项,由于上面我们设置过gdt,这个基地址就是0,也就是跳转去执行system中的代码,system的头部就是head,下一节继续讲解head.s


图片来源英特尔® 64 位和 IA-32 架构开发人员手册:卷 3A 第76页

你可能感兴趣的:(linux0.11内核分析-setup)