• setup.s


    1 ! 
    2 ! setup.s (C) 1991 Linus Torvalds 
    3 ! 
    4 ! setup.s is responsible for getting the system data from the BIOS, 
    5 ! and putting them into the appropriate places in system memory. 
    6 ! both setup.s and system has been loaded by the bootblock. 
    7 ! 
    8 ! This code asks the bios for memory/disk/other parameters, and 
    9 ! puts them in a "safe" place: 0x90000-0x901FF, ie where the 
    10 ! boot-block used to be. It is then up to the protected mode 
    11 ! system to read them from there before the area is overwritte n 
    for buffer-blocks. 

    ! setup.s 负责从 BIOS 中获取系统数据,并将这些数据放到系统内存的适当地方。 
    ! 此时 setup.s 和 system 已经由 bootsect 引导块加载到内存中。 

    ! 这段代码询问 bios 有关内存/磁盘/其它参数, 并将这些参数放到一个 
    ! “安全的”地方: 0x90000-0x901FF,也即原来 bootsect 代码块曾经在 
    ! 的地方,然后在被缓冲块覆盖掉之前由保护模式的 system 读取。 
    13 ! 

    15 ! NOTE! These had better be the same as in bootsect.s! 
    ! 以下这些参数最好和 bootsect.s 中的相同! 
    16 
    17 INITSEG = 0x9000 ! we move boot here - out of the way ! 原来 bootsect 所处的段。

    18 SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). ! system 在 0x10000(64k)处。 
    19 SETUPSEG = 0x9020 ! this is the current segment ! 本程序所在的段地址。 
    20 

    21 .globl begtext, begdata, begbss, endtext, enddata, endbss 
    22 .text 
    23 begtext: 
    24 .data 
    25 begdata: 
    26 .bss 
    27 begbss: 
    28 .text 
    29 
    30 entry start 
    31 start: 
    32 
    33 ! ok, the read went well so we get current cursor position and save it for 

    34 ! posterity. 
    ! ok,整个读磁盘过程都正常, 现在将光标位置保存以备今后使用。 
    35 
    36 mov ax,#INITSEG

    ! 将 ds 置成#INITSEG(0x9000)。这已经在 bootsect 程序中 
    ! 设置过,但是现在是 setup 程序,Linus 觉得需要再重新 
    ! 设置一下。 

    37 mov ds,ax !重新设置ds寄存器的值,此时,在段内使用偏移量寻址的话实际地址为0x90000+偏移量

    ! BIOS 中断 0x10 的读光标功能号 ah = 0x03 
    ! 输入:bh = 页号 
    ! 返回:ch = 扫描开始线,cl = 扫描结束线, 
    ! dh = 行号(0x00 是顶端),dl = 列号(0x00 是左边)。
    38 mov ah,#0x03 ! read cursor pos  
    39 xor bh,bh 
    40 int 0x10 ! save it in known place, con_ init fetches 

    41 mov [0],dx ! it from 0x90000. !这里的【0】是相对位置寻址,初始地址已经设置为0x90000
    ! 上两句是说将光标位置信息存放在 0x90000 处,控制台初始化时会来取。

    43 ! Get memory size (extended mem, kB) ! 下面 3 句取扩展内存的大小值(KB)。 
    ! 是调用中断 0x15,功能号 ah = 0x88 
    ! 返回:ax = 从 0x100000(1M)处开始的扩展内存大小(KB)。 
    ! 若出错则 CF 置位,ax = 出错码。 
    44 
    45 mov ah,#0x88 
    46 int 0x15 
    47 mov [2],ax ! 将扩展内存数值存在 0x90002 处(1 个字)。 

    48 
    49 ! Get video-card data: ! 下面这段用于取显示卡当前显示模式。 
    ! 调用 BIOS 中断 0x10,功能号 ah = 0x0f 
    ! 返回:ah = 字符列数,al = 显示模式,bh = 当前显示页。 
    ! 0x90004(1 字)存放当前页,0x90006 显示模式,0x90007 字符列数。 

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

    55 
    56 ! check for EGA/VGA and some config parameters ! 检查显示方式(EGA/VGA)并取参数。 
    ! 调用 BIOS 中断 0x10,附加功能选择 -取方式信息 
    ! 功能号:ah = 0x12,bl = 0x10 
    ! 返回:bh = 显示状态 
    ! (0x00 - 彩色模式,I/O 端口=0x3dX) 
    ! (0x01 - 单色模式,I/O 端口=0x3bX) 
    ! bl = 安装的显示内存 
    ! (0x00 - 64k, 0x01 - 128k, 0x02 - 192k, 0x03 = 256k) 
    ! cx = 显示卡特性参数(参见程序后的说明)。 
    57 

    58 mov ah,#0x12 
    59 mov bl,#0x10 
    60 int 0x10 
    61 mov [8],ax ! 0x90008 = ?? 
    62 mov [10],bx ! 0x9000A = 安装的显示内存, 0x9000B = 显示状态(彩色/单色) 
    63 mov [12],cx ! 0x9000C = 显示卡特性参数。 

    64 
    65 ! Get hd0 data ! 取第一个硬盘的信息(复制硬盘参数表) 。 
    ! 第 1 个硬盘参数表的首地址竟然是中断向量 0x41 的向量值! 而第 2 个硬盘 
    ! 参数表紧接第 1 个表的后面,中断向量 0x46 的向量值也指向这第 2 个硬盘 
    ! 的参数表首址。表的长度是 16 个字节(0x10)。 
    ! 下面两段程序分别复制 BIOS 有关两个硬盘的参数表,0x90080 处存放第 1 个 
    ! 硬盘的表,0x90090 处存放第 2 个硬盘的表。 
    66 
    67 mov ax,#0x0000 
    68 mov ds,ax

    !这里下面出现的4*0x41其实只是因为第一个硬盘信息存放的地点的指针存放位置与0x41中断向量的向量值相同,都是0x104,直接写

    !【0x104】即可
    69 lds si,[4*0x41] ! 取中断向量 0x41 的值,也即 hd0 参数表的地址
    ds:si 
    70 mov ax,#INITSEG 

    71 mov es,ax 
    72 mov di,#0x0080 ! 传输的目的地址: 0x9000:0x0080 
    Πes:di 

    73 mov cx,#0x10 ! 共传输 0x10 字节。 
    74 rep 
    75 movsb 
    76

    77 ! Get hd1 data 
    78 
    79 mov ax,#0x0000 
    80 mov ds,ax 
    81 lds si,[4*0x46] ! 取中断向量 0x46 的值,也即 hd1 参数表的地址
    ds:si 
    82 mov ax,#INITSEG 

    83 mov es,ax 
    84 mov di,#0x0090 ! 传输的目的地址: 0x9000:0x0090 
    Πes:di 

    85 mov cx,#0x10 
    86 rep 
    87 movsb 
    88 
    89 ! Check that there IS a hd1 :-) ! 检查系统是否存在第 2 个硬盘, 如果不存在则第 2 个表清零。 
    ! 利用 BIOS 中断调用 0x13 的取盘类型功能。 
    ! 功能号 ah = 0x15; 
    ! 输入:dl = 驱动器号(0x8X 是硬盘:0x80 指第 1 个硬盘,0x81 第 2 个硬盘) 
    ! 输出:ah = 类型码;00 --没有这个盘,CF 置位; 01 --是软驱,没有 change-line 支持; 
    ! 02 --是软驱(或其它可移动设备),有 change-line 支持; 03 --是硬盘。 
    90 
    91 mov ax,#0x01500 
    92 mov dl,#0x81 
    93 int 0x13 
    94 jc no_disk1 
    95 cmp ah,#3 ! 是硬盘吗?(类型 = 3 ?)。 

    96 je is_disk1 
    97 no_disk1: 
    98 mov ax,#INITSEG ! 第 2 个硬盘不存在,则对第 2 个硬盘表清零。 
    99 mov es,ax 
    100
     mov di,#0x0090 
    101
     mov cx,#0x10 
    102
     mov ax,#0x00 
    103
     rep 
    104
     stosb 
    105
     is_disk1: 
    106
     
    107
     ! now we want to move to protected mode ... ! 现在我们要进入保护模式中了... 
    108
     
    109
     cli ! no interrupts allowed ! ! 此 时不允许中断。 
    110
     
    111
     ! first we move the system to it's rightful place 
    ! 首先我们将 system 模块移到正确的位置。 
    ! bootsect 引导程序是将 system 模块读入到从 0x10000(64k)开始的位置。 由于当时假设 
    ! system 模块最大长度不会超过 0x80000(512k),也即其末端不会超过内存地址 0x90000, 
    ! 所以 bootsect 会将自己移动到 0x90000 开始的地方,并把 setup 加载到它的后面。 
    ! 下面这段程序的用途是再把整个 system 模块移动到 0x00000 位置,即把从 0x10000 到 0x8ffff 
    ! 的内存数据块(512k),整块地向内存低端移动了 0x10000(64k)的位置。 

    112
     
    113
     mov ax,#0x0000 
    114
     cld ! 清零进位标志DF 
    115
     do_move: 
    116
     mov es,ax ! destination segment ! es:di
     Î目的地址(初始为 0x0000:0x0) 
    117
     add ax,#0x1000 
    118
     cmp ax,#0x9000 ! 已经把从 0x8000 段开始的 64k 代码移动完?

    119 jz end_move 
    120
     mov ds,ax ! source segment ! ds:si
     Î源地址(初始为 0x1000:0x0) 
    121
     sub di,di 
    122
     sub si,si 
    123
     mov cx,#0x8000 ! 移动 0x8000 字(64k 字节)。 
    124
     rep 
    125
     movsw 
    126
     jmp do_move 
    127
     
    128
     ! then we load the segment descriptors 
    ! 此后,我们加载段描述符。 
    ! 从这里开始会遇到 32 位保护模式的操作,因此需要 Intel 32 位保护模式编程方面的知识了, 
    ! 有关这方面的信息请查阅列表后的简单介绍或附录中的详细说明。这里仅作概要说明。 
    ! 在进入保护模式中运行之前, 我们需要首先设置好需要使用的段描述符表。 这里需要设置全局 
    ! 描述符表和中断描述符表。 

    ! lidt 指令用于加载中断描述符表(idt)寄存器,它的操作数是 6 个字节,0-1 字节是描述符表的 
    ! 长度值(字节);2-5 字节是描述符表的 32 位线性基地址(首地址), 其形式参见下面 
    ! 219-220 行和 223-224 行的说明。 中断描述符表中的每一个表项(8 字节) 指出发生中断时 
    ! 需要调用的代码的信息,与中断向量有些相似, 但要包含更多的信息。 

    ! lgdt 指令用于加载全局描述符表(gdt)寄存器,其操作数格式与 lidt 指令的相同。 全局描述符 
    ! 表中的每个描述符项(8 字节)描述了保护模式下数据和代码段(块)的信息。其中包括段的 
    ! 最大长度限制(16 位)、段的线性基址(32 位)、 段的特权级、段是否在内存、读写许可以及 
    ! 其它一些保护模式运行的标志。参见后面 205-216 行。 

    129
     
    130
     end_move: 
    131
     mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-) 
    132
     mov ds,ax ! ds 指向本程序(setup)段。 
    133
     lidt idt_48 ! load idt with 0,0 
    ! 加载中断描述符表(idt)寄存器,idt_48 是 6 字节操作数的位置 
    ! (见 218 行)。 前 2 字节表示 idt 表的限长,后 4 字节表示 idt 表 
    ! 所处的基地址。 

    134
     lgdt gdt_48 ! load gdt with whatever appro priate 
    ! 加载全局描述符表(gdt)寄存器,gdt_48 是 6 字节操作数的位置 
    ! (见 222 行)。 

    135
     
    136
     ! that was painless, now we enable A20 
    ! 以上的操作很简单, 现在我们开启 A20 地址线。 参见程序列表后有关 A20 信号线的说明。 
    ! 关于所涉及到的一些端口和命令,可参考 kernel/chr_drv/keyboard.S 程序后对键盘接口的说明。 

    137
     
    138
     call empty_8042 ! 等待输入缓冲器空。 
    ! 只有当输入缓冲器为空时才可以对其进行写命令。 

    139
     mov al,#0xD1 ! command write ! 0xD1 命令码-表示要写数据到 
    140
     out #0x64,al ! 8042 的 P2 端口。P2 端口的位 1 用于 A20 线的选通。 
    ! 数据要写到 0x60 口。 

    141
     call empty_8042 ! 等待输入缓冲器空,看命令是否被接受。 
    142
     mov al,#0xDF ! A20 on ! 选通 A20 地址线的参数。 
    143
     out #0x60,al 
    144
     call empty_8042 ! 输入缓冲器为空, 则表示 A20 线已经选通。 
    145
     
    146
     ! well, that went ok, I hope. Now we have to reprogram the int errupts :-(

    147 ! we put them right after the intel-reserved hardware interrup ts, at 
    148
     ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really 
    149
     ! messed this up with the original PC, and they haven't been a ble to 
    150
     ! rectify it afterwards. Thus the bios puts interrupts at 0x08 -0x0f, 
    151
     ! which is used for the internal hardware interrupts as well. We just 
    152
     ! have to reprogram the 8259's, and it isn't fun. 
    !! 希望以上一切正常。现在我们必须重新对中断进行编程
    / 
    !! 我们将它们放在正好处于 intel 保留的硬件中断后面, 在 int 0x20-0x2F。 
    !! 在那里它们不会引起冲突。 不幸的是 IBM 在原 PC 机中搞糟了, 以后也没有纠正过来。 
    !! PC 机的 bios 将中断放在了 0x08-0x0f,这些中断也被用于内部硬件中断。 
    !! 所以我们就必须重新对 8259 中断控制器进行编程,这一点都没劲。 

    153
     
    154
     mov al,#0x11 ! initialization seque nce 
    ! 0x11 表示初始化命令开始, 是 ICW1 命令字,表示边 
    ! 沿触发、多片 8259 级连、最后要发送 ICW4 命令字。 

    155
     out #0x20,al ! send it to 8259A-1 ! 发送到 8259A 主芯片。 
    ! 下面定义的两个字是直接使用机器码表示的两条相对跳转指令, 起延时作用。 
    ! 0xeb 是直接近跳转指令的操作码,带 1 个字节的相对位移值。因此跳转范围是-127 到 127。CPU 通过 
    ! 把这个相对位移值加到 EIP 寄存器中就形成一个新的有效地址。 此时 EIP 指向下一条被执行的指令。 
    ! 执行时所花费的 CPU 时钟周期数是 7 至 10 个。0x00eb 表示跳转值是 0 的一条指令, 因此还是直接 
    ! 执行下一条指令。这两条指令共可提供 14-20 个 CPU 时钟周期的延迟时间。 在 as86 中没有表示相应 
    ! 指令的助记符, 因此 Linus 在 setup.s 等一些汇编程序中就直接使用机器码来表示这种指令。另外, 
    ! 每个空操作指令 NOP 的时钟周期数是 3 个, 因此若要达到相同的延迟效果就需要 6 至 7 个 NOP 指令。 

    156
     .word 0x00eb,0x00eb ! jmp $+2, jmp $+2 ! '$' 表示当前指令的地址, 
    157
     out #0xA0,al ! and to 8259A-2 ! 再发送到 8259A 从芯片。 
    158
     .word 0x00eb,0x00eb 
    159
     mov al,#0x20 ! start of hardware in t's (0x20) 
    160
     out #0x21,al ! 送主芯片 ICW2 命令字,起始中断号, 要送奇地址。 
    161
     .word 0x00eb,0x00eb 
    162
     mov al,#0x28 ! start of hardware in t's 2 (0x28) 
    163
     out #0xA1,al ! 送从芯片 ICW2 命令字,从芯片的起始中断号。 
    164
     .word 0x00eb,0x00eb 
    165
     mov al,#0x04 ! 8259-1 is master 
    166
     out #0x21,al ! 送主芯片 ICW3 命令字,主芯片的 IR2 连从芯片 INT。 
    167
     .word 0x00eb,0x00eb !参见代码列表后的说明。 
    168
     mov al,#0x02 ! 8259-2 is slave 
    169
     out #0xA1,al ! 送从芯片 ICW3 命令字,表示从芯片的 INT 连到主芯 
    ! 片的 IR2 引脚上。 

    170
     .word 0x00eb,0x00eb 
    171
     mov al,#0x01 ! 8086 mode for both 
    172
     out #0x21,al ! 送主芯片 ICW4 命令字。8086 模式;普通 EOI 方式, 
    ! 需发送指令来复位。初始化结束,芯片就绪。 

    173
     .word 0x00eb,0x00eb 
    174
     out #0xA1,al !送从芯片 ICW4 命令字,内容同上。 
    175
     .word 0x00eb,0x00eb 
    176
     mov al,#0xFF ! mask off all interru pts for now 
    177
     out #0x21,al ! 屏蔽主芯片所有中断请求。 
    178
     .word 0x00eb,0x00eb 
    179
     out #0xA1,al !屏蔽从芯片所有中断请求。 
    180
     
    181
     ! well, that certainly wasn't fun :-(. Hopefully it works, and we don't 
    182
     ! need no steenking BIOS anyway (except for the initial loadin g :-). 
    183
     ! The BIOS-routine wants lots of unnecessary data, and it's le ss 

    184 ! "interesting" anyway. This is how REAL programmers do it. 
    185
     ! 
    186
     ! Well, now's the time to actually move into protected mode. T o make 
    187
     ! things as simple as possible, we do no register set-up or an ything, 
    188
     ! we let the gnu-compiled 32-bit programs do that. We just jum p to 
    189
     ! absolute address 0x00000, in 32-bit protected mode. 
    !! 哼,上面这段当然没劲
    /, 希望这样能工作, 而且我们也不再需要乏味的 BIOS 了(除了 
    !! 初始的加载
    ?。BIOS 子程序要求很多不必要的数据,而且它一点都没趣。 那是“真正”的 
    !! 程序员所做的事。 

    190
     
    ! 这里设置进入 32 位保护模式运行。 首先加载机器状态字(lmsw-Load Machine Status Word), 也称 
    ! 控制寄存器 CR0,其比特位 0 置 1 将导致 CPU 工作在保护模式。 

    191
     mov ax,#0x0001 ! protected mode (PE) bit ! 保护 模式比特位(PE)。 
    192
     lmsw ax ! This is it! ! 就这样加载机器状态字! 
    193
     jmpi 0,8 ! jmp offset 0 of segment 8 (c s) ! 跳转至 cs 段 8,偏移 0 处。 
    ! 我们已经将 system 模块移动到 0x00000 开始的地方,所以这里的偏移地址是 0。这里的段 
    ! 值的 8 已经是保护模式下的段选择符了,用于选择描述符表和描述符表项以及所要求的特权级。 
    ! 段选择符长度为 16 位(2 字节);位 0-1 表示请求的特权级 0-3,linux 操作系统只 
    ! 用到两级:0 级(系统级)和 3 级(用户级); 位 2 用于选择全局描述符表(0)还是局部描 
    ! 述符表(1);位 3-15 是描述符表项的索引, 指出选择第几项描述符。所以段选择符 
    ! 8(0b0000,0000,0000,1000)表示请求特权级 0、 使用全局描述符表中的第 1 项,该项指出 
    ! 代码的基地址是 0(参见 209 行),因此这里的跳转指令就会去执行 system 中的代码。 

    194
     
    195
     ! This routine checks that the keyboard command queue is empty 
    196
     ! No timeout is used - if this hangs there is something wrong with 
    197
     ! the machine, and we probably couldn't proceed anyway. 
    ! 下面这个子程序检查键盘命令队列是否为空。这里不使用超时方法 - 如果这里死机, 
    ! 则说明 PC 机有问题,我们就没有办法再处理下去了。 
    ! 只有当输入缓冲器为空时(状态寄存器位 2 = 0)才可以对其进行写命令。 

    198
     empty_8042: 
    199
     .word 0x00eb,0x00eb ! 这是两个跳转指令的机器码(跳转到下一句), 相当于延时空操作。 
    200
     in al,#0x64 ! 8042 status port ! 读 AT 键盘控制器状态寄存器。 
    201
     test al,#2 ! is input buffer full? ! 测试位 2,输入缓冲器满? 
    202
     jnz empty_8042 ! yes - loop 
    203
     ret 
    204
     
    205
     gdt: ! 全局描述符表开始处。 描述符表由多个 8 字节长的描述符项组成。 
    ! 这里给出了 3 个描述符项。第 1 项无用(206 行),但须存在。第 2 项是系统代码段 
    ! 描述符(208-211 行),第 3 项是系统数据段描述符(213-216 行)。 每个描述符的具体 
    ! 含义参见列表后说明。 

    206
     .word 0,0,0,0 ! dummy ! 第 1 个描述符, 不用。 
    207
     ! 这里在 gdt 表中的偏移量为 0x08,当加载代码段寄存器(段选择符)时, 使用的是这个偏移值。 
    208
     .word 0x07FF ! 8Mb - limit=2047 (2048*4096= 8Mb) 
    209
     .word 0x0000 ! base address=0 
    210
     .word 0x9A00 ! code read/exec 
    211
     .word 0x00C0 ! granularity=4096, 386 
    212
     ! 这里在 gdt 表中的偏移量是 0x10,当加载数据段寄存器(如 ds 等)时,使用的是这个偏移值。 
    213
     .word 0x07FF ! 8Mb - limit=2047 (2048*4096= 8Mb) 
    214
     .word 0x0000 ! base address=0 
    215
     .word 0x9200 ! data read/write 
    216
     .word 0x00C0 ! granularity=4096, 386 
    217
     
    218
     idt_48: 

    219 .word 0 ! idt limit=0 
    220
     .word 0,0 ! idt base=0L 
    221
     
    222
     gdt_48: 
    223
     .word 0x800 ! gdt limit=2048, 256 GDT entr ies 
    ! 全局表长度为 2k 字节,因为每 8 字节组成一个段描述符项 
    ! 所以表中共可有 256 项。 

    224
     .word 512+gdt,0x9 ! gdt base = 0X9xxxx 
    ! 4 个字节构成的内存线性地址:0x0009<<16 + 0x0200+gdt 
    ! 也即 0x90200 + gdt(即在本程序段中的偏移地址,205 行)。 

    225
     
    226
     .text 
    227
     endtext: 
    228
     .data 
    229
     enddata: 
    230
     .bss 
    231
     endbss:

     
     
     
     
     
  • 相关阅读:
    数据结构 -- 栈(一)
    数据结构 -- 栈(二)
    Linux 静态库 & 动态库
    Python及Pycharm安装详细教程
    Makefile研究(三) —— 实际应用
    Makefile研究(二)—— 完整可移植性模板
    Makefile研究 (一)—— 必备语法
    JSON 下 -- jansson 示例
    C语言中的static 详细分析
    Linux 命令 -- tar
  • 原文地址:https://www.cnblogs.com/skl374199080/p/3864897.html
Copyright © 2020-2023  润新知