Linux0.11内核--启动引导代码分析bootsect.s

本文详细介绍了Linux系统的启动过程,从BIOS读取bootsect代码开始,到系统内核的加载和初始化。涉及BIOS中断、内存操作、磁盘读取等关键技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Linux内核中的系统启动引导代码位于/boot目录下

但是,由于。Linus当时是在MINIX系统上开发Linux的,最初MINIX系统上还没有移植gas程序,因此Linus就使用了MINIX系统上的as86。

bootsect需要是16位的实模式程序。目前gas汇编器也支持16位的编译。所以现在也可以直接用as编译器直接编译。

下面是用at&t格式改写的bootsect.s代码

.code16 # rewrite with AT&T syntax by falcon <wuzhangjin@gmail.com> at 081012 # # SYS_SIZE is the number of clicks (16 bytes) to be loaded. # 0x3000 is 0x30000 bytes = 196kB, more than enough for current # versions of linux # .equ SYSSIZE, 0x3000 # # bootsect.s (C) 1991 Linus Torvalds # # bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves # iself out of the way to address 0x90000, and jumps there. # # It then loads 'setup' directly after itself (0x90200), and the system # at 0x10000, using BIOS interrupts. # # NOTE! currently system is at most 8*65536 bytes long. This should be no # problem, even in the future. I want to keep it simple. This 512 kB # kernel size should be enough, especially as this doesn't contain the # buffer cache as in minix # # The loader has been made as simple as possible, and continuos # read errors will result in a unbreakable loop. Reboot by hand. It # loads pretty fast by getting whole sectors at a time whenever possible. .global _start, begtext, begdata, begbss, endtext, enddata, endbss .text begtext: .data begdata: .bss begbss: .text .equ SETUPLEN, 4 # nr of setup-sectors .equ BOOTSEG, 0x07c0 # original address of boot-sector .equ INITSEG, 0x9000 # we move boot here - out of the way .equ SETUPSEG, 0x9020 # setup starts here .equ SYSSEG, 0x1000 # system loaded at 0x10000 (65536). .equ ENDSEG, SYSSEG + SYSSIZE # where to stop loading # ROOT_DEV: 0x000 - same type of floppy as boot. # 0x301 - first partition on first drive etc .equ ROOT_DEV, 0x301 ljmp $BOOTSEG, $_start _start: mov $BOOTSEG, %ax mov %ax, %ds mov $INITSEG, %ax mov %ax, %es mov $256, %cx sub %si, %si sub %di, %di rep movsw ljmp $INITSEG, $go go: mov %cs, %ax mov %ax, %ds mov %ax, %es # put stack at 0x9ff00. mov %ax, %ss mov $0xFF00, %sp # arbitrary value >>512 # load the setup-sectors directly after the bootblock. # Note that 'es' is already set up. load_setup: mov $0x0000, %dx # drive 0, head 0 mov $0x0002, %cx # sector 2, track 0 mov $0x0200, %bx # address = 512, in INITSEG .equ AX, 0x0200+SETUPLEN mov $AX, %ax # service 2, nr of sectors int $0x13 # read it jnc ok_load_setup # ok - continue mov $0x0000, %dx mov $0x0000, %ax # reset the diskette int $0x13 jmp load_setup ok_load_setup: # Get disk drive parameters, specifically nr of sectors/track mov $0x00, %dl mov $0x0800, %ax # AH=8 is get drive parameters int $0x13 mov $0x00, %ch #seg cs mov %cx, %cs:sectors+0 # %cs means sectors is in %cs mov $INITSEG, %ax mov %ax, %es # Print some inane message mov $0x03, %ah # read cursor pos xor %bh, %bh int $0x10 mov $38, %cx mov $0x0007, %bx # page 0, attribute 7 (normal) #lea msg1, %bp mov $msg1, %bp mov $0x1301, %ax # write string, move cursor int $0x10 # ok, we've written the message, now # we want to load the system (at 0x10000) mov $SYSSEG, %ax mov %ax, %es # segment of 0x010000 call read_it call kill_motor # 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, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending # on the number of sectors that the BIOS reports currently. #seg cs mov %cs:root_dev+0, %ax cmp $0, %ax jne root_defined #seg cs mov %cs:sectors+0, %bx mov $0x0208, %ax # /dev/ps0 - 1.2Mb cmp $15, %bx je root_defined mov $0x021c, %ax # /dev/PS0 - 1.44Mb cmp $18, %bx je root_defined undef_root: jmp undef_root root_defined: #seg cs mov %ax, %cs:root_dev+0 # after that (everyting loaded), we jump to # the setup-routine loaded directly after # the bootblock: ljmp $SETUPSEG, $0 #jump to 0x9020:0000(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 1+ SETUPLEN # sectors read of current track head: .word 0 # current head track: .word 0 # current track read_it: mov %es, %ax test $0x0fff, %ax die: jne die # es must be at 64kB boundary xor %bx, %bx # bx is starting address within segment rp_read: mov %es, %ax cmp $ENDSEG, %ax # have we loaded all yet? jb ok1_read ret ok1_read: #seg cs mov %cs:sectors+0, %ax sub sread, %ax mov %ax, %cx shl $9, %cx add %bx, %cx jnc ok2_read je ok2_read xor %ax, %ax sub %bx, %ax shr $9, %ax ok2_read: call read_track mov %ax, %cx add sread, %ax #seg cs cmp %cs:sectors+0, %ax jne ok3_read mov $1, %ax sub head, %ax jne ok4_read incw track ok4_read: mov %ax, head xor %ax, %ax ok3_read: mov %ax, sread shl $9, %cx add %cx, %bx jnc rp_read mov %es, %ax add $0x1000, %ax mov %ax, %es xor %bx, %bx jmp rp_read read_track: push %ax push %bx push %cx push %dx mov track, %dx mov sread, %cx inc %cx mov %dl, %ch mov head, %dx mov %dl, %dh mov $0, %dl and $0x0100, %dx mov $2, %ah int $0x13 jc bad_rt pop %dx pop %cx pop %bx pop %ax ret bad_rt: mov $0, %ax mov $0, %dx int $0x13 pop %dx pop %cx pop %bx pop %ax jmp read_track #/* # * 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 $0x3f2, %dx mov $0, %al outsb pop %dx ret sectors: .word 0 msg1: .byte 13,10 .ascii "Loading system ... compiled by yan!" .byte 13,10,13,10 .org 508 root_dev: .word ROOT_DEV boot_flag: .word 0xAA55 .text endtext: .data enddata: .bss endbss:

系统加电启动时,首先由BIOS读取bootsect代码到地址0x7c00处,然后首先将自己移动至地址0x9000处,大小是256个字,相当于512

字节。

如何实现内存块的复制,运用ds:si->es:di的方式,将ds所指的段移 到ps所指的段中去,cx中指定移动的次数。gas中的段间跳转指令 ljmp A,B,其中A代表目标段地址,B代表A段中的偏移值。

然后跳转到移动后代码的下一条语句执行,这是由于

ljmp $INITSEG, $go

跳转后,该指令自动修改CS代码段的值。

移动代码后,由于这是会使用pop,push,call等指令,需要使用堆栈段,所以需要重新设置段基址,0x9ff00。

然后通过BIOS的INT 0x13中断来从磁盘加载setup模块。

***************************************

BIOS中断INT 0x13读磁盘功能

读扇区:

ah=0x02 - 读磁盘扇区到内存;a1=需要读出的扇区数量

ch=磁道(柱面)号的低8位; c1=开始扇区(位0-5),磁道号高2 位(位6-7);

dh=磁头号; d1=驱动器号(如果是硬盘则位7要 置位),0为当前A驱动器

es:bx->指向数据缓冲区;如果出错则CF标志置位。

***************************************
然后再用BIOS的INT 0x13中断读磁盘参数
***************************************
BIOS中断INT 0x13取磁盘驱动器参数功能
ah=0x08 dl=驱动器号(如果是硬盘则要置位7为1)
返回信息
如果出错则CF置位,并且ah=状态码
ah=0,al=0,bl=驱动器类型(AT/PS2)
ch=最大磁道号的低8位,cl=每磁道最大扇区数(位0-5),最大磁道 号高2位(位6-7)
dh=最大磁头数,dl=驱动器数量
es:di->软驱磁盘参数表
***************************************
然后利用BIOS的INT 0x10中断读取光标位置并且输出信息
***************************************
BIOS中断0x10,读光标位置功能
功能号ah=0x03
输入:bh=页号
返回:ch=扫描开始线;cl=扫描结束线;dh=行号(0x00顶端);dl= 列号(0x00最左边)
***************************************
***************************************
BIOS中断0x10,显示字符串
功能号ah=0x13
输入:al=放置光标的方式及规定属性,0x01表示使用bl中的属性 值,光标停留在字符串结尾处。es:bp此寄存器指向要显示的字符串 起始位置处。cx=显示的字符串字符数。bh=显示页面号;bl=字符属 性,7为normal,dh=行号,dl=列号。
从磁盘上都数据必须位于64KB边界开始处。因为实模式下的寄存器为 16位,所以最大寻址为2^16=64KB,即段限长也为64KB,一次性最大 能拷贝的字节数也是64KB。
***************************************
.org伪指令
.org 508
指令表示表示后面的指令从地址508开始,linux0.11初始化时根文件 系统所在设备号就存放在0x90508(0x90000是bootsect.s程序的开始 处)开始的两个字节处。供后面的程序main.c调用。
为什么是508而不是510呢,因为510和511这首个扇区的最后两个字节 必须存放0xaa55,作为该盘是有效引导扇区的标志,仅供bios中的程 序加载引导扇区时识别使用。
最后加载system到0x10000处。
下面read_it和kill_monitor两个子程序待续.......
下面是加载内存的模块分布和移动。

linux .....\boot .....\....\bootsect.s .....\....\head.s .....\....\setup.s .....\fs .....\..\bitmap.c .....\..\block_dev.c .....\..\buffer.c .....\..\char_dev.c .....\..\exec.c .....\..\fcntl.c .....\..\file_dev.c .....\..\file_table.c .....\..\inode.c .....\..\ioctl.c .....\..\Makefile .....\..\namei.c .....\..\open.c .....\..\pipe.c .....\..\read_write.c .....\..\stat.c .....\..\super.c .....\..\truncate.c .....\include .....\.......\a.out.h .....\.......\asm .....\.......\...\io.h .....\.......\...\memory.h .....\.......\...\segment.h .....\.......\...\system.h .....\.......\const.h .....\.......\ctype.h .....\.......\errno.h .....\.......\fcntl.h .....\.......\linux .....\.......\.....\config.h .....\.......\.....\fs.h .....\.......\.....\hdreg.h .....\.......\.....\head.h .....\.......\.....\kernel.h .....\.......\.....\mm.h .....\.......\.....\sched.h .....\.......\.....\sys.h .....\.......\.....\tty.h .....\.......\signal.h .....\.......\stdarg.h .....\.......\stddef.h .....\.......\string.h .....\.......\sys .....\.......\...\stat.h .....\.......\...\times.h .....\.......\...\types.h .....\.......\...\utsname.h .....\.......\...\wait.h .....\.......\termios.h .....\.......\time.h .....\.......\unistd.h .....\.......\utime.h .....\init .....\....\main.c .....\kernel .....\......\asm.s .....\......\blk_drv .....\......\.......\blk.h .....\......\.......\floppy.c .....\......\.......\hd.c .....\......\.......\ll_rw_blk.c .....\......\.......\Makefile .....\......\.......\ramdisk.c .....\......\chr_drv .....\......\.......\console.c .....\......\.......\keyboard.S .....\......\.......\Makefile .....\......\.......\rs_io.s .....\......\.......\serial.c .....\......\.......\tty_io.c .....\......\.......\tty_ioctl.c .....\......\exit.c .....\......\fork.c .....\......\math .....\......\....\Makefile .....\......\....\math_emulate.c .....\......\mktime.c .....\......\panic.c .....\......\printk.c .....\......\sched.c .....\......\signal.c .....\......\sys.c .....\......\system_call.s .....\......\vsprintf.c .....\lib .....\...\close.c .....\...\ctype.c .....\...\dup.c .....\...\errno.c .....\...\execve.c .....\...\Makefile .....\...\malloc.c .....\...\open.c
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值