一. 谁是最开始执行的代码
a. 最终生成的bzImage的大小
-
cong@msi:/work/os/linux-2.4.12$ ls -alh ./arch/i386/boot/bzImage
-
-rw-rw-r-- 1 cong cong 794K Oct 22 02:17 ./arch/i386/boot/bzImage
b.bochsr的配置
floppya: 1_44="./arch/i386/boot/bzImage", status=inserted
boot: floppy
c. zImage中的数据布局
d. 生成bzImage的过程
tools/build -b bbootsect bsetup compressed/bvmlinux.out CURRENT > bzImage
所以刚开始执行的是 arch/i386/boot/bootsect.S
二.最开始的bootsect.S分析
2.1 arch/i386/boot/bootsect.S分析
-
#include <asm/boot.h>
-
-
SETUPSECTS = 4 /* default nr of setup-sectors */
-
BOOTSEG = 0x07C0 /* original address of boot-sector */
-
INITSEG = DEF_INITSEG /* we move boot here - out of the way */
-
SETUPSEG = DEF_SETUPSEG /* setup starts here */
-
SYSSEG = DEF_SYSSEG /* system loaded at 0x10000 (65536) */
-
SYSSIZE = DEF_SYSSIZE /* system size: # of 16-byte clicks */
-
/* to be loaded */
-
ROOT_DEV = 0 /* ROOT_DEV is now written by "build" */
-
SWAP_DEV = 0 /* SWAP_DEV is now written by "build" */
-
-
#ifndef SVGA_MODE
-
#define SVGA_MODE ASK_VGA
-
#endif
-
-
#ifndef RAMDISK
-
#define RAMDISK 0
-
#endif
-
-
#ifndef ROOT_RDONLY
-
#define ROOT_RDONLY 1
-
#endif
-
-
.code16
-
.text
-
-
.global _start
-
_start:
-
-
# First things first. Move ourself from 0x7C00 -> 0x90000 and jump there.
-
//1.把bootsect的512字节由0x7C00复制到0x90000,并跳转到0x90000处
-
movw $BOOTSEG, %ax
-
movw %ax, %ds # %ds = BOOTSEG
-
movw $INITSEG, %ax
-
movw %ax, %es # %ax = %es = INITSEG
-
movw $256, %cx #bootsect一共512个字节,这儿是2个字节一复制,所以是512/2=256次
-
subw %si, %si
-
subw %di, %di
-
cld
-
rep
-
movsw
-
ljmp $INITSEG, $go -->jmpf 0x9000:0019
-
-
# bde - changed 0xff00 to 0x4000 to use debugger at 0x6400 up (bde). We
-
# wouldn't have to worry about this if we checked the top of memory. Also
-
# my BIOS can be configured to put the wini drive tables in high memory
-
# instead of in the vector table. The old stack might have clobbered the
-
# drive table.
-
-
go: movw $0x4000-12, %di # 0x4000 is an arbitrary value >=
-
# length of bootsect + length of
-
# setup + room for stack;
-
# 12 is disk parm size.
-
movw %ax, %ds # %ax and %es already contain INITSEG
-
movw %ax, %ss
-
movw %di, %sp # put stack at INITSEG:0x4000-12.
-
-
# Many BIOS's default disk parameter tables will not recognize
-
# multi-sector reads beyond the maximum sector number specified
-
# in the default diskette parameter tables - this may mean 7
-
# sectors in some cases.
-
#
-
# Since single sector reads are slow and out of the question,
-
# we must take care of this by creating new parameter tables
-
# (for the first disk) in RAM. We will set the maximum sector
-
# count to 36 - the most we will encounter on an ED 2.88.
-
#
-
# High doesn't hurt. Low does.
-
#
-
# Segments are as follows: %cs = %ds = %es = %ss = INITSEG, %fs = 0,
-
# and %gs is unused.
-
//0x78到0x80,0x81四个字节中存放着磁盘参数表的地址
-
//这个地址刚开始是由fs:(%bx)指定,后来通过ldsw指令放在了ds:si中
-
//rep movsw word ptr es:[di], word ptr ds:[si]把磁盘参数表的数据读到了0x93ff4
-
movw %cx, %fs # %fs = 0
-
movw $0x78, %bx -->fs:bx是磁盘参数表的地址# %fs:%bx is parameter table address
-
pushw %ds
-
ldsw %fs:(%bx), %si -->把从fs:0x78磁盘参数表的地址复制到ds:si
-
movb $6, %cl -->读12个字节,每次读2个共6次
-
pushw %di -->di=0x4000-12=0x3FF4 # %di = 0x4000-12.
-
rep -->把磁盘参数表的数据读到[0x93FF4-0x94000]处共12个字节
-
movsw -->rep movsw word ptr es:[di], word ptr ds:[si]中
-
//修改0x93ff4+0x4处的sector_count的值
-
popw %di
-
popw %ds
-
movb $36, 0x4(%di) # patch sector count
-
//修改fs:(%bx)所指向的磁盘参数表的地址为0x93F44-->di=0x3F44,es=0x9000
-
movw %di, %fs:(%bx)
-
movw %es, %fs:2(%bx)
-
-
# Get disk drive parameters, specifically number of sectors/track.
-
-
# It seems that there is no BIOS call to get the number of sectors.
-
# Guess 36 sectors if sector 36 can be read, 18 sectors if sector 18
-
# can be read, 15 if sector 15 can be read. Otherwise guess 9.
-
# Note that %cx = 0 from rep movsw above.
-
//按36 18 15 9依次去测试磁道的扇区数:
-
//第1次试的寄存器如下:
-
//ax=0x0201: ah=02->读磁盘到内存 al=01-->需要读出的扇区数=1
-
//cx=0x0024: ch=0-->柱面的低8位=0 cl=0x24-->开始扇区(位0-5)是24,柱面号高2位(6-7)
-
//dx=0x0000:dh=00-->磁头号=0 dl=0-->驱动器号
-
//es:bx=0x9000:0x200是数据缓冲区,如果出错CF标志置位,ah是出错码
-
//综上,从0磁头0柱面36扇区开始读取一个扇区,如果没有出错,则说明sector_num=36
-
//如果出错则试18扇区,再试15扇区,最后试9扇区
-
//结果保存在sector中
-
movw $disksizes, %si #char disksizes[4]={36, 18, 15, 9}要试的扇区数表
-
probe_loop:
-
lodsb -->lodsb al, byte ptr ds:[si]-->执行完后si+1
-
cbtw -->将测试的setor_nr保存在al中,这儿扩展ax=00al
-
movw %ax, sectors -->将结果先保存在sector中ds:0x1c3
-
cmpw $disksizes+4, %si -->看si是不是到了disksize表的最后
-
jae got_sectors -->如果所有的测试都失败了,就认为sector_num是9
-
-
xchgw %cx, %ax -->交换ax与cx的值,则cx=要试的扇区数=36,18,15,9
-
xorw %dx, %dx -->dx清0
-
movw $0x0200, %bx -->ex:bx=0x90200,即setup要保存的位置
-
movw $0x0201, %ax -->int0x13的0x2服务, 01-->要读取的扇区数
-
int $0x13
-
jc probe_loop # try next value
-
//下面是获取光标信息
-
got_sectors:
-
movb $0x03, %ah # read cursor pos
-
xorb %bh, %bh
-
int $0x10
-
movw $9, %cx
-
movb $0x07, %bl # page 0, attribute 7 (normal)
-
# %bh is set above; int10 doesn't
-
# modify it
-
//下面是打印字符Loading
-
movw $msg1, %bp
-
movw $0x1301, %ax # write string, move cursor
-
int $0x10 # tell the user we're loading..
-
-
# Load the setup-sectors directly after the moved bootblock (at 0x90200).
-
# We should know the drive geometry to do it, as setup may exceed first
-
# cylinder (for 9-sector 360K and 720K floppies).
-
//下面读取setup的内容到0x90200处
-
movw $0x0001, %ax # set sread (sector-to-read) to 1 as
-
movw $sread, %si # the boot sector has already been read
-
movw %ax, (%si) -->这三行是将sread中记录己读的扇区数设为1,因为bootsect己读所以设为1
-
-
xorw %ax, %ax # reset FDC
-
xorb %dl, %dl
-
int $0x13 -->这三行是将软盘复位
-
movw $0x0200, %bx # address = 512, in INITSEG
-
next_step:
-
movb setup_sects, %al -->setup_sects是setup的扇区数,即总共要读取的扇区数是5-->此后al=5
-
movw sectors, %cx -->secotrs是每个磁道的扇区数=0x12-->此后cx=0x12
-
subw (%si), %cx -->计算本磁道还可以读的扇区数=本磁盘的扇区-己读取的扇区:cx=0x12-0x1=0x11
-
cmpb %cl, %al -->cl=0x11:本磁道还要读取的扇区数-->al=5是总共要读取的扇区数
-
jbe no_cyl_crossing -->如果cl>al说明不需要跨磁道读扇区
-
movw sectors, %ax
-
subw (%si), %ax # (%si) = sread
-
no_cyl_crossing:
-
call read_track -->读取本磁道中剩余的扇区数到0x90200,即把setup放在了0x90200处
-
pushw %ax # save it
-
call set_next # set %bx properly; it uses %ax,%cx,%dx
-
popw %ax # restore
-
subb %al, setup_sects # rest - for next step
-
jnz next_step
-
//下面把system的内容读取到0x100000
-
pushw $SYSSEG
-
popw %es # %es = SYSSEG
-
call read_it -->将带解压头的vmlinux.out读到0x100000=1M处
-
call kill_motor -->关闭驱动器马达
-
call print_nl -->打印回车换行
-
-
# After that we check which root-device to use. If the device is
-
# defined (!= 0), nothing is done and the given device is used.
-
# Otherwise, one of /dev/fd0H2880 (2,32) or /dev/PS0 (2,28) or /dev/at0 (2,8)
-
# depending on the number of sectors we pretend to know we have.
-
-
# Segments are as follows: %cs = %ds = %ss = INITSEG,
-
# %es = SYSSEG, %fs = 0, %gs is unused.
-
-
movw root_dev, %ax -->检查root_dev是否是0,如果不是0,认为文件系统的结点己定义,则跳到setup中了
-
orw %ax, %ax
-
jne root_defined
-
-
movw sectors, %bx -->如果文件系统结点没有定义,下面尝试
-
movw $0x0208, %ax # /dev/ps0 - 1.2Mb
-
cmpw $15, %bx
-
je root_defined
-
-
movb $0x1c, %al # /dev/PS0 - 1.44Mb
-
cmpw $18, %bx
-
je root_defined
-
-
movb $0x20, %al # /dev/fd0H2880 - 2.88Mb
-
cmpw $36, %bx
-
je root_defined
-
-
movb $0, %al # /dev/fd0 - autodetect
-
root_defined:
-
movw %ax, root_dev
-
-
# After that (everything loaded), we jump to the setup-routine
-
# loaded directly after the bootblock:
-
-
ljmp $SETUPSEG, $0 -->跳到setup中去了
-
-
# These variables are addressed via %si register as it gives shorter code.
-
-
sread: .word 0 # sectors read of current track
-
head: .word 0 # current head
-
track: .word 0 # current track
-
-->中间略过一些函数
-
-
sectors: .word 0
-
disksizes: .byte 36, 18, 15, 9
-
msg1: .byte 13, 10
-
.ascii "Loading"
-
-
# XXX: This is a fairly snug fit.
-
-
.org 497
-
setup_sects: .byte SETUPSECTS -->这儿在编译时定义了一个默认值,但最后tools/build会修改这个值为编译出来的setup的扇区数
-
root_flags: .word ROOT_RDONLY
-
syssize: .word SYSSIZE -->这儿在编译时定义了一个默认值,但最后tools/build会修改这个值为编译出来的vmlinux.out/16的值
-
swap_dev: .word SWAP_DEV
-
ram_size: .word RAMDISK
-
vid_mode: .word SVGA_MODE
-
root_dev: .word ROOT_DEV -->这儿在编译时定义了一个默认值,但最后tools/build会修改这个值为CURRENT的值
-
boot_flag: .word 0xAA55
2.2. 总结一下bootsect的作用
a. 先把bootsect自己这512个字节移动到0x90000,然后跳到0x90000+0x19去运行
b. 把磁盘上从第2个扇区开始的n=5个扇区复制到0x90200,要复制扇区的数目记录在mbr的497处
c. 把磁盘上从第n=5个扇区开始的n+N个扇区的kernel复制到0x100000=1M处,要复制的大小记录在mbr的500处
附录: 参考文章:
[精华] bootsect.S分析(一篇很有用的老东东)
附录2:CBW
CBW :字节转换为字执行的操作:AL的内容符号扩展到AH,形成AX中的字。即如果(AL)的最高有效位为0,则(AH)=0;如(AL)的最高有效位为1,则(AH)=0FFH。
三. setup分析
注意这儿的setup是由setup.S与video.S一起组成的
3.1 arch/i386/boot/setup.S代码分析
-
#include <linux/config.h>
-
#include <asm/segment.h>
-
#include <linux/version.h>
-
#include <linux/compile.h>
-
#include <asm/boot.h>
-
#include <asm/e820.h>
-
-
/* Signature words to ensure LILO loaded us right */
-
#define SIG1 0xAA55
-
#define SIG2 0x5A5A
-
-
INITSEG = DEF_INITSEG # 0x9000, we move boot here, out of the way
-
SYSSEG = DEF_SYSSEG # 0x1000, system loaded at 0x10000 (65536).
-
SETUPSEG = DEF_SETUPSEG # 0x9020, this is the current segment
-
# ... and the former contents of CS
-
-
DELTA_INITSEG = SETUPSEG - INITSEG # 0x0020
-
-
.code16
-
.globl begtext, begdata, begbss, endtext, enddata, endbss
-
-
.text
-
begtext:
-
.data
-
begdata:
-
.bss
-
begbss:
-
.text
-
//第1次跳
-
start:
-
jmp trampoline -->0: eb 2a -->jmp 2c -->这个jmp放在setup的最开始,偏移是0
-
-
# This is the setup header, and it must start at %cs:2 (old 0x9020:2)
-
-
.ascii "HdrS" # header signature
-
.word 0x0202 # header version number (>= 0x0105)
-
# or else old loadlin-1.5 will fail)
-
realmode_swtch: .word 0, 0 # default_switch, SETUPSEG
-
start_sys_seg: .word SYSSEG
-
.word kernel_version # pointing to kernel version string
-
# above section of header is compatible
-
# with loadlin-1.5 (header v1.5). Don't
-
# change it.
-
-
type_of_loader: .byte 0 # = 0, old one (LILO, Loadlin,
-
# Bootlin, SYSLX, bootsect...)
-
# See Documentation/i386/boot.txt for
-
# assigned ids
-
-
# flags, unused bits must be zero (RFU) bit within loadflags
-
loadflags:
-
LOADED_HIGH = 1 # If set, the kernel is loaded high
-
CAN_USE_HEAP = 0x80 # If set, the loader also has set
-
# heap_end_ptr to tell how much
-
# space behind setup.S can be used for
-
# heap purposes.
-
# Only the loader knows what is free
-
#ifndef __BIG_KERNEL__
-
.byte 0
-
#else
-
.byte LOADED_HIGH
-
#endif
-
-
setup_move_size: .word 0x8000 # size to move, when setup is not
-
# loaded at 0x90000. We will move setup
-
# to 0x90000 then just before jumping
-
# into the kernel. However, only the
-
# loader knows how much data behind
-
# us also needs to be loaded.
-
-
code32_start: # here loaders can put a different
-
# start address for 32-bit code.
-
#ifndef __BIG_KERNEL__
-
.long 0x1000 # 0x1000 = default for zImage
-
#else
-
.long 0x100000 # 0x100000 = default for big kernel
-
#endif
-
-
ramdisk_image: .long 0 # address of loaded ramdisk image
-
# Here the loader puts the 32-bit
-
# address where it loaded the image.
-
# This only will be read by the kernel.
-
-
ramdisk_size: .long 0 # its size in bytes
-
-
bootsect_kludge:
-
.word bootsect_helper, SETUPSEG
-
-
heap_end_ptr: .word modelist+1024 # (Header version 0x0201 or later)
-
# space from here (exclusive) down to
-
# end of setup code can be used by setup
-
# for local heap purposes.
-
-
pad1: .word 0
-
cmd_line_ptr: .long 0 # (Header version 0x0202 or later)
-
# If nonzero, a 32-bit pointer
-
# to the kernel command line.
-
# The command line should be
-
# located between the start of
-
# setup and the end of low
-
# memory (0xa0000), or it may
-
# get overwritten before it
-
# gets read. If this field is
-
# used, there is no longer
-
# anything magical about the
-
# 0x90000 segment; the setup
-
# can be located anywhere in
-
# low memory 0x10000 or higher.
-
//第2次跳
-
trampoline: call start_of_setup -->又跳了一次
-
.space 1024
-
# End of setup header #####################################################
-
-
start_of_setup:
-
# Bootlin depends on this being done early
-
movw $0x01500, %ax
-
movb $0x81, %dl
-
int $0x13
-
-
#ifdef SAFE_RESET_DISK_CONTROLLER
-
# Reset the disk controller.
-
movw $0x0000, %ax
-
movb $0x80, %dl
-
int $0x13
-
#endif
-
-
//原先ds=0x9000,执行下面两行后ds=0x9020
-
movw %cs, %ax # aka SETUPSEG
-
movw %ax, %ds
-
//setup_sig1与setup_sig2都放在setup的最后,作用是防止引导程序LILO加载setup不完整
-
//这儿用软盘启动,不担心这个,直接跳到good_sig1去
-
cmpw $SIG1, setup_sig1
-
jne bad_sig
-
-
cmpw $SIG2, setup_sig2
-
jne bad_sig
-
//第3次跳
-
jmp good_sig1 -->直接跳到good_sig1去
-
-
# Routine to print asciiz string at ds:si
-
prtstr:
-
lodsb
-
andb %al, %al
-
jz fin
-
-
call prtchr
-
jmp prtstr
-
-
fin: ret
-
-
# Space printing
-
prtsp2: call prtspc # Print double space
-
prtspc: movb $0x20, %al # Print single space (note: fall-thru)
-
-
# Part of above routine, this one just prints ascii al
-
prtchr: pushw %ax
-
pushw %cx
-
xorb %bh, %bh
-
movw $0x01, %cx
-
movb $0x0e, %ah
-
int $0x10
-
popw %cx
-
popw %ax
-
ret
-
-
beep: movb $0x07, %al
-
jmp prtchr
-
-
no_sig_mess: .string "No setup signature found ..."
-
//第4次跳
-
good_sig1:
-
jmp good_sig
-
-
# We now have to find the rest of the setup code/data
-
bad_sig:
-
movw %cs, %ax # SETUPSEG
-
subw $DELTA_INITSEG, %ax # INITSEG
-
movw %ax, %ds
-
xorb %bh, %bh
-
movb (497), %bl # get setup sect from bootsect
-
subw $4, %bx # LILO loads 4 sectors of setup
-
shlw $8, %bx # convert to words (1sect=2^8 words)
-
movw %bx, %cx
-
shrw $3, %bx # convert to segment
-
addw $SYSSEG, %bx
-
movw %bx, %cs:start_sys_seg
-
# Move rest of setup code/data to here
-
movw $2048, %di # four sectors loaded by LILO
-
subw %si, %si
-
movw %cs, %ax # aka SETUPSEG
-
movw %ax, %es
-
movw $SYSSEG, %ax
-
movw %ax, %ds
-
rep
-
movsw
-
movw %cs, %ax # aka SETUPSEG
-
movw %ax, %ds
-
cmpw $SIG1, setup_sig1
-
jne no_sig
-
-
cmpw $SIG2, setup_sig2
-
jne no_sig
-
-
jmp good_sig
-
-
no_sig:
-
lea no_sig_mess, %si
-
call prtstr
-
-
no_sig_loop:
-
jmp no_sig_loop
-
//第4次跳到这儿
-
good_sig:
-
//原先ds=0x9020,执行下面两行后ds=0x9000,跟上面一次是把ds变为0x9020现在又回去了
-
//这儿的目的是:把memory的信息放在0x9000:offset处,所以要把ds变为0x9000
-
movw %cs, %ax # aka SETUPSEG
-
subw $DELTA_INITSEG, %ax # aka INITSEG
-
movw %ax, %ds
-
# Check if an old loader tries to load a big-kernel
-
//查看loadflasgs的值是不是1,type_of_loaderr的值是不是0,如果都对就跳到loader_ok中
-
testb $LOADED_HIGH, %cs:loadflags # Do we have a big kernel?
-
jz loader_ok # No, no danger for old loaders.
-
-
cmpb $0, %cs:type_of_loader # Do we have a loader that
-
# can deal with us?
-
jnz loader_ok //第5次跳 # Yes, continue.
-
-
pushw %cs # No, we have an old loader,
-
popw %ds # die.
-
lea loader_panic_mess, %si
-
call prtstr
-
-
jmp no_sig_loop
-
-
loader_panic_mess: .string "Wrong loader, giving up..."
-
//第5次跳到这儿
-
loader_ok:
-
# Get memory size (extended mem, kB)
-
-
xorl %eax, %eax
-
movl %eax, (0x1e0)
-
#ifndef STANDARD_MEMORY_BIOS_CALL
-
movb %al, (E820NR)
-
# Try three different memory detection schemes. First, try
-
# e820h, which lets us assemble a memory map, then try e801h,
-
# which returns a 32-bit memory size, and finally 88h, which
-
# returns 0-64m
-
//下面用三种不同的方法去获取memory_size,幸好我这儿是用的最简单的0x88 int 0x15的方式获得的
-
//下面的一大堆代码都不需要看了,获取之后将memory的信息放在0x90002处
-
# method E820H:
-
# the memory map from hell. e820h returns memory classified into
-
# a whole bunch of different types, and allows memory holes and
-
# everything. We scan through this memory map and build a list
-
# of the first 32 memory areas, which we return at [E820MAP].
-
# This is documented at http://www.teleport.com/~acpi/acpihtml/topic245.htm
-
-
#define SMAP 0x534d4150
-
//第1种检测内存的方法是E820
-
// 在include/asm-i386/e820.h中规定了
// #define E820MAP 0x2d0 /* our map */
// #define E820NR 0x1e8 /* # entries in E820MAP */
-
//所以E820的结果会放在0x90000+0x2d0, 个数放在0x900000+0x1e8中
-
meme820:
-
xorl %ebx, %ebx # continuation counter
-
movw $E820MAP, %di -->E820的数据结果会放在E820MAP中
-
jmpe820:
-
movl $0x0000e820, %eax # e820, upper word zeroed
-
movl $SMAP, %edx # ascii 'SMAP'
-
movl $20, %ecx # size of the e820rec
-
pushw %ds # data record.
-
popw %es
-
int $0x15 # make the call
-
jc bail820 # fall to e801 if it fails
-
-
cmpl $SMAP, %eax # check the return is `SMAP'
-
jne bail820 # fall to e801 if it fails
-
-
# cmpl $1, 16(%di) # is this usable memory?
-
# jne again820
-
-
# If this is usable memory, we save it by simply advancing %di by
-
# sizeof(e820rec).
-
#
-
good820:
-
movb (E820NR), %al # up to 32 entries
-
cmpb $E820MAX, %al
-
jnl bail820
-
-
incb (E820NR) -->E820的map个数每次加1
-
movw %di, %ax
-
addw $20, %ax
-
movw %ax, %di
-
again820:
-
cmpl $0, %ebx # check to see if
-
jne jmpe820 # %ebx is set to EOF
-
bail820:
-
-
-
# method E801H:
-
# memory size is in 1k chunksizes, to avoid confusing loadlin.
-
# we store the 0xe801 memory size in a completely different place,
-
# because it will most likely be longer than 16 bits.
-
# (use 1e0 because that's what Larry Augustine uses in his
-
# alternative new memory detection scheme, and it's sensible
-
# to write everything into the same place.)
-
-
meme801:
-
stc # fix to work around buggy
-
xorw %cx,%cx # BIOSes which dont clear/set
-
xorw %dx,%dx # carry on pass/error of
-
# e801h memory size call
-
# or merely pass cx,dx though
-
# without changing them.
-
movw $0xe801, %ax
-
int $0x15
-
jc mem88
-
-
cmpw $0x0, %cx # Kludge to handle BIOSes
-
jne e801usecxdx # which report their extended
-
cmpw $0x0, %dx # memory in AX/BX rather than
-
jne e801usecxdx # CX/DX. The spec I have read
-
movw %ax, %cx # seems to indicate AX/BX
-
movw %bx, %dx # are more reasonable anyway...
-
-
e801usecxdx:
-
andl $0xffff, %edx # clear sign extend
-
shll $6, %edx # and go from 64k to 1k chunks
-
movl %edx, (0x1e0) # store extended memory size
-
andl $0xffff, %ecx # clear sign extend
-
addl %ecx, (0x1e0) # and add lower memory into
-
# total size.
-
-
# Ye Olde Traditional Methode. Returns the memory size (up to 16mb or
-
# 64mb, depending on the bios) in ax.
-
mem88:
-
-
#endif
-
movb $0x88, %ah -->0x88 int 0x15 中断获取扩展内存:即meory_size-1M大小的内存
-
int $0x15
-
movw %ax, (2) -->将memory的信息放在0x90002处
-
-
# Set the keyboard repeat rate to the max
-
movw $0x0305, %ax
-
xorw %bx, %bx
-
int $0x16
-
-
# Check for video adapter and its parameters and allow the
-
# user to browse video modes.
-
call video -->setup模块是setup.s+video.S一起生成的
-
-
//将第一块硬盘的参数表放在0x90080处 -->这个地方跟linux0.11是一样的,所以好熟悉好轻松
-
# Get hd0 data...
-
xorw %ax, %ax
-
movw %ax, %ds
-
ldsw (4 * 0x41), %si
-
movw %cs, %ax # aka SETUPSEG
-
subw $DELTA_INITSEG, %ax # aka INITSEG
-
pushw %ax
-
movw %ax, %es
-
movw $0x0080, %di
-
movw $0x10, %cx
-
pushw %cx
-
cld
-
rep
-
movsb
-
-
//将第二块硬盘的参数表放在0x90090处
-
# Get hd1 data...
-
xorw %ax, %ax
-
movw %ax, %ds
-
ldsw (4 * 0x46), %si
-
popw %cx
-
popw %es
-
movw $0x0090, %di
-
rep
-
movsb
-
//检查系统是不是有两个硬盘,如果没有把表2清0
-
# Check that there IS a hd1 :-)
-
movw $0x01500, %ax
-
movb $0x81, %dl
-
int $0x13
-
jc no_disk1
-
-
cmpb $3, %ah
-
je is_disk1
-
-
no_disk1:
-
movw %cs, %ax # aka SETUPSEG
-
subw $DELTA_INITSEG, %ax # aka INITSEG
-
movw %ax, %es
-
movw $0x0090, %di
-
movw $0x10, %cx
-
xorw %ax, %ax
-
cld
-
rep
-
stosb
-
is_disk1:
-
# check for Micro Channel (MCA) bus
-
movw %cs, %ax # aka SETUPSEG
-
subw $DELTA_INITSEG, %ax # aka INITSEG
-
movw %ax, %ds
-
xorw %ax, %ax
-
movw %ax, (0xa0) # set table length to 0
-
movb $0xc0, %ah
-
stc
-
int $0x15 # moves feature table to es:bx
-
jc no_mca
-
-
pushw %ds
-
movw %es, %ax
-
movw %ax, %ds
-
movw %cs, %ax # aka SETUPSEG
-
subw $DELTA_INITSEG, %ax # aka INITSEG
-
movw %ax, %es
-
movw %bx, %si
-
movw $0xa0, %di
-
movw (%si), %cx
-
addw $2, %cx # table length is a short
-
cmpw $0x10, %cx
-
jc sysdesc_ok
-
-
movw $0x10, %cx # we keep only first 16 bytes
-
sysdesc_ok:
-
rep
-
movsb
-
popw %ds
-
no_mca:
-
# Check for PS/2 pointing device
-
movw %cs, %ax # aka SETUPSEG
-
subw $DELTA_INITSEG, %ax # aka INITSEG
-
movw %ax, %ds
-
movw $0, (0x1ff) # default is no pointing device
-
int $0x11 # int 0x11: equipment list
-
testb $0x04, %al # check if mouse installed
-
jz no_psmouse
-
-
movw $0xAA, (0x1ff) # device present
-
no_psmouse:
-
-
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
-
没有定义直接省略
-
#endif
-
-
# Now we want to move to protected mode ...
-
cmpw $0, %cs:realmode_swtch
-
jz rmodeswtch_normal -->进入rmodeswtch_normal
-
-
lcall %cs:realmode_swtch
-
-
jmp rmodeswtch_end
-
-
rmodeswtch_normal:
-
pushw %cs
-
call default_switch -->关中断
-
-
rmodeswtch_end:
-
# we get the code32 start address and modify the below 'jmpi'
-
# (loader may have changed it)
-
movl %cs:code32_start, %eax -->这儿是bzImage,所以执行后eax=0x00100000
-
movl %eax, %cs:code32 -->执行后code32由原先的0x1000改变为0x100000
-
-
# Now we move the system to its rightful place ... but we check if we have a
-
# big-kernel. In that case we *must* not move it ...
-
testb $LOADED_HIGH, %cs:loadflags
-
jz do_move0 # .. then we have a normal low
-
# loaded zImage
-
# .. or else we have a high
-
# loaded bzImage
-
jmp end_move # ... and we skip moving
-
-
do_move0:
-
movw $0x100, %ax # start of destination segment
-
movw %cs, %bp # aka SETUPSEG
-
subw $DELTA_INITSEG, %bp # aka INITSEG
-
movw %cs:start_sys_seg, %bx # start of source segment
-
cld
-
do_move:
-
movw %ax, %es # destination segment
-
incb %ah # instead of add ax,#0x100
-
movw %bx, %ds # source segment
-
addw $0x100, %bx
-
subw %di, %di
-
subw %si, %si
-
movw $0x800, %cx
-
rep
-
movsw
-
cmpw %bp, %bx # assume start_sys_seg > 0x200,
-
# so we will perhaps read one
-
# page more than needed, but
-
# never overwrite INITSEG
-
# because destination is a
-
# minimum one page below source
-
jb do_move
-
-
end_move:
-
# then we load the segment descriptors
-
movw %cs, %ax # aka SETUPSEG
-
movw %ax, %ds
-
-
# Check whether we need to be downward compatible with version <=201
-
cmpl $0, cmd_line_ptr
-
jne end_move_self # loader uses version >=202 features
-
cmpb $0x20, type_of_loader
-
je end_move_self # bootsect loader, we know of it
-
-
# Boot loader doesnt support boot protocol version 2.02.
-
# If we have our code not at 0x90000, we need to move it there now.
-
# We also then need to move the params behind it (commandline)
-
# Because we would overwrite the code on the current IP, we move
-
# it in two steps, jumping high after the first one.
-
movw %cs, %ax
-
cmpw $SETUPSEG, %ax
-
je end_move_self
-
-
cli # make sure we really have
-
# interrupts disabled !
-
# because after this the stack
-
# should not be used
-
subw $DELTA_INITSEG, %ax # aka INITSEG
-
movw %ss, %dx
-
cmpw %ax, %dx
-
jb move_self_1
-
-
addw $INITSEG, %dx
-
subw %ax, %dx # this will go into %ss after
-
# the move
-
move_self_1:
-
movw %ax, %ds
-
movw $INITSEG, %ax # real INITSEG
-
movw %ax, %es
-
movw %cs:setup_move_size, %cx
-
std # we have to move up, so we use
-
# direction down because the
-
# areas may overlap
-
movw %cx, %di
-
decw %di
-
movw %di, %si
-
subw $move_self_here+0x200, %cx
-
rep
-
movsb
-
ljmp $SETUPSEG, $move_self_here
-
-
move_self_here:
-
movw $move_self_here+0x200, %cx
-
rep
-
movsb
-
movw $SETUPSEG, %ax
-
movw %ax, %ds
-
movw %dx, %ss
-
end_move_self: # now we are at the right place
-
lidt idt_48 --> 加载idt # load idt with 0,0
-
xorl %eax, %eax # Compute gdt_base
-
movw %ds, %ax # (Convert %ds:gdt to a linear ptr)
-
shll $4, %eax
-
addl $gdt, %eax -->将gdt的地址填充到gdt_48的基地址处
-
movl %eax, (gdt_48+2)
-
lgdt gdt_48 -->加载gdt
-
-
# that was painless, now we enable a20
-
call empty_8042
-
-
movb $0xD1, %al # command write
-
outb %al, $0x64
-
call empty_8042
-
-
movb $0xDF, %al # A20 on
-
outb %al, $0x60
-
call empty_8042
-
-
#
-
# You must preserve the other bits here. Otherwise embarrasing things
-
# like laptops powering off on boot happen. Corrected version by Kira
-
# Brown from Linux 2.2
-
#
-
//打开A20
-
inb $0x92, %al #
-
orb $02, %al # "fast A20" version
-
outb %al, $0x92 # some chips have only this
-
-
# wait until a20 really *is* enabled; it can take a fair amount of
-
# time on certain systems; Toshiba Tecras are known to have this
-
# problem. The memory location used here (0x200) is the int 0x80
-
# vector, which should be safe to use.
-
-
xorw %ax, %ax # segment 0x0000
-
movw %ax, %fs
-
decw %ax # segment 0xffff (HMA)
-
movw %ax, %gs
-
a20_wait:
-
incw %ax # unused memory location <0xfff0
-
movw %ax, %fs:(0x200) # we use the "int 0x80" vector
-
cmpw %gs:(0x210), %ax # and its corresponding HMA addr
-
je a20_wait # loop until no longer aliased
-
-
# make sure any possible coprocessor is properly reset..
-
xorw %ax, %ax
-
outb %al, $0xf0
-
call delay
-
-
outb %al, $0xf1
-
call delay
-
-
# well, that went ok, I hope. Now we mask all interrupts - the rest
-
# is done in init_IRQ().
-
movb $0xFF, %al # mask all interrupts for now
-
outb %al, $0xA1
-
call delay
-
-
movb $0xFB, %al # mask all irq's but irq2 which
-
outb %al, $0x21 # is cascaded
-
-
# 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 0x1000 (or the loader supplied one),
-
# in 32-bit protected mode.
-
#
-
# Note that the short jump isn't strictly needed, although there are
-
# reasons why it might be a good idea. It won't hurt in any case.
-
//打开PE位-->将cr0第0位置1
-
movw $1, %ax # protected mode (PE) bit
-
lmsw %ax # This is
-
jmp flush_instr
-
-
flush_instr:
-
xorw %bx, %bx # Flag to indicate a boot
-
xorl %esi, %esi # Pointer to real-mode code
-
movw %cs, %si
-
subw $DELTA_INITSEG, %si
-
shll $4, %esi # Convert to 32-bit pointer
-
# NOTE: For high loaded big kernels we need a
-
# jmpi 0x100000,__KERNEL_CS
-
#
-
# but we yet haven't reloaded the CS register, so the default size
-
# of the target offset still is 16 bit.
-
# However, using an operant prefix (0x66), the CPU will properly
-
# take our 48 bit far pointer. (INTeL 80386 Programmer's Reference
-
# Manual, Mixing 16-bit and 32-bit code, page 16-6)
-
-
.byte 0x66, 0xea # prefix + jmpi-opcode
-
code32: .long 0x1000 # will be set to 0x100000
-
# for big kernels
-
.word __KERNEL_CS -->jmpi的汇编代码,会修改这个地方
-
-->同时这会加载gdt表进入保护模式
-
# Here's a bunch of information about your current kernel..
-
kernel_version: .ascii UTS_RELEASE
-
.ascii " ("
-
.ascii LINUX_COMPILE_BY
-
.ascii "@"
-
.ascii LINUX_COMPILE_HOST
-
.ascii ") "
-
.ascii UTS_VERSION
-
.byte 0
-
-
# This is the default real mode switch routine.
-
# to be called just before protected mode transition
-
default_switch:
-
cli # no interrupts allowed !
-
movb $0x80, %al # disable NMI for bootup
-
# sequence
-
outb %al, $0x70
-
lret
-
-
# This routine only gets called, if we get loaded by the simple
-
# bootsect loader _and_ have a bzImage to load.
-
# Because there is no place left in the 512 bytes of the boot sector,
-
# we must emigrate to code space here.
-
bootsect_helper:
-
cmpw $0, %cs:bootsect_es
-
jnz bootsect_second
-
-
movb $0x20, %cs:type_of_loader
-
movw %es, %ax
-
shrw $4, %ax
-
movb %ah, %cs:bootsect_src_base+2
-
movw %es, %ax
-
movw %ax, %cs:bootsect_es
-
subw $SYSSEG, %ax
-
lret # nothing else to do for now
-
-
bootsect_second:
-
pushw %cx
-
pushw %si
-
pushw %bx
-
testw %bx, %bx # 64K full?
-
jne bootsect_ex
-
-
movw $0x8000, %cx # full 64K, INT15 moves words
-
pushw %cs
-
popw %es
-
movw $bootsect_gdt, %si
-
movw $0x8700, %ax
-
int $0x15
-
jc bootsect_panic # this, if INT15 fails
-
-
movw %cs:bootsect_es, %es # we reset %es to always point
-
incb %cs:bootsect_dst_base+2 # to 0x10000
-
bootsect_ex:
-
movb %cs:bootsect_dst_base+2, %ah
-
shlb $4, %ah # we now have the number of
-
# moved frames in %ax
-
xorb %al, %al
-
popw %bx
-
popw %si
-
popw %cx
-
lret
-
-
bootsect_gdt:
-
.word 0, 0, 0, 0
-
.word 0, 0, 0, 0
-
-
bootsect_src:
-
.word 0xffff
-
-
bootsect_src_base:
-
.byte 0x00, 0x00, 0x01 # base = 0x010000
-
.byte 0x93 # typbyte
-
.word 0 # limit16,base24 =0
-
-
bootsect_dst:
-
.word 0xffff
-
-
bootsect_dst_base:
-
.byte 0x00, 0x00, 0x10 # base = 0x100000
-
.byte 0x93 # typbyte
-
.word 0 # limit16,base24 =0
-
.word 0, 0, 0, 0 # BIOS CS
-
.word 0, 0, 0, 0 # BIOS DS
-
-
bootsect_es:
-
.word 0
-
-
bootsect_panic:
-
pushw %cs
-
popw %ds
-
cld
-
leaw bootsect_panic_mess, %si
-
call prtstr
-
-
bootsect_panic_loop:
-
jmp bootsect_panic_loop
-
-
bootsect_panic_mess:
-
.string "INT15 refuses to access high mem, giving up."
-
-
# This routine checks that the keyboard command queue is empty
-
# (after emptying the output buffers)
-
#
-
# Some machines have delusions that the keyboard buffer is always full
-
# with no keyboard attached...
-
#
-
# If there is no keyboard controller, we will usually get 0xff
-
# to all the reads. With each IO taking a microsecond and
-
# a timeout of 100,000 iterations, this can take about half a
-
# second ("delay" == outb to port 0x80). That should be ok,
-
# and should also be plenty of time for a real keyboard controller
-
# to empty.
-
#
-
-
empty_8042:
-
pushl %ecx
-
movl $100000, %ecx
-
-
empty_8042_loop:
-
decl %ecx
-
jz empty_8042_end_loop
-
-
call delay
-
-
inb $0x64, %al # 8042 status port
-
testb $1, %al # output buffer?
-
jz no_output
-
-
call delay
-
inb $0x60, %al # read it
-
jmp empty_8042_loop
-
-
no_output:
-
testb $2, %al # is input buffer full?
-
jnz empty_8042_loop # yes - loop
-
empty_8042_end_loop:
-
popl %ecx
-
ret
-
-
# Read the cmos clock. Return the seconds in al
-
gettime:
-
pushw %cx
-
movb $0x02, %ah
-
int $0x1a
-
movb %dh, %al # %dh contains the seconds
-
andb $0x0f, %al
-
movb %dh, %ah
-
movb $0x04, %cl
-
shrb %cl, %ah
-
aad
-
popw %cx
-
ret
-
-
# Delay is needed after doing I/O
-
delay:
-
outb %al,$0x80
-
ret
-
-
# Descriptor tables
-
gdt:
-
.word 0, 0, 0, 0 # dummy
-
.word 0, 0, 0, 0 # unused
-
-
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
-
.word 0 # base address = 0
-
.word 0x9A00 # code read/exec
-
.word 0x00CF # granularity = 4096, 386
-
# (+5th nibble of limit)
-
-
.word 0xFFFF # 4Gb - (0x100000*0x1000 = 4Gb)
-
.word 0 # base address = 0
-
.word 0x9200 # data read/write
-
.word 0x00CF # granularity = 4096, 386
-
# (+5th nibble of limit)
-
idt_48:
-
.word 0 # idt limit = 0
-
.word 0, 0 # idt base = 0L
-
gdt_48:
-
.word 0x8000 # gdt limit=2048,
-
# 256 GDT entries
-
-
.word 0, 0 # gdt base (filled in later)
-
-
# Include video setup & detection code
-
-
#include "video.S" -->将video.S插入到了setup.S中
-
-
# Setup signature -- must be last
-
setup_sig1: .word SIG1
-
setup_sig2: .word SIG2
-
-
# After this point, there is some free space which is used by the video mode
-
# handling code to store the temporary mode table (not used by the kernel).
-
-
modelist:
-
-
.text
-
endtext:
-
.data
-
enddata:
-
.bss
-
endbss:
3.2 总结一下head.S与video.S所形成的bsetup的作用
a. 获取memory的size放在0x90002处
b. 获取hd的参数放在0x90080-0x900A0处
c. 关中断,加载idt gdt,开启A20,打开PE位
d. 跳进保护模式-->0010:00100000处
四.一些问题的说明
4.1 关于bsetup的生成
a.在 ./linux-2.4.12/arch/i386/boot/Makefile中
-
69 setup.s: setup.S video.S Makefile $(BOOT_INCL) $(TOPDIR)/include/linux/version.h $(TOPDIR)/include/linux/compile.h
-
70 $(CPP) $(CPPFLAGS) -D__ASSEMBLY__ -traditional $(SVGA_MODE) $(RAMDISK) $< -o $@
看清楚,L70的规则是$<(第一个依赖文件)不是$^(所有的依赖文件)
b. 再看一下编译的过程
-
make[1]: Entering directory `/work/os/linux-2.4.12/arch/i386/boot'
-
gcc -E -D__KERNEL__ -I/work/os/linux-2.4.12/include -D__BIG_KERNEL__ -D__ASSEMBLY__ -traditional -DSVGA_MODE=NORMAL_VGA setup.S -o bsetup.s
-
as -o bsetup.o bsetup.s
-
ld -m elf_i386 -Ttext 0x0 -s --oformat binary -e begtext -o bsetup bsetup.o
c. 的确是这样的vidoe.S没有参与编译,那vidoe.S中的内容是在什么地方添加进去的呢?
答案:是在setup.S的最后 #include "video.S",所以在编译时没有显示的出现video.S,但在查看bsetup.s时,会知道这个viode.S插入到了setup.S中。
4.2 如何兼容bzImage的内核与zImage的内核?
a. 在./linux-2.4.12/arch/i386/boot/setup.S中定义了code32_start,
如果在编译时指定用bzImage,则code32_start=0x100000,反之code32_start=0x1000
-
117 code32_start: # here loaders can put a different
-
118 # start address for 32-bit code.
-
119 #ifndef __BIG_KERNEL__
-
120 .long 0x1000 # 0x1000 = default for zImage
-
121 #else
-
122 .long 0x100000 # 0x100000 = default for big kernel
-
123 #endif
b.
在./linux-2.4.12/arch/i386/boot/setup.S中setup向内核的跳转的指令前加了一个标签code32
-
728 # NOTE: For high loaded big kernels we need a
-
729 # jmpi 0x100000,__KERNEL_CS
-
730 #
-
731 # but we yet haven't reloaded the CS register, so the default size
-
732 # of the target offset still is 16 bit.
-
733 # However, using an operant prefix (0x66), the CPU will properly
-
734 # take our 48 bit far pointer. (INTeL 80386 Programmer's Reference
-
735 # Manual, Mixing 16-bit and 32-bit code, page 16-6)
-
736
-
737 .byte 0x66, 0xea # prefix + jmpi-opcode
-
738 code32: .long 0x1000 # will be set to 0x100000
-
739 # for big kernels
-
740 .word __KERNEL_CS
c.
在./linux-2.4.12/arch/i386/boot/setup.S中setup在运行期间动态的改变了这个跳转指令的值
-
549 rmodeswtch_end:
-
550 # we get the code32 start address and modify the below 'jmpi'
-
551 # (loader may have changed it)
-
552 movl %cs:code32_start, %eax
-
553 movl %eax, %cs:code32
66 ea 00 10 00 00 ljmpw $0x0,$0x1000
66 ea 00 00 10 00 ljmpw $0x0,$0x100000
jmpf 0x0010:00100000 ; 66ea000010001000
阅读(2888) | 评论(0) | 转发(0) |