从开机加电到执行main函数

2019-04-13 13:22发布

    从开机到main函数的执行分三步完成:   (1)启动BIOS,准备实模式下的中断向量表和中断服务程序。   (2)从启动盘加载操作系统程序到内存,并为保护模式做准备,加载操作系统程序的工作就是利用(1)中准备好的中断服务程序实现的。   (3)为执行32位main函数做过渡工作    本篇先详细说明(1)和(2),即从开机到操作系统内核加载到内存当中的过程。
一. BIOS启动   Intel CPU的硬件都是设计为加电即进入16位实模式状态运行,且硬件逻辑设计成了加电瞬间将CS的值置为0xF000,IP置为0xFFF0,CS:IP 0xFFFF0, 就正好是在固化在主板上一块小ROM芯片中BIOS程序的地址范围(0xFE000~0xFFFFF),而BIOS程序的入口地址正好是0xFFFF0,这样按下开机键后,就开始执行BIOS程序了。BIOS启动后会检测硬件(显卡、内存),并会在内存最开始的地方0x00000的地方用1KB的内存空间(0x00000~0x003FF)创建实模式下的中断向量表,在0x00400~0x004FF内存地址间构建BIOS数据区,并在0x0E05B的位置加载8KB与中断向量相对应的实模式下中断服务程序。注意:实模式下的中断向量占4个字节,CS占2个,IP占2个,每个中断向量都指向一个具体的中断服务程序。 二.加载操作系统内核   操作系统内核代码是分三次加载:   (1)BIOS中断int 0x19把第一扇区的bootsect的内容加载到内存   (2)执行int 0x13中断服务程序把启动盘中2~5扇区的setup程序加载到内存   (3)仍然执行int 0x13中断服务程序把启动盘中第6~240扇区的System模块加载到内存   2.1 引导程序bootsect加载及执行   计算机完成自检后会让CPU接收到一个int 0x19中断,从而使CPU跳转到int 0x19对应的中断服务程序入口地址(0x0E6F2),把启动盘第一扇区512B内容复制到0x07C00(BOOTSEG)内存地址处,这个过程和linux无关,是BIOS中先固化好的。第一扇区中的内容就是bootsect程序,是由bootsect.s汇编程序汇编而成的。至此,计算机内存中第一次有了linux操作系统的代码,虽然只是启动代码。   bootsect程序的执行(参见/boot/bootsect.s): (1)规划内存:实模式下,CPU最大寻址范围是1MB  SETUPLEN = 4                ! nr of setup-sectors BOOTSEG  = 0x07c0            ! original address of boot-sector INITSEG  = 0x9000            ! we move boot here - out of the way SETUPSEG = 0x9020            ! setup starts here SYSSEG   = 0x1000            ! system loaded at 0x10000 (65536). ENDSEG   = SYSSEG + SYSSIZE        ! where to stop loading (2)将自身全部内容(512B)从内存0x07C00(BOOTSEG)处复制到0x90000(INITSEG) entry start start:     mov    ax,#BOOTSEG     mov    ds,ax     mov    ax,#INITSEG     mov    es,ax     mov    cx,#256     sub    si,si     sub    di,di     rep                !rep重复的是跟在它后面的一条指令,此处rep movw,重复的是movs指令:把DS:SI的内容复制到ES:DI中     movw               !搬移完成后,CS = BOOTSEG(0x07C0)搬移完成后,执行跳转,跳转后,CS:0x9000(INITSEG),IP为从INSTSEG到go:mov ax,cs这行对应的偏移     jmpi    go,INITSEG   !段间跳转指令,执行这条指令后:CS = INITSEG,IP = go;跳转完成后,操作系统已经不需要完全依耐BIOS go:    mov    ax,cs 因为代码的整体位置发送了改变,CS也已经改变,所以需要对代码中各个段进行调整,包括DS、ES、SS、SP     mov    ds,ax     mov    es,ax ! put stack at 0x9ff00.     mov    ss,ax     mov    sp,#0xFF00        ! arbitrary value >>512  !给CS DS ES SS(栈基址寄存器)赋值0x9000,指定栈顶指针指向0x9FF00 执行完后,DS ES SS都是0x9000,SP是0x9FF00。
2.2 执行bootsect程序加载启动盘中2~5扇区的setup程序到内存 加载setup程序需要借助于BIOS的int 0x13中断服务程序,在执行这个程序之前需要将磁盘扇区、加载到的内存中的位置等信息传给中断服务程序,然后执行 int 0x13指令,产生0x13中断,执行中断程序,将setup.s对应的程序加载至内存SETUPSEG(0x90200)处,这样搬移过后的bootsect与加载完的setup连在一块了。 ! load the setup-sectors directly after the bootblock. ! Note that 'es' is already set up. load_setup:     mov    dx,#0x0000        ! drive 0, head 0     mov    cx,#0x0002        ! sector 2, track 0     mov    bx,#0x0200        ! address = 512, in INITSEG     mov    ax,#0x0200+SETUPLEN    ! service 2, nr of sectors     int    0x13            ! read it 上面mov指令是给中断服务程序传参数,通过读int 0x13中断向量,跳转到指定中断服务程序     jnc    ok_load_setup        ! ok - continue     mov    dx,#0x0000     mov    ax,#0x0000        ! reset the diskette     int    0x13     j    load_setup ok_load_setup: 2.3 执行int 0x13中断服务程序把启动盘中第6~240扇区的System模块加载到内存 由于这次是要加载240个扇区120KB数据,会耗费一定时间,因此设置"Load System"开机提示。加载工作主要是bootsect调用read_it子程序完成,这个子程序将软盘第六扇区开始的约240个扇区的system模块加载至内存的SYSSEG(0x10000)处往后的120KB空间中。 加载完成后,bootsect程序的都已经完成,接下来通过执行: jmpi 0,SETUPSEG跳转至0x90200处,即setup程序加载的位置,CS:IP指向setup程序的第一条指令,意味着由setup程序接着bootsect程序继续执行。setup程序现在开始执行,第一件事是利用BIOS提供的中断服务程序从设备上获取机器系统参数,包括光标位置、显示页面等数据,并分别从中断向量0x41和0x46所指向的内存地址处获取硬盘参数表1、硬盘参数表2,把它们存放在0x9000:0x0080和0x9000:0x9000处。这些机器参数被加载到内存的0x90000~0x901FC位置,机器参数是直接覆盖已经执行完成的bootsect程序所占用的内存空间。到此为止,操作系统内核程序加载到内存已经完成了。 entry start start: ! ok, the read went well so we get current cursor position and save it for ! posterity.     mov    ax,#INITSEG    ! this is done in bootsect already, but...     mov    ds,ax     mov    ah,#0x03    ! read cursor pos     xor    bh,bh     int    0x10        ! save it in known place, con_init fetches     mov    [0],dx        ! it from 0x90000. ! Get memory size (extended mem, kB)     mov    ah,#0x88     int    0x15     mov    [2],ax ! Get video-card data:     mov    ah,#0x0f     int    0x10     mov    [4],bx        ! bh = display page     mov    [6],ax        ! al = video mode, ah = window width ! check for EGA/VGA and some config parameters     mov    ah,#0x12     mov    bl,#0x10     int    0x10     mov    [8],ax     mov    [10],bx     mov    [12],cx ! Get hd0 data     mov    ax,#0x0000     mov    ds,ax     lds    si,[4*0x41]     mov    ax,#INITSEG     mov    es,ax     mov    di,#0x0080     mov    cx,#0x10     rep     movsb ! Get hd1 data     mov    ax,#0x0000     mov    ds,ax     lds    si,[4*0x46]     mov    ax,#INITSEG     mov    es,ax     mov    di,#0x0090     mov    cx,#0x10     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,#INITSEG     mov    es,ax     mov    di,#0x0090     mov    cx,#0x10     mov    ax,#0x00     rep     stosb is_disk1:  !获取机器系统数据:光标位置、显示页面、硬盘参数等,并将这些覆盖到之前bootsect区域。