Linux 引导解析(使用bootsect.s+setup.s方式)

我详细解析了bootsect.s,同时阅读了setup.s。其中bootsect.s存放于磁盘的主引导扇区,bios-startup程序加载该程序(bootsect.s)到内存0x700处,并由此执行bootsect.s来引导Linuxkernel。在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(includevideo.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 !)。一定要注意到这两者之间的不同:-Ttext0x1000(调用的是经过压缩的内核镜像vmlinux),-Ttext0x100000(调用的是未经过压缩的大内核镜像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将它“自己“从被ROMBIOS载入的绝对地址0x7C00处搬到0x9000处,然后利用一个jmpi(jumpindirectly)的指令,跳到新位置的jmpi的下一行(go:)去执行。   二. 接着,将其他segmentregisters包括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 中断服务int10h的第13h号功能,在萤幕上输出字串“Loading“,这个字串在boot linux时都会首先被看到。   七.接下来做的事是检查root device,之后就仿照一开始的方法,利用indirectjump 跳至刚刚已读入的setup.s部份。   源代码解析如下: ! ! bootsect.s Copyright (C) 1991, 1992 Linus Torvalds ! modifiedby Drew Eckhardt ! modified by Bruce Evans (bde) ! ! bootsect.s isloaded at 0x7c00 by the bios-startup routines, and moves ! itself outof the way to address 0x90000, and jumps there. ! ! bde – should notjump blindly, there may be systems with only 512K low ! memory. Use int0x12 to get the top of memory, etc. ! ! It then loads ‘setup‘ directlyafter itself (0x90200), and the system ! at 0x10000, using BIOSinterrupts. ! ! NOTE! currently system is at most (8*65536-4096) byteslong. This should ! be no problem, even in the future. I want to keepit simple. This 508 kB ! kernel size should be enough, especially asthis doesn‘t contain the ! buffer cache as in minix (and especially nowthat the kernel is ! compressed 🙂 ! ! The loader has been made assimple as possible, and continuous ! read errors will result in aunbreakable loop. Reboot by hand. It ! loads pretty fast by gettingwhole 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 ! #defineDEF_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 boothere – out of the way SETUPSEG = DEF_SETUPSEG ! setup starts hereSYSSEG = 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 = 0SWAP_DEV = 0 #ifndef SVGA_MODE #define SVGA_MODE ASK_VGA #endif #ifndefRAMDISK #define RAMDISK 0 #endif #ifndef CONFIG_ROOT_RDONLY #defineCONFIG_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 isfussy (old HP) */ int 3 #endif mov ax,#BOOTSEG mov ds,ax movax,#INITSEG mov es,ax mov cx,#256 sub si,si sub di,di cld rep movswjmpi go,INITSEG !The lines 66–76 move the bootsector code from address 0x7C00 to0x90000. !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 thenumber 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 ofmemory. Also ! my BIOS can be configured to put the wini drive tablesin high memory ! instead of in the vector table. The old stack mighthave clobbered the ! drive table. mov ds,ax ! The value of ax is INITSEG mov ss,ax ! put stack atINITSEG:0x4000-12. mov sp,di /* * Many BIOS‘s default disk parametertables will not * recognize multi-sector reads beyond the maximumsector number * specified in the default diskette parameter tables -this may * mean 7 sectors in some cases. * * Since single sector readsare slow and out of the question, * we must take care of this bycreating new parameter tables * (for the first disk) in RAM. We willset the maximum sector * count to 36 – the most we will encounter on anED 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 dsseg 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)指向的附加段中的一个字中去. !Thelines 120–132 possibily copy the parameter table to ram 0x4000-12 popdi 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 movbx,#0x0200 ! address = 512, in INITSEG mov ah,#0x02 ! service 2, nr ofsectors mov al,setup_sects ! (assume all on head 0, track 0) !SETUPSECS = 4 which is defined at line 39 ! default nr of setup-sectorsint 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 forfixed ! disks. It doesn‘t work for one of my BIOS‘s (1987 Award). Itwas ! 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 18can 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 cmpsi,#disksizes+4 jae got_sectors ! if all else fails, try 9 ! jae:aboveor 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 aftersetup (es = cs) mov ax,#0x0201 ! service 2, 1 sector int 0x13 jcprobe_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 movbx,sectors mov ax,#0x0208 ! /dev/ps0 – 1.2Mb cmp bx,#15 je root_definedmov al,#0x1c ! /dev/PS0 – 1.44Mb cmp bx,#18 je root_defined moval,#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 – startingaddress segment (normally 0x1000) ! sread: .word 0 ! sectors read ofcurrent track head: .word 0 ! current head track: .word 0 ! currenttrack read_it: mov al,setup_sects inc al mov sread,al mov ax,es testax,#0x0fff die: jne die ! es must be at 64kB boundary xor bx,bx ! bx isstarting address within segment rp_read: #ifdef __BIG_KERNEL__ #defineCALL_HIGHLOAD_KLUDGE .word 0x1eff,0x220 ! call far * bootsect_kludge !NOTE: as86 can‘t assemble this CALL_HIGHLOAD_KLUDGE ! this is withinsetup.S #else mov ax,es sub ax,#SYSSEG #endif cmp ax,syssize ! have weloaded all yet? jbe ok1_read ret ok1_read: mov ax,sectors sub ax,sreadmov cx,ax shl cx,#9 add cx,bx jnc ok2_read je ok2_read xor ax,ax subax,bx shr ax,#9 ok2_read: call read_track mov cx,ax add ax,sread cmpax,sectors jne ok3_read mov ax,#1 sub ax,head jne ok4_read inc trackok4_read: mov head,ax xor ax,ax ok3_read: mov sread,ax shl cx,#9 addbx,cx jnc rp_read !rp_read is in 303. mov ax,es add ah,#0x10 mov es,axxor 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 ofcurrent track inc cx mov ch,dl mov dx,head ! current head mov dh,dl anddx,#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 allof the registers. The assumption is that this is * called from aroutine, 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 intodx print_digit: rol dx, #4 ! rotate so that lowest 4 bits are used movax, #0xe0f ! ah = request, al = mask for nybble and al, dl add al,#0x90 ! convert al to ASCII hex (four instructions) daa adc al, #0x40daa int 0x10 loop print_digit ret /* * This procedure turns off the floppy drive motor, so * that weenter the kernel in a known state, and * don‘t have to worry about itlater. */ kill_motor: push dx mov dx,#0x3f2 ! outb 指令使用短指令 xor al, aloutb 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: .wordCONFIG_ROOT_RDONLY syssize: .word SYSSIZE swap_dev: .word SWAP_DEVram_size: .word RAMDISK vid_mode: .word SVGA_MODE root_dev: .wordROOT_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. ! bothsetup.s and system has been loaded by the bootblock. ! ! This code asksthe bios for memory/disk/other parameters, and ! puts them in a “safe“place: 0x90000-0x901FF, ie where the ! boot-block used to be. It isthen up to the protected mode ! system to read them from there beforethe area is overwritten ! for buffer-blocks. ! ! Move PS/2 aux initcode to psaux.c ! (troyer@saifr00.cfsat.Honeywell.COM) 03Oct92 ! ! somechanges and additional features by Christoph Niemann, ! March 1993/June1994 (Christoph.Niemann@linux.org) ! ! add APM BIOS checking by StephenRothwell, May 1994 ! (Stephen.Rothwell@canb.auug.org.au) ! ! High loadstuff, initrd support and position independency ! by Hans Lermen &Werner Almesberger, February 1996 ! , ! ! Video handling moved tovideo.S by Martin Mares, March 1996 ! ! ! Extended memory detectionscheme retwiddled by orc@pell.chi.il.us (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 waySYSSEG = DEF_SYSSEG ! 0x1000, system loaded at 0x10000 (65536).SETUPSEG = DEF_SETUPSEG ! 0x9020, this is the current segment ! … andthe 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, muststart at CS:2 (old 0x9020:2) ! .ascii “HdrS“ ! Signature forSETUP-header .word 0x0201 ! Version number of header format ! (must be>= 0x0105 ! else old loadlin-1.5 will fail) realmode_swtch: .word0,0 ! default_switch,SETUPSEG start_sys_seg: .word SYSSEG .wordkernel_version ! pointing to kernel version string ! note: above partof header is compatible with loadlin-1.5 (header v1.5), ! must notchange it type_of_loader: .byte 0 ! = 0, old one (LILO, Loadlin, ! Bootlin,SYSLX, bootsect…) ! else it is set by the loader: ! 0xTV: T=0 forLILO ! T=1 for Loadlin ! T=2 for bootsect-loader ! T=3 for SYSLX ! T=4for ETHERBOOT ! V = version loadflags: ! flags, unused bits must bezero (RFU) LOADED_HIGH = 1 ! bit within loadflags, ! if set, then thekernel is loaded high CAN_USE_HEAP = 0x80 ! if set, the loader also hasset heap_end_ptr ! to tell how much space behind setup.S | can be usedfor 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) arenot ! loaded at 0x90000. We will move ourselves ! to 0x90000 then justbefore jumping into ! the kernel. However, only the loader ! know howmuch of data behind us also needs ! to be loaded. code32_start: ! hereloaders can put a different ! start address for 32-bit code. #ifndef__BIG_KERNEL__ .long 0x1000 ! 0x1000 = default for zImage #else .long0x100000 ! 0x100000 = default for big kernel #endif ramdisk_image:.long 0 ! address of loaded ramdisk image ! Here the loader (or kernelgenerator) puts ! the 32-bit address were it loaded the image. ! Thisonly will be interpreted by the kernel. ramdisk_size: .long 0 ! itssize in bytes bootsect_kludge: .word bootsect_helper,SETUPSEGheap_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 jmpgood_sig1 ! Routine to print ASCIIz string at DS:SI prtstr: lodsb !lodsb是从串取指令(AL<–(SI)),(SI)<–(SI)+/-1 andal,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 ! 利用int0x10(AH=0x0E)在屏幕上以BL中得值为背景色显示存储在AL中的字符。 prtchr: push ax push cx xorbh,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被载入的地方 xorbh,bh mov bl,[497] ! get setup sects from boot sector sub bx,#4 ! LILOloads 4 sectors of setup !注意:此时bx中存放的是存储还未读取的setup代码的扇区数。 shl bx,#8 !convert to words mov cx,bx !cx通常用作计数器。 shr bx,#3 ! convert to segmentadd bx,#SYSSEG !计算出存放setup.s剩余部分代码、数据的地址。 seg cs mov start_sys_seg,bx !把setup.s剩余部分的代码、数据移动到这个地方。 mov di,#2048 ! four sectors loaded byLILO sub si,si mov ax,cs ! aka #SETUPSEG=0x9020 mov es,ax movax,#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 testbyte 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 cscmp byte ptr type_of_loader,#0 ! Have we one of the new loaders? !参见77–85,#0:采用LILO引导, !在Romed Linux中,我们使用bootsect.s引导, !则应改为cmp byteptr type_of_loader,#2 !或者,不要更改下面内容,直接跳到loader_ok处。 jnz loader_ok ! YES,OK ! NO, we have an old loader, must give up push cs pop ds leasi,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 becausethat‘s what Larry Augustine uses in his ! alternative new memorydetection scheme, and it‘s sensible ! to write everything into the sameplace.) 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 movdi,#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 jeis_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 axcld rep stosb is_disk1: ! check for Micro Channel (MCA) bus mov ax,cs ! aka #SETUPSEG subax,#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 ! putsfeature 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 movsi,bx mov di,#0xa0 mov cx,(si) add cx,#2 ! table length is a short cmpcx,#0x10 jc sysdesc_ok mov cx,#0x10 ! we keep only first 16 bytessysdesc_ok: rep movsb pop ds no_mca: ! Check for PS/2 pointing device mov ax,cs ! aka #SETUPSEG sub ax,#DELTA_INITSEG ! aka #INITSEG movds,ax mov [0x1ff],#0 ! default is no pointing device int 0x11 ! int0x11: equipment determination test al,#0x04 ! check if pointing deviceinstalled 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 pointoffset mov [72],cx ! BIOS 16 bit code segment mov [74],dx ! BIOS datasegment mov [78],esi ! BIOS code segment length mov [82],di ! BIOS datasegment length ! ! Redo the installation check as the 32 bit connect !modifies the flags returned on some BIOSs ! mov ax,#0x05300 ! APM BIOSinstallation 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 calldefault_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 movcode32,eax !一旦这个数据不再被需要,它将被覆盖(overwrite),通过把整个内核景象从0x10000移动到!0x1000(当然是物理地址)。这一功能将由setup.s来实现,setup.s把环境设置成保护模式(641–661),!同时跳转到0x1000处,那里是整个压缩内核的开端(head.s、misc.c)。设立堆栈,并且calls!decompress_kernel(),负责把内核解压缩到0x100000处,然后跳转到该地址。 ! Now we move thesystem to its rightful place ! …but we check, if we have abig-kernel. ! in this case we *must* not move it … seg cs test byteptr loadflags,#LOADED_HIGH jz do_move0 ! we have a normal low loadedzImage ! we have a high loaded big kernel jmp end_move ! … and weskip moving do_move0: mov ax,#0x100 ! start of destination segment mov bp,cs !aka #SETUPSEG sub bp,#DELTA_INITSEG ! aka #INITSEG seg cs movbx,start_sys_seg ! start of source segment ! start_sys_seg:0x1000,therethe system will be loaded. cld ! ‘direction‘=0, movs moves forwarddo_move: mov es,ax ! destination segment inc ah ! instead of addax,#0x100 mov ds,bx ! source segment add bx,#0x100 sub di,di sub si,simov cx,#0x800 rep movsw ! 共移动了34k数据。 cmp bx,bp ! we assumestart_sys_seg > 0x200, ! so we will perhaps read one page more then! needed, but never overwrite INITSEG because ! destination is minimumone 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 intwo steps, jumping high after the first one. mov ax,cs cmp ax,#SETUPSEGje 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 subdx,ax ! this will be SS after the move move_self_1: mov ds,ax movax,#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 mayoverlap mov di,cx dec di mov si,di sub cx,#move_self_here+0x200 repmovsb jmpi move_self_here,SETUPSEG ! jump to our final placemove_self_here: mov cx,#move_self_here+0x200 ! What‘s meaning????? repmovsb mov ax,#SETUPSEG mov ds,ax mov ss,dx ! now we are at the rightplace 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, ! whichshould 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 ! segment0xffff (HMA) mov gs,ax mov bx,[TEST_ADDR] ! we want to restore thevalue later a20_wait: inc ax mov [TEST_ADDR],ax seg gs cmpax,[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 theinterrupts 🙁 ! we put them right after the intel-reserved hardwareinterrupts, at ! int 0x20-0x2F. There they won‘t mess up anything.Sadly IBM really ! messed this up with the original PC, and theyhaven‘t been able to ! rectify it afterwards. Thus the bios putsinterrupts at 0x08-0x0f, ! which is used for the internal hardwareinterrupts as well. We just ! have to reprogram the 8259‘s, and itisn‘t fun. mov al,#0x11 ! initialization sequence out #0x20,al ! send it to8259A-1 call delay out #0xA0,al ! and to 8259A-2 call delay moval,#0x20 ! start of hardware int‘s (0x20) out #0x21,al call delay moval,#0x28 ! start of hardware int‘s 2 (0x28) out #0xA1,al call delay moval,#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 forboth 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 wedon‘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 ! thingsas simple as possible, we do no register set-up or anything, ! we letthe GNU-compiled 32-bit programs do that. We just jump to ! absoluteaddress 0x1000 (or the loader supplied one), ! in 32-bit protectedmode. ! ! Note that the short jump isn‘t strictly needed, althoughthere are ! reasons why it might be a good idea. It won‘t hurt in anycase. ! mov ax,#1 ! protected mode (PE) bit lmsw ax ! This is it! jmpflush_instr flush_instr: xor bx,bx ! Flag to indicate a boot ! NOTE: For high loaded big kernels we need a ! jmpi0x100000,__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 bitfar pointer. (INTeL 80386 Programmer‘s Reference ! Manual, Mixing16-bit and 32-bit code, page 16-6) db 0x66,0xea ! prefix + jmpi-opcodecode32: dd 0x1000 ! will be set to 0x100000 for big kernels dw__KERNEL_CS kernel_version: .ascii UTS_RELEASE .ascii “ (“ .asciiLINUX_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 noplace left in the 512 bytes of the boot sector, ! we must emigrate tocode space here. ! !由以上的注释可知,下面代码对于Romed Linux是很重要的,因为我采用了LINUX的simple! bootsect loader。 bootsect_helper: seg cs cmp word ptr bootsect_es,#0jnz bootsect_second seg cs mov byte ptr type_of_loader,#0x20 mov ax,esshr ax,#4 seg cs mov byte ptr bootsect_src_base+2,ah mov ax,es seg csmov bootsect_es,ax sub ax,#SYSSEG retf ! nothing else to do for nowbootsect_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, INT15moves words push cs pop es !es=0x9000 INITSEG mov si,#bootsect_gdt movax,#0x8700 int 0x15 !es:bx=数据传输区地址 jc bootsect_panic ! this, if INT15fails seg cs mov es,bootsect_es ! we reset es to always point to0x10000 seg cs inc byte ptr bootsect_dst_base+2 bootsect_ex: seg cs movah, byte ptr bootsect_dst_base+2 shl ah,4 ! we now have the number ofmoved 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: .word0xffff bootsect_src_base: .byte 0,0,1 ! base = 0x010000 .byte 0x93 !typbyte .word 0 ! limit16,base24 =0 bootsect_dst: .word 0xffffbootsect_dst_base: .byte 0,0,0×10 ! base = 0x100000 .byte 0x93 !typbyte .word 0 ! limit16,base24 =0 .word 0,0,0,0 ! BIOS CS .word0,0,0,0 ! BIOS DS bootsect_es: .word 0 bootsect_panic: push cs pop ds cld lea si,bootsect_panic_mess callprtstr bootsect_panic_loop: jmp bootsect_panic_loopbootsect_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 delusionsthat 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 ! outputbuffer? jz no_output call delay in al,#0x60 ! read it jmpempty_8042_loop no_output: test al,#2 ! is input buffer full? jnzempty_8042_loop ! yes – loop empty_8042_end_loop: pop ecx ret ! ! Read the CMOS clock. Return the seconds in al ! gettime: pushcx mov ah,#0x02 int 0x1a mov al,dh ! dh contains the seconds andal,#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 ! baseaddress=0 .word 0x9A00 ! code read/exec .word 0x00CF !granularity=4096, 386 (+5th nibble of limit) .word 0xFFFF ! 4Gb – (0x100000*0x1000 = 4Gb) .word 0x0000 ! baseaddress=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 thevideo mode ! handling code to store the temporary mode table (not usedby the kernel). ! modelist: .text endtext: .data enddata: .bss endbss:

一个今天胜过两个明天

Linux 引导解析(使用bootsect.s+setup.s方式)

相关文章:

你感兴趣的文章:

标签云: