现在CPU开始执行bootsect,它的作用是把第二部分、第三部分程序陆续加载到内存中。把放到合理的内存位置需要先对内存进行规划。
根据上一节,boostsect当前所在内存位置是0x07c0,大小为512byte,现在要将其挪动到内存的0x9000(INITSET)。除此以外,还要将位于硬盘上的4个(SETUPLEN)扇区的setup程序加载到内存的0x9020(SETUPSEG);最后要将system程序(内核)加载到内存的0x1000(SYSSEG),并指定了其末尾位置(ENDSEG)。
SYSSIZE = 0x3000
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
注意,这里的INITSET,SETUPSEG,SYSSEG会被ds(数据段基址)载入,因此在实模式下的实际加载内存位置是0x90000,0x90200(bootsect占据的512字节内存末尾正是0x90000+0x200,说明bootsect和setup在内存里紧挨着),0x10000。
如何将已位于内存的bootsect挪动位置
通过ds:si和es:di指定源地址和目的地址,cx指定重复操作的次数,movw每次复制2字节一共是512字节。然后jmpi段间跳转将cs:ip设置为0x9000:go以让程序从go标签继续。
cs已被更新为0x9000,同样将其赋予ds、es,并设置栈顶为0x9ff00,为后续栈操作做好准备。
mov ax,#BOOTSEG
mov ds,ax
mov ax,#INITSEG
mov es,ax
mov cx,#256
sub si,si
sub di,di
rep
movw
jmpi go,INITSEG
go: mov ax,cs
mov ds,ax
mov es,ax
; put stack at 0x9ff00.
mov ss,ax
mov sp,#0xFF00 ; arbitrary value >>512
如何将setup加载到指定位置
答案仍是调用BIOS的0x13中断,只不过和上一节课0x19中断不同的是,0x13的参数由我们bootsect的程序来指定,其中包括指定硬盘0盘面0磁头、2扇区0磁道、偏移地址512(0x200)、ah中断所属的功能号0x02,al待读setup的扇区数是4。最后调用int 0x13,通过cf标志是否为0判断0x13执行是否成功:失败并则重试,成功则无条件跳转到ok_load_setup。
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
jnc ok_load_setup ; ok - continue
mov dx,#0x0000
mov ax,#0x0000 ; reset the diskette
int 0x13
jmp load_setup
如何将system加载到指定位置
核心代码就这几行,其作用是把从硬盘第 6 个扇区开始往后的 240 个扇区,加载到内存 0x10000 处,和之前的从硬盘复制到内存类似,read_it归根到底也用到了0x13。其原理都是通过磁盘控制器访问磁盘,要给出柱面,磁头,扇区等信息。详细read_it参考:https://blog.csdn.net/kunkliu/article/details/126294876
ok_load_setup:
...
mov ax,#SYSSEG
mov es,ax ; segment of 0x10000
call read_it
...
jmpi 0,0x9020
现在所有操作系统代码都已加载内存,给一张来自于《Linux0.11源码解读》的内存图景: