linux 0.11 内核学习 -- setup.s

参考资料 Linux内核完全注释.pdf

            网上相关资料

! setup程序的主要作用是利用rom bios的中断来读取机器系统参数,并将这些数据保存在0x90000
! 开始的位置(覆盖掉了bootsect程序所在的位置),所取得的参数被内核的相关程序使用。注意在bootsect
! 中已经将该模块和system、模块加载到内存中。
! 然后setup程序将system模块从地址0x10000-0x8fff(当时认为内核的最大值)整块移动到内存的绝对地址
! 0x00000处。接下来加载中断描述符表寄存器idtr和全局描述符表gdtr,开启a20地址线,重新设置两个
! 中断控制芯片,将硬件终端号重新设置,最后设置cpu的控制寄存器cr0,从而进入32位的保护模式,并
! 且跳转到system模块最前面的head.s程序处开始继续运行。
!
! 为了能让head.s在32位的保护模式下运行,本程序设置了中断描述符表idt和全局描述符表gdt,并在gdt
! 中设置了当前内核代码段的描述符和数据段的描述符。在下面的head.s程序中会根据内核的需要重新设置
! 这些描述符表。
!
!
! setup.s  (C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!

! NOTE! These had better be the same as in bootsect.s!

INITSEG  = 0x9000 ! we move boot here - out of the way,原来的bootsect段
SYSSEG   = 0x1000 ! system loaded at 0x10000 (65536).system所在段
SETUPSEG = 0x9020 ! this is the current segment,本程序所在段

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

entry start
start:

! ok, the read went well so we get current cursor position and save it for
! posterity.
! 整个读磁盘的过程都很顺利,现在将光标位置保存以备后用。

 ! 设置ds = 0x9000
 mov ax,#INITSEG ! this is done in bootsect already, but...
 mov ds,ax

 !//////////////////////////////////////////////////////
 ! 调用系统中断0x10读取光标位置。下面是中断前的准备和调用中断。
 mov ah,#0x03 ! read cursor pos ah = 0x03
 xor bh,bh  ! bh = 页号
 int 0x10  ! save it in known place, con_init fetches
 !///////////////////////////////////////////////////////
 ! 将信息保存在0x90000处,控制台初始化时来读取。
 mov [0],dx  ! it from 0x90000.

! Get memory size (extended mem, kB)
! 得到拓展内存的大小,调用中断0x15,同时将得到信息保存到0x90002处。
 mov ah,#0x88
 int 0x15
 mov [2],ax

! Get video-card data:
! 下面的代码用于取得的是当前的显卡的显示模式
! 调用中断0x10,同时将信息存储。

 !//////////////////////////////////////////////////////
 mov ah,#0x0f
 int 0x10
 !//////////////////////////////////////////////////////
 ! 0x90004中存放的是当前页
 ! 0x90006 显示模式
 ! 0x90007 字符列数
 mov [4],bx  ! bh = display page
 mov [6],ax  ! al = video mode, ah = window width
 !/////////////////////////////////////////////////////

! check for EGA/VGA and some config parameters
! 检查显示模式。并取得参数。其中ega和vga是显示器的两种模式。
! 利用中断0x10来实现读取信息,并将相关信息保存。
 !/////////////////////////////////////////////////
 mov ah,#0x12
 mov bl,#0x10
 int 0x10
 !////////////////////////////////////////////////
 mov [8],ax  ! ??
 mov [10],bx  ! 0x9000A -- 显存大小
    ! 0x9000B -- 显示状态,彩色还是单色
 mov [12],cx  ! 0x9000C -- 显卡的特征参数
 !///////////////////////////////////////////////
 
! Get hd0 data
! 获取第一个硬盘信息,赋值硬盘参数列表
 mov ax,#0x0000
 mov ds,ax
 !///////////////////////////////////////////////
 ! 利用中断向量0x41的值,也即是hd0参数列表的地址。
 !
 ! pc机上的中断向量表 : pc机bios在初始化时会在物理内存
 ! 开始的一夜内存中存放中断向量表,每个中断向量表对应的
 ! 中断服务处理程序isr的地址使用4个字节来表示。但是某些
 ! 的中断向量却使用其他的值,这包括中断向量0x41和ox46,
 ! 这两个中断向量的处理程序地址实际上就是硬盘参数表的位置。
 !
 ! 在CPU被加电的时候,最初的1M的内存,是由BIOS为我们安排
 ! 好了的,每一字节都有特殊的用处。

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

! Get hd1 data
! 取得hd1的参数列表,方法同上。
 mov ax,#0x0000
 mov ds,ax
 lds si,[4*0x46]
 mov ax,#INITSEG
 mov es,ax
 mov di,#0x0090
 mov cx,#0x10
 rep
 movsb

! Check that there IS a hd1 :-)
! 检查是否存在第二个硬盘,如果不存在,第二个硬盘表清0.利用bios的int 0x13来实现。

 mov ax,#0x01500
 mov dl,#0x81 ! 0x81指的是第2个硬盘
 int 0x13

 jc no_disk1
 cmp ah,#3  ! 是硬盘吗 ? 类型 = 3
 je is_disk1
no_disk1:
! 第二个硬盘不存在,则对第二个硬盘表清0
 mov ax,#INITSEG
 mov es,ax
 mov di,#0x0090 ! 0x90090 ---+--- 0x10
 mov cx,#0x10
 mov ax,#0x00
 rep
 stosb
is_disk1:

! now we want to move to protected mode ...
 cli   ! no interrupts allowed !禁止中断

! first we move the system to it's rightful place
! 首先我们将system模块移动到正确的位置。下面程序代码是将system模块移动到0x0000
! 位置,即把从0x10000-0x8ffff的内存数据512k,整体向内存低端移动了0x10000 - 64k

 mov ax,#0x0000
 cld   ! 'direction'=0, movs moves forward
do_move:
 mov es,ax  ! destination segment
    ! 目的地址初始为0x000 : 0x0
 add ax,#0x1000
 cmp ax,#0x9000 ! 移动完毕
 jz end_move
 mov ds,ax  ! source segment
    ! 源地址 0x1000 : 0x0
 sub di,di
 sub si,si
 mov  cx,#0x8000 ! 移动64k
 rep
 movsw
 jmp do_move
! 现在已经将system模块加载到内存的0地址。

! then we load the segment descriptors
! lidt指令用于加载中断描述符表idt寄存器。其中加载时只是加载的描述符表的线性基地址。
! 中断描述符表中的每一个表项指出发生中断时需要调用的代码信息。
!
! lgdt指令用于加载中断描述符表idt。
! ldgt指令用于加载全局描述符表gdt寄存器。
!
! 8086处理器的保护模式和实时模式,采用实
! 时模式的寻址,没有虚拟内存空间,首先物理
! 内存的每个位置都是使用20位的地址来标识的。
! 寻址是通过使用cs,ds,ss,es加上段的偏移量。
!
! 在保护模式下,cpu通过选择子找到段描述符,来
! 寻址。包括全局段表,局部段表,中断表。
! 段选择子通过ldgr寄存器来找到全局段表,通过idtr
! 找到中断表。
!
end_move:   
! 加载中断描述符idt
 mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)
 mov ds,ax
 lidt idt_48  ! load idt with 0,0,idt_48在下面定义。
 lgdt gdt_48  ! load gdt with whatever appropriate,
    ! gdt_48在下面定义。

! that was painless, now we enable A20
! 以上的操作很简单,现在我们开启a20地址线。
!
 ! 为了兼容使用开启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
 !///////////////////////////////////////////////

! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.
!
! 下面的代码是给中断编程,我们将他放在处于intel保留的硬件中断后面,在
! int 0x20 -- 0x2f,在哪里它们不会引起中断。
!
! 下面是8259芯片的简介 : 8259芯片是一种可编程控制芯片。每片可以管理8
! 个中断源。通过多片的级联方式,能构成最多管理64个中断向量的系统。在
! pc/at系列的兼容机中,使用了两个8259a芯片,共可管理15级中断向量。主
! 8259a芯片的端口基址是0x20,从芯片是0xa0.
!
 !/////////////////////////////////////////////////////////
 ! 0x11表示初始化命令开始,是icw1命令字,表示边沿触发,多片
 ! 8259级联,最后要发送icw4命令字。8259a的编程就是根据应用
 ! 程序需要将初始化字icw1 -- icw4和操作命令字ocw1 -- ocw3
 ! 分别写入初始化命令寄存器组和操作命令寄存器组。
 !
 mov al,#0x11  ! initialization sequence
 out #0x20,al  ! send it to 8259A-1
 !/////////////////////////////////////////////////////////
 !/////////////////////////////////////////////////////////
 ! 使用如下的.word 0x00eb,0x00eb来祈祷延迟的作用。下面是相
 ! 关解释 : 0xeb是直接跳转指令操作码,带一个字节的相对地址
 ! 偏移量。0x00eb表示跳转值是0的一条指令,因此还是直接执行
 ! 下一条指令。在as86中没有表示的助记符,所以linus直接使用了
 ! 机器码来表示这种指令。
 .word 0x00eb,0x00eb  ! jmp $+2, jmp $+2
 !/////////////////////////////////////////////////////////

 !//////////////////////////////////////////////////////////
 ! 进行8259a芯片编程。一次发送icw2,3,4
 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.

 !////////////////////////////////////////////////
 ! 加载cr0
 mov ax,#0x0001 ! protected mode (PE) bit
 lmsw ax  ! This is it!
 ! cpu处于保护模式。
 !////////////////////////////////////////////////
 
 jmpi 0,8  ! jmp offset 0 of segment 8 (cs)
    ! 跳转到cs段8,偏移量0
 ! 我们已经将system模块移动到0x00000处,所以这里的偏移地址
 ! 是0.这里的段值的8已经是保护模式下的段选择符了,用于选择
 ! 描述符表和描述符表项以及所要求的特权级。
 ! 段选择符长度为16位;0-1位表示请求的特权级;Linus只
 ! 只使用了2级:0级系统级和3级用户级。第2位用于选择是全局描
 ! 述符表还是局部描述符表。3-15位是描述表项的索引,指出是第
 ! 几项描述符。
 ! 8 -- 0000,0000,0000,1000,表示请求的特权级是0--系统级,
 ! 使用全局描述符表第1项,该代码指出代码的基地址是0,因此这
 ! 里的跳转指令就回去执行system中的代码。
 !

! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.
!
! 下面的代码检查键盘命令队列是否为空。
! 只有当输入缓冲区为空时才可以对其进行写的操作。
!
empty_8042:
 .word 0x00eb,0x00eb ! 延迟
 in al,#0x64 ! 8042 status port
 test al,#2  ! is input buffer full?
    ! 输入缓冲区满 ?
 jnz empty_8042 ! yes - loop
 ret

!////////////////////////////////////////////////////////////////
! 数据描述
!
! (1)Linux的任务:
! ---定义GDT表
! ---定义LDT表
! ---初始化的时候执行LGDT指令,将GDT表的基地址装入到GDTR中
! ---进程初始化的时候执行LLDT指令,将LLDT表的基地址装入到LDTR中
!
! (2)CPU的任务
! ---用GDTR寄存器保存GDT表的基地址
! ---用LDTR寄存器保存当前进程的LDT表的基地址
! ---需要访问内存的时候,利用LDTR(或者GDTR,多数情况下是前者)找到相应的表,再根据提供的内存地址的某些部分找到相应的表项,然后再对表项的内容继续操作,得到最终的物理地址。所有这些操作都是在一条指令的指令周期里面完成的。

! gdt -- 描述符表的主要作用是将应用程序的逻辑地址转换为线性地址。
gdt:
 !/////////////////////////////////////////////////////
 .word 0,0,0,0  ! dummy,第一个描述符,不可用,
    ! 主要适用于保护。
 !/////////////////////////////////////////////////////

 !///////////////////////////////////////////////////////
 ! 系统代码段描述符。加载代码段时,使用这个偏移量。段描述符
 ! 一共64位。描述如下 :
 ! 0 -- 15 limit字段决定段的长度
 ! 16 -- 39 56 -- 63段的首字节的线性地址
 ! 40 -- 43 描述段的类型和存取权限
 ! 44 系统标志;如果被清0,则是系统端。
 ! 45 -- 46 dpl 描述符的特权级;用于限制这个段的存取。它表示
 !         为访问这个段而要求的cpu的最小优先级。
 ! 47 -- 1
 ! 48 -- 51
 ! 52 被linux忽略。
 ! 53 -- 0
 ! 54 -- d/s
 ! 55 -- g 力度标志
 !
 .word 0x07FF  ! 8Mb - limit=2047 (2048*4096=8Mb)
 .word 0x0000  ! base address=0
 .word 0x9A00  ! code read/exec
 .word 0x00C0  ! granularity=4096, 386
 !
 ! 从高地址到低地址为 00c0 9a00 0000 07ff
 ! 那么断脊地址就是00 00 0000
 ! 偏移量是 07ff
 ! 描述符为 07ff
 !
 !///////////////////////////////////////////////////////

 !///////////////////////////////////////////////////////
 ! 系统数据段描述符。当加载数据段寄存器时使用的是这个偏移量。
 .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

!/////////////////////////////////////////////////////////////////

.text
endtext:
.data
enddata:
.bss
endbss:

posted @ 2010-01-19 08:36  qiang.xu  阅读(2102)  评论(0编辑  收藏  举报