我详细解析了bootsect.s,同时阅读了setup.s。其中bootsect.s存放于磁盘的主引导扇区,bios-startup程序 加载该程序(bootsect.s)到内存0x700处,并由此执行bootsect.s来引导Linux kernel。在bootsect.s中加载setup.s至内存中,并在执行完它自身后,jump跳至刚刚已读入的setup部份,继续执行。
Linux Kernel Image 生成过程: 一. 引导扇区汇编代码bootsect.s被预处理成bbootsect.s或bootsect.s(无论有无D_BIG_KERNEL),当然这取决于编译 目标是bzImage还是bImage。bbootsect.s被汇编,然后被转化成合法的二进制文件,通过调用bbootsect.s。(或者是 bootsect.s被汇编,然后被转化成合法的二进制文件,通过调用bootsect.s。) 二. 启动代码setup.s(include video.s)被预处理成bsetup.s(就bzImage而言),或者是setup.s(就zImage而言)。和bootsect.s一样,不同 的是bzImage使用-D_BIG_KERNEL来标志。结果同样也将被转化成合法的二进制文件,通过调用bsetup.s或setup.s。 三. 进入子目录 arch/i386/boot/compressed 把/usr/src/linux/vmlinux 转变成$tmppiggy.gz(临时文件名,合法的二进制格式)。删除.note 和.comment 文件。 四. gzip -9 <$tmppiggy> $tmppiggy.gz。(gunzip()在main.c中被实现。用于解压缩部份Linux内核,负责解压缩Linux内核的函数还有 decompressed()) 五. 联接(Link)$temppiggy.gz ,使其成为 ELF relocate(ld -r)文件 piggy.0。 六. 编译压缩程序 head.s 和 misc.c(仍然存在于子目录 arch/i386/boot/compressed中。)成ELF的目标文件head.o和misc.o。 七. 联接(Link)所有的目标文件head.o、misc.o、piggy.o成bvmlinux(或者vmlinux,就zImage而言,注意:不要错 误地把它认为是 /usr/src/linux/vmlinux !)。一定要注意到这两者之间的不同:-Ttext 0x1000(调用的是经过压缩的内核镜像vmlinux),-Ttext 0x100000(调用的是未经过压缩的大内核镜像bvmlinux,还有bzImage compression loader 将被high-loaded,调到内存高地址处)。 八. 把bvmlinux转化成合法的二进制文件 bvmlinux.out ,删除 .note 和.comment部份。 九. 最后返回 arch/i386/boot 子目录,使用编程工具把bbootsect+bsetup+compressed/bvmlinux.out 编译成bzImage。(也可以使编译成zImage)。
bootsect.s完成如下功能: 一. bootsect.s将它“自己“从被ROM BIOS载入的绝对地址0x7C00处搬到0x9000处,然后利用一个jmpi(jump indirectly)的指令,跳到新位置的jmpi的下一行(go:)去执行。 二. 接着,将其他segment registers包括DS,ES,SS都指向0x9000这个位置,与CS看齐。另外将SP及DX指向一任意位移地址( offset ),这个地址等一下会用来存放磁盘参数表(disk para- meter table )。设置堆栈段,且开始处为0x4000-12。 三. 读入磁盘参数,并建立参数表。 四. 接着利用BIOS中断服务int 13h的第0号功能,重置磁盘控制器,使得刚才的设定发挥功能。 五. 完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup程序,也就是setup.S,此读入动作是利用BIOS中断 服务int 13h的第2号功能。setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存中紧邻着bootsect 所在的位置。待setup的image读入内存后,利用BIOS中断服务int 13h的第8号功能读取目前磁盘的参数。 六. 再来,就要读入真正linux的kernel了,也就是你可以在linux的根目录下看到的“vmlinuz“ 。在读入前,将会先呼叫BIOS中断服务int 10h 的第3号功能,读取游标位置,之后再呼叫BIOS 中断服务int 10h的第13h号功能,在萤幕上输出字串“Loading“,这个字串在boot linux时都会首先被看到。 七. 接下来做的事是检查root device,之后就仿照一开始的方法,利用indirectjump 跳至刚刚已读入的setup.s部份。
源代码解析如下:
! ! bootsect.s Copyright (C) 1991, 1992 Linus Torvalds ! modified by Drew Eckhardt ! modified by Bruce Evans (bde) ! ! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves ! itself out of the way to address 0x90000, and jumps there. ! ! bde - should not jump blindly, there may be systems with only 512K low ! memory. Use int 0x12 to get the top of memory, etc. ! ! It then loads ‘setup‘ directly after itself (0x90200), and the system ! at 0x10000, using BIOS interrupts. ! ! NOTE! currently system is at most (8*65536-4096) bytes long. This should ! be no problem, even in the future. I want to keep it simple. This 508 kB ! kernel size should be enough, especially as this doesn‘t contain the ! buffer cache as in minix (and especially now that the kernel is ! compressed :-) ! ! The loader has been made as simple as possible, and continuous ! read errors will result in a unbreakable loop. Reboot by hand. It ! loads pretty fast by getting whole tracks at a time whenever possible.
#include /* for CONFIG_ROOT_RDONLY */ #include
.text
!The values of DEF_INITSEG, DEF_SETUPSEG, DEF_SYSSEG, DEF_SYSSIZE are taken !from include/asm/boot.h
! /*Don‘t touch these ,unless you really know what you‘re doing*/ ! #define DEF_INITSEG 0x9000 ! #define DEF_SETUPSEG 0x9020 ! #define DEF_SYSSIZE 0x7F00 ! #define DEF_SYSSEG 0x1000
SETUPSECS = 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: number of 16-byte clicks ! to be loaded
! ROOT_DEV & SWAP_DEV are now written by “build“. ROOT_DEV = 0 SWAP_DEV = 0 #ifndef SVGA_MODE #define SVGA_MODE ASK_VGA #endif #ifndef RAMDISK #define RAMDISK 0 #endif #ifndef CONFIG_ROOT_RDONLY #define CONFIG_ROOT_RDONLY 1 #endif
! ld86 requires an entry symbol. This may as well be the usual one. .globl _main _main: #if 0 /* hook for debugger, harmless unless BIOS is fussy (old HP) */ int 3 #endif mov ax,#BOOTSEG mov ds,ax mov ax,#INITSEG mov es,ax mov cx,#256 sub si,si sub di,di cld rep movsw jmpi go,INITSEG
!The lines 66--76 move the bootsector code from address 0x7C00 to 0x90000. !This is achieved by: ! 1. set ds:si to $BOOTSEG:0 (0x07C0:0 = 0x07C00) ! 2. set es:di to $INITSEG:0 (0x9000:0 = 0x90000) ! 3. set the number of 16bit words in %cx (256 words = 512 bytes = 1 sector) ! 4. clear DF (direction) flag in EFLAGS to auto-increment address (cld) ! 5. go ahead and copy 512 bytes (rep movsw)
! ax and es already contain INITSEG
go: mov di,#0x4000-12 ! 0x4000 is arbitrary value >= length of ! bootsect + length of setup + room for stack ! 12 is disk parm size
! 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.
mov ds,ax ! The value of ax is INITSEG mov ss,ax ! put stack at INITSEG:0x4000-12. mov sp,di /* * 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: ds=es=ss=cs - INITSEG, * fs = 0, gs is unused. */
! cx contains 0 from rep movsw above
mov fs,cx mov bx,#0x78 ! fs:bx is parameter table address push ds seg fs lds si,(bx) ! ds:si is source; ! The line equal: lds si,fs:[bx]
mov cl,#6 ! copy 12 bytes cld push di
rep movsw ! movs可以把由(SI)指向的数据段中的一个字 ! 传送到由(DI)指向的附加段中的一个字中去. !The lines 120--132 possibily copy the parameter table to ram 0x4000-12 pop di pop ds
movb 4(di),*36 ! patch sector count
seg fs mov (bx),di seg fs mov 2(bx),es
! load the setup-sectors directly after the bootblock. ! Note that ‘es‘ is already set up. ! Also cx is 0 from rep movsw above.
load_setup: xor ah,ah ! reset FDC xor dl,dl int 0x13
! 完成重置磁盘控制器之后,bootsect就从磁盘上读入紧邻着bootsect的setup
!程序,也就是setup.S,此读入动作是利用BIOS中断服务int 13h的第2号功能。
!程序第167-173行。
!setup的image将会读入至程序所指定的内存绝对地址0x90200处,也就是在内存
!中紧邻着bootsect 所在的位置。待setup的image读入内存后,利用BIOS中断服
!务int 13h的第8号功能读取目前磁盘的参数。(194-196)
xor dx, dx ! drive 0, head 0 mov cl,#0x02 ! sector 2, track 0 mov bx,#0x0200 ! address = 512, in INITSEG mov ah,#0x02 ! service 2, nr of sectors mov al,setup_sects ! (assume all on head 0, track 0) ! SETUPSECS = 4 which is defined at line 39 ! default nr of setup-sectors int 0x13 ! read it jnc ok_load_setup ! ok - continue 进位为0则转移
push ax ! dump error code call print_nl mov bp, sp call print_hex pop ax
jmp load_setup
ok_load_setup:
! Get disk drive parameters, specifically nr of sectors/track
#if 0
! bde - the Phoenix BIOS manual says function 0x08 only works for fixed ! disks. It doesn‘t work for one of my BIOS‘s (1987 Award). It was ! fatal not to check the error code.
xor dl,dl mov ah,#0x08 ! AH=8 is get drive parameters int 0x13 xor ch,ch #else
! 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.
mov si,#disksizes ! table of sizes to try
probe_loop: lodsb ! 该指令把由(SI)指定的数据段中某单元的内容送 ! 到AL或AX中,并根据方向标志及数据类型修改SI的内容. cbw ! extend to word mov sectors, ax cmp si,#disksizes+4 jae got_sectors ! if all else fails, try 9 ! jae:above or equal 0 xchg ax, cx ! cx = track and sector xor dx, dx ! drive 0, head 0 xor bl, bl mov bh,setup_sects inc bh shl bh,#1 ! address after setup (es = cs) mov ax,#0x0201 ! service 2, 1 sector int 0x13 jc probe_loop ! try next value
#endif
got_sectors:
! Restore es
mov ax,#INITSEG mov es,ax
! Print some inane message
mov ah,#0x03 ! read cursor pos xor bh,bh int 0x10
mov cx,#9 mov bx,#0x0007 ! page 0, attribute 7 (normal) mov bp,#msg1 mov ax,#0x1301 ! write string, move cursor int 0x10
! ok, we‘ve written the message, now ! we want to load the system (at 0x10000)
mov ax,#SYSSEG mov es,ax ! segment of 0x010000 call read_it 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.
seg cs mov ax,root_dev or ax,ax jne root_defined seg cs mov bx,sectors mov ax,#0x0208 ! /dev/ps0 - 1.2Mb cmp bx,#15 je root_defined mov al,#0x1c ! /dev/PS0 - 1.44Mb cmp bx,#18 je root_defined mov al,#0x20 ! /dev/fd0H2880 - 2.88Mb cmp bx,#36 je root_defined mov al,#0 ! /dev/fd0 - autodetect root_defined: seg cs mov root_dev,ax
! after that (everything loaded), we jump to ! the setup-routine loaded directly after ! the bootblock:
jmpi 0,SETUPSEG ! 开始执行setup.s
! This routine loads the system at address 0x10000, making sure ! no 64kB boundaries are crossed. We try to load it as fast as ! possible, loading whole tracks whenever we can. ! ! in: es - starting address segment (normally 0x1000) ! sread: .word 0 ! sectors read of current track head: .word 0 ! current head track: .word 0 ! current track
read_it: mov al,setup_sects inc al mov sread,al mov ax,es test ax,#0x0fff die: jne die ! es must be at 64kB boundary xor bx,bx ! bx is starting address within segment rp_read: #ifdef __BIG_KERNEL__ #define CALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220 ! call far * bootsect_kludge ! NOTE: as86 can‘t assemble this CALL_HIGHLOAD_KLUDGE ! this is within setup.S #else mov ax,es sub ax,#SYSSEG #endif cmp ax,syssize ! have we loaded all yet? jbe ok1_read ret ok1_read: mov ax,sectors sub ax,sread mov cx,ax shl cx,#9 add cx,bx jnc ok2_read je ok2_read xor ax,ax sub ax,bx shr ax,#9 ok2_read: call read_track mov cx,ax add ax,sread cmp ax,sectors jne ok3_read mov ax,#1 sub ax,head jne ok4_read inc track ok4_read: mov head,ax xor ax,ax ok3_read: mov sread,ax shl cx,#9 add bx,cx jnc rp_read !rp_read is in 303. mov ax,es add ah,#0x10 mov es,ax xor bx,bx jmp rp_read
read_track: pusha pusha mov ax, #0xe2e ! loading... message 2e = . mov bx, #7 ! BL=前景色 AL=字符 int 0x10 popa
mov dx,track ! track : current track mov cx,sread ! sectors read of current track inc cx mov ch,dl mov dx,head ! current head mov dh,dl and dx,#0x0100 mov ah,#2
push dx ! save for error dump push cx push bx push ax
int 0x13 jc bad_rt add sp, #8 popa ret
bad_rt: push ax ! save error code call print_all ! ah = error, al = read
xor ah,ah ! 软盘系统复位 xor dl,dl int 0x13
add sp, #10 ! popa 会影响sp的值 popa jmp read_track
/* * print_all is for debugging purposes. * It will print out all of the registers. The assumption is that this is * called from a routine, with a stack frame like * dx * cx * bx * ax * error * ret <- sp * */
print_all: mov cx, #5 ! error code + 4 registers mov bp, sp
print_loop: push cx ! save count left call print_nl ! nl for readability
cmp cl, #5 jae no_reg ! see if register name is needed
mov ax, #0xe05 + ‘A - 1 sub al, cl int 0x10
mov al, #‘X int 0x10
mov al, #‘: int 0x10
no_reg: add bp, #2 ! next register call print_hex ! print it pop cx loop print_loop ret
print_nl: mov ax, #0xe0d ! CR int 0x10 mov al, #0xa ! LF int 0x10 ret
/* * print_hex is for debugging purposes, and prints the word * pointed to by ss:bp in hexadecimal. */
print_hex: mov cx, #4 ! 4 hex digits mov dx, (bp) ! load word into dx print_digit: rol dx, #4 ! rotate so that lowest 4 bits are used mov ax, #0xe0f ! ah = request, al = mask for nybble and al, dl add al, #0x90 ! convert al to ASCII hex (four instructions) daa adc al, #0x40 daa int 0x10 loop print_digit ret
/* * This procedure turns off the floppy drive motor, so * that we enter the kernel in a known state, and * don‘t have to worry about it later. */ kill_motor: push dx mov dx,#0x3f2 ! outb 指令使用短指令 xor al, al outb pop dx ret
sectors: .word 0
disksizes: .byte 36,18,15,9
msg1: .byte 13,10 .ascii “Loading“
.org 497 setup_sects: .byte SETUPSECS root_flags: .word CONFIG_ROOT_RDONLY syssize: .word SYSSIZE swap_dev: .word SWAP_DEV ram_size: .word RAMDISK vid_mode: .word SVGA_MODE root_dev: .word ROOT_DEV boot_flag: .word 0xAA55
在程序bootsect.s执行完以后(setup.s的前4个扇区的内容已被读入),setup.s紧接着开始执行。它负责从BIOS中获 取系统信息,并且将它们存放到内存中适当的地方。它完成如下一些主要功能: 一. 检测setup.s的代码是否已完全被读入,如果没有的话,则查找其余部分代码。并将它自己的其余部分移动到内存中紧邻着先前被读入的4个扇区的内容后 面。 二. 检测键盘、显示适配器、PS/2设备、Micro Channel(mca) bus,获取内存长度(以kb计算)。 三. 检查系统是否已被移动到了正确的地方,假如代码没有被准确移动到0x90000这个地方,我们则需要把代码移动到那个地方。当然在这以前,首先得检验我们 调用的是否是bing-kernel。
程序大部分代码解析如下: ! ! setup.S Copyright (C) 1991, 1992 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. ! ! Move PS/2 aux init code to psaux.c ! ([email protected]) 03Oct92 ! ! some changes and additional features by Christoph Niemann, ! March 1993/June 1994 ([email protected]) ! ! add APM BIOS checking by Stephen Rothwell, May 1994 ! ([email protected]) ! ! High load stuff, initrd support and position independency ! by Hans Lermen & Werner Almesberger, February 1996 ! , ! ! Video handling moved to video.S by Martin Mares, March 1996 ! ! ! Extended memory detection scheme retwiddled by [email protected] (david ! parsons) to avoid loadlin confusion, July 1997 !
#define __ASSEMBLY__ #include #include #include #include #include
! 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
.globl begtext, begdata, begbss, endtext, enddata, endbss .text begtext: .data begdata: .bss begbss: .text
entry start start: jmp start_of_setup ! ------------------------ start of header -------------------------------- ! ! SETUP-header, must start at CS:2 (old 0x9020:2) ! .ascii “HdrS“ ! Signature for SETUP-header .word 0x0201 ! Version number of header format ! (must be >= 0x0105 ! 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 ! note: above part of header is compatible with loadlin-1.5 (header v1.5), ! must not change it
type_of_loader: .byte 0 ! = 0, old one (LILO, Loadlin, ! Bootlin, SYSLX, bootsect...) ! else it is set by the loader: ! 0xTV: T=0 for LILO ! T=1 for Loadlin ! T=2 for bootsect-loader ! T=3 for SYSLX ! T=4 for ETHERBOOT ! V = version loadflags: ! flags, unused bits must be zero (RFU) LOADED_HIGH = 1 ! bit within loadflags, ! if set, then 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 0x00 #else .byte LOADED_HIGH #endif
setup_move_size: .word 0x8000 ! size to move, when we (setup) are not ! loaded at 0x90000. We will move ourselves ! to 0x90000 then just before jumping into ! the kernel. However, only the loader ! know how much of 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 (or kernel generator) puts ! the 32-bit address were it loaded the image. ! This only will be interpreted by the kernel. ramdisk_size: .long 0 ! its size in bytes bootsect_kludge: .word bootsect_helper,SETUPSEG heap_end_ptr: .word modelist+1024 ! space from here (exclusive) down to ! end of setup code can be used by setup ! for local heap purposes. ! ------------------------ end of header ----------------------------------
start_of_setup: ! Bootlin depends on this being done early mov ax,#0x01500 mov dl,#0x81 int 0x13
#ifdef SAFE_RESET_DISK_CONTROLLER ! Reset the disk controller. mov ax,#0x0000 mov dl,#0x80 int 0x13 #endif
! set DS=CS, we know that SETUPSEG == CS at this point mov ax,cs ! aka #SETUPSEG mov ds,ax
!0xAA55和0x5A5A是用来确保LILO正确引导setup.s的两个标志 !检察setup代码结尾处的标志是否为AA55或5A5A, !假如是,则跳到good_sig1处;否则,跳到bad_sig处,查找setup.s的剩余部分代码, !并且将其移动到内存中0x1000处。 cmp setup_sig1,#SIG1 jne bad_sig cmp setup_sig2,#SIG2 jne bad_sig jmp good_sig1
! Routine to print ASCIIz string at DS:SI
prtstr: lodsb !lodsb是从串取指令(AL<--(SI)),(SI)<--(SI)+/-1 and al,al jz fin !显示完所有需要显示的内容后,则返回调用处。 call prtchr jmp prtstr fin: ret
! Space printing
prtsp2: call prtspc ! Print double space prtspc: mov al,#0x20 ! Print single space (fall-thru!)
! Part of above routine, this one just prints ASCII al ! 利用int 0x10(AH=0x0E)在屏幕上以BL中得值为背景色显示存储在AL中的字符。 prtchr: push ax push cx xor bh,bh mov cx,#0x01 mov ah,#0x0e int 0x10 pop cx pop ax ret
beep: mov al,#0x07 jmp prtchr
no_sig_mess: .ascii “No setup signature found ...“ db 0x00
good_sig1: jmp good_sig
!我们现在必须得找到setup代码、数据的剩余部分。 bad_sig: mov ax,cs ! aka #SETUPSEG(cs的值为#SETUPSEG=0x9020) sub ax,#DELTA_INITSEG ! aka #INITSEG(#INITSEG=0x9000) mov ds,ax ! 设置ds=0x9000,即bootsect.s被载入的地方 xor bh,bh mov bl,[497] ! get setup sects from boot sector sub bx,#4 ! LILO loads 4 sectors of setup !注意:此时bx中存放的是存储还未读取的setup代码的扇区数。 shl bx,#8 ! convert to words mov cx,bx !cx通常用作计数器。 shr bx,#3 ! convert to segment add bx,#SYSSEG !计算出存放setup.s剩余部分代码、数据的地址。 seg cs mov start_sys_seg,bx
!把setup.s剩余部分的代码、数据移动到这个地方。 mov di,#2048 ! four sectors loaded by LILO sub si,si mov ax,cs ! aka #SETUPSEG=0x9020 mov es,ax mov ax,#SYSSEG mov ds,ax !ax=0x1000 rep movsw !movs(movsw、movsb)可以把由(SI)指向的数据段中的一个字(或字节)传送到由(DI) !指向的附加段中的一个字(字节)中去,同时根据方向标志及数据格式(字或字节)对SI和 !DI进行修改。但与REP联用时,则可将数据段中的整个字符串传送到附加段中去。
mov ax,cs ! aka #SETUPSEG mov ds,ax cmp setup_sig1,#SIG1 jne no_sig ! 同142--149 cmp setup_sig2,#SIG2 jne no_sig jmp good_sig
no_sig: lea si,no_sig_mess call prtstr no_sig_loop: jmp no_sig_loop
good_sig: mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov ds,ax !ds=0x9000
! check if an old loader tries to load a big-kernel seg cs test byte ptr loadflags,#LOADED_HIGH ! Have we a big kernel? jz loader_ok ! NO, no danger even for old loaders ! YES, we have a big-kernel seg cs cmp byte ptr type_of_loader,#0 ! Have we one of the new loaders? ! 参见77--85,#0:采用LILO引导, !在Romed Linux中,我们使用bootsect.s引导, !则应改为cmp byte ptr type_of_loader,#2 !或者,不要更改下面内容,直接跳到loader_ok处。 jnz loader_ok ! YES, OK ! NO, we have an old loader, must give up push cs pop ds lea si,loader_panic_mess call prtstr jmp no_sig_loop loader_panic_mess: .ascii “Wrong loader: giving up.“ db 0
loader_ok: ! Get memory size (extended mem, kB)
#ifndef STANDARD_MEMORY_BIOS_CALL push ebx
xor ebx,ebx ! preload new memory slot with 0k mov [0x1e0], ebx
mov ax,#0xe801 int 0x15 jc oldstylemem
! Memory size is in 1 k 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.)
and ebx, #0xffff ! clear sign extend shl ebx, 6 ! and go from 64k to 1k chunks mov [0x1e0],ebx ! store extended memory size
and eax, #0xffff ! clear sign extend add [0x1e0],eax ! and add lower memory into total size.
! and fall into the old memory detection code to populate the ! compatibility slot.
oldstylemem: pop ebx #else mov dword ptr [0x1e0], #0 #endif mov ah,#0x88 int 0x15 mov [2],ax
! Set the keyboard repeat rate to the max
mov ax,#0x0305 xor bx,bx ! clear bx int 0x16
! Check for video adapter and its parameters and allow the ! user to browse video modes.
call video ! NOTE: we need DS pointing to boot sector !The function video is achieved in video.s ! Get hd0 data
xor ax,ax ! clear ax mov ds,ax lds si,[4*0x41] mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG push ax mov es,ax mov di,#0x0080 mov cx,#0x10 push cx cld rep movsb
! Get hd1 data
xor ax,ax ! clear ax mov ds,ax lds si,[4*0x46] pop cx pop es mov di,#0x0090 rep movsb
! 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,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov es,ax mov di,#0x0090 mov cx,#0x10 xor ax,ax ! clear ax cld rep stosb is_disk1:
! check for Micro Channel (MCA) bus mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov ds,ax mov ds,ax xor ax,ax mov [0xa0], ax ! set table length to 0 mov ah, #0xc0 stc int 0x15 ! puts feature table at es:bx jc no_mca push ds mov ax,es mov ds,ax mov ax,cs ! aka #SETUPSEG sub ax, #DELTA_INITSEG ! aka #INITSEG mov es,ax mov si,bx mov di,#0xa0 mov cx,(si) add cx,#2 ! table length is a short cmp cx,#0x10 jc sysdesc_ok mov cx,#0x10 ! we keep only first 16 bytes sysdesc_ok: rep movsb pop ds
no_mca:
! Check for PS/2 pointing device
mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG mov ds,ax mov [0x1ff],#0 ! default is no pointing device int 0x11 ! int 0x11: equipment determination test al,#0x04 ! check if pointing device installed jz no_psmouse mov [0x1ff],#0xaa ! device present no_psmouse:
#ifdef CONFIG_APM ! check for APM BIOS ! NOTE: DS is pointing to the boot sector ! mov [64],#0 ! version == 0 means no APM BIOS
mov ax,#0x05300 ! APM BIOS installation check xor bx,bx int 0x15 jc done_apm_bios ! error -> no APM BIOS
cmp bx,#0x0504d ! check for “PM“ signature jne done_apm_bios ! no signature -> no APM BIOS
and cx,#0x02 ! Is 32 bit supported? je done_apm_bios ! no ...
mov ax,#0x05304 ! Disconnect first just in case xor bx,bx int 0x15 ! ignore return code
mov ax,#0x05303 ! 32 bit connect xor bx,bx int 0x15 jc no_32_apm_bios ! error
mov [66],ax ! BIOS code segment mov [68],ebx ! BIOS entry point offset mov [72],cx ! BIOS 16 bit code segment mov [74],dx ! BIOS data segment mov [78],esi ! BIOS code segment length mov [82],di ! BIOS data segment length ! ! Redo the installation check as the 32 bit connect ! modifies the flags returned on some BIOSs ! mov ax,#0x05300 ! APM BIOS installation check xor bx,bx int 0x15 jc apm_disconnect ! error -> should not happen, tidy up
cmp bx,#0x0504d ! check for “PM“ signature jne apm_disconnect ! no signature -> should not happen, tidy up
mov [64],ax ! record the APM BIOS version mov [76],cx ! and flags jmp done_apm_bios
apm_disconnect: mov ax,#0x05304 ! Disconnect xor bx,bx int 0x15 ! ignore return code jmp done_apm_bios
no_32_apm_bios: and [76], #0xfffd ! remove 32 bit support bit
done_apm_bios: #endif
! Now we want to move to protected mode ...
seg cs cmp realmode_swtch,#0 jz rmodeswtch_normal seg cs callf far * realmode_swtch jmp rmodeswtch_end rmodeswtch_normal: push cs call default_switch rmodeswtch_end:
! we get the code32 start address and modify the below ‘jmpi‘ ! (loader may have changed it) seg cs mov eax,code32_start seg cs mov code32,eax
!一旦这个数据不再被需要,它将被覆盖(overwrite),通过把整个内核景象从0x10000移动到 !0x1000(当然是物理地址)。这一功能将由setup.s来实现,setup.s把环境设置成保护模式(641--661), !同时跳转到0x1000处,那里是整个压缩内核的开端(head.s、misc.c)。设立堆栈,并且calls !decompress_kernel(),负责把内核解压缩到0x100000处,然后跳转到该地址。 ! Now we move the system to its rightful place ! ...but we check, if we have a big-kernel. ! in this case we *must* not move it ... seg cs test byte ptr loadflags,#LOADED_HIGH jz do_move0 ! we have a normal low loaded zImage ! we have a high loaded big kernel jmp end_move ! ... and we skip moving
do_move0: mov ax,#0x100 ! start of destination segment mov bp,cs ! aka #SETUPSEG sub bp,#DELTA_INITSEG ! aka #INITSEG seg cs mov bx,start_sys_seg ! start of source segment ! start_sys_seg:0x1000,there the system will be loaded. cld ! ‘direction‘=0, movs moves forward do_move: mov es,ax ! destination segment inc ah ! instead of add ax,#0x100 mov ds,bx ! source segment add bx,#0x100 sub di,di sub si,si mov cx,#0x800 rep movsw ! 共移动了34k数据。 cmp bx,bp ! we assume start_sys_seg > 0x200, ! so we will perhaps read one page more then ! needed, but never overwrite INITSEG because ! destination is minimum one page below source jb do_move
! then we load the segment descriptors
end_move: mov ax,cs ! aka #SETUPSEG ! right, forgot this at first. didn‘t work :-) mov ds,ax !cs=ds,cs、ds不一定等于0x9020。
! If we have our code not at 0x90000, we need to move it there now. ! We also then need to move the parameters behind it (command line) ! Because we would overwrite the code on the current IP, we move ! it in two steps, jumping high after the first one. mov ax,cs cmp ax,#SETUPSEG je end_move_self cli ! make sure we really have interrupts disabled ! ! because after this the stack should not be used sub ax,#DELTA_INITSEG ! aka #INITSEG mov dx,ss cmp dx,ax jb move_self_1 add dx,#INITSEG sub dx,ax ! this will be SS after the move move_self_1: mov ds,ax mov ax,#INITSEG ! real INITSEG mov es,ax seg cs mov cx,setup_move_size std ! we have to move up, so we use direction down ! because the areas may overlap mov di,cx dec di mov si,di sub cx,#move_self_here+0x200 rep movsb jmpi move_self_here,SETUPSEG ! jump to our final place move_self_here: mov cx,#move_self_here+0x200 ! What‘s meaning????? rep movsb mov ax,#SETUPSEG mov ds,ax mov ss,dx ! now we are at the right place end_move_self:
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
! 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 is the int 0x1f vector, ! which should be safe to use; any *unused* memory location < 0xfff0 ! should work here.
#define TEST_ADDR 0x7c
push ds xor ax,ax ! segment 0x0000 mov ds,ax dec ax ! segment 0xffff (HMA) mov gs,ax mov bx,[TEST_ADDR] ! we want to restore the value later a20_wait: inc ax mov [TEST_ADDR],ax seg gs cmp ax,[TEST_ADDR+0x10] je a20_wait ! loop until no longer aliased mov [TEST_ADDR],bx ! restore original value pop ds
! make sure any possible coprocessor is properly reset..
xor ax,ax out #0xf0,al call delay out #0xf1,al call delay
! 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.
mov al,#0x11 ! initialization sequence out #0x20,al ! send it to 8259A-1 call delay out #0xA0,al ! and to 8259A-2 call delay mov al,#0x20 ! start of hardware int‘s (0x20) out #0x21,al call delay mov al,#0x28 ! start of hardware int‘s 2 (0x28) out #0xA1,al call delay mov al,#0x04 ! 8259-1 is master out #0x21,al call delay mov al,#0x02 ! 8259-2 is slave out #0xA1,al call delay mov al,#0x01 ! 8086 mode for both out #0x21,al call delay out #0xA1,al call delay mov al,#0xFF ! mask off all interrupts for now out #0xA1,al call delay mov al,#0xFB ! mask all irq‘s but irq2 which out #0x21,al ! 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. ! mov ax,#1 ! protected mode (PE) bit lmsw ax ! This is it! jmp flush_instr flush_instr: xor bx,bx ! Flag to indicate a boot
! 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) db 0x66,0xea ! prefix + jmpi-opcode code32: dd 0x1000 ! will be set to 0x100000 for big kernels dw __KERNEL_CS
kernel_version: .ascii UTS_RELEASE .ascii “ (“ .ascii LINUX_COMPILE_BY .ascii “@“ .ascii LINUX_COMPILE_HOST .ascii “) “ .ascii UTS_VERSION db 0
! This is the default real mode switch routine. ! to be called just before protected mode transition
default_switch: cli ! no interrupts allowed ! mov al,#0x80 ! disable NMI for the bootup sequence out #0x70,al retf
! 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. ! !由以上的注释可知,下面代码对于Romed Linux是很重要的,因为我采用了LINUX的simple ! bootsect loader。 bootsect_helper: seg cs cmp word ptr bootsect_es,#0 jnz bootsect_second seg cs mov byte ptr type_of_loader,#0x20 mov ax,es shr ax,#4 seg cs mov byte ptr bootsect_src_base+2,ah mov ax,es seg cs mov bootsect_es,ax sub ax,#SYSSEG retf ! nothing else to do for now bootsect_second: push cx push si push bx test bx,bx ! 64K full ? !BX=0x0,es=0x9000 jne bootsect_ex mov cx,#0x8000 ! full 64K move, INT15 moves words push cs pop es !es=0x9000 INITSEG mov si,#bootsect_gdt mov ax,#0x8700 int 0x15 !es:bx=数据传输区地址 jc bootsect_panic ! this, if INT15 fails seg cs mov es,bootsect_es ! we reset es to always point to 0x10000 seg cs inc byte ptr bootsect_dst_base+2 bootsect_ex: seg cs mov ah, byte ptr bootsect_dst_base+2 shl ah,4 ! we now have the number of moved frames in ax xor al,al pop bx pop si pop cx retf
bootsect_gdt: .word 0,0,0,0 .word 0,0,0,0 bootsect_src: .word 0xffff bootsect_src_base: .byte 0,0,1 ! base = 0x010000 .byte 0x93 ! typbyte .word 0 ! limit16,base24 =0 bootsect_dst: .word 0xffff bootsect_dst_base: .byte 0,0,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: push cs pop ds cld lea si,bootsect_panic_mess call prtstr bootsect_panic_loop: jmp bootsect_panic_loop bootsect_panic_mess: .ascii “INT15 refuses to access high memory. Giving up.“ db 0
! 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...
empty_8042: push ecx mov ecx,#0xFFFFFF
empty_8042_loop: dec ecx jz empty_8042_end_loop
call delay in al,#0x64 ! 8042 status port test al,#1 ! output buffer? jz no_output call delay in al,#0x60 ! read it jmp empty_8042_loop no_output: test al,#2 ! is input buffer full? jnz empty_8042_loop ! yes - loop empty_8042_end_loop: pop ecx ret
! ! Read the CMOS clock. Return the seconds in al ! gettime: push cx mov ah,#0x02 int 0x1a mov al,dh ! dh contains the seconds and al,#0x0f mov ah,dh mov cl,#0x04 shr ah,cl aad pop cx ret
! ! Delay is needed after doing I/O ! delay: .word 0x00eb ! jmp $+2 ret
! ! Descriptor tables !
gdt: .word 0,0,0,0 ! dummy
.word 0,0,0,0 ! unused
.word 0xFFFF ! 4Gb - (0x100000*0x1000 = 4Gb) .word 0x0000 ! base address=0 .word 0x9A00 ! code read/exec .word 0x00CF ! granularity=4096, 386 (+5th nibble of limit)
.word 0xFFFF ! 4Gb - (0x100000*0x1000 = 4Gb) .word 0x0000 ! 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 0x800 ! gdt limit=2048, 256 GDT entries .word 512+gdt,0x9 ! gdt base = 0X9xxxx
! ! Include video setup & detection code !
#include “video.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: