linux0.11源码分析1 bootsect.s文件分析
从开机加电到执行main函数的过程
- 由于linux0.11系统当时存储在软盘上,所以其加电过程主要目的就是从启动盘加载操作系统程序,完成执行main函数的准备工作。
- 从开机到mian函数的启动共分三部分:
- 第一部分是启动bios,准备实模式下的中断向量表和中断服务程序。
- 第二部分是从启动盘加载操作系统到内存
- 第三部分为执行32位的main函数做过渡工作
第一部分
- 从硬件来看,Intel将所有的80x86系列的CPU的硬件都设计为加电进入16位实模式运行,加电后指向0xFFFF0位置
- BIOS程序在内存最开始的位置用1kb的内存空间(0x00400~0x004FF)构建中断向量表。 在紧挨着的地方用256字节的内存空间构建BIOS数据区(0x00400~0x004FF),在56kb以后的位置(0x0E2CE)加载了8kb左右的中断向量表相应的中断服务程序。
第二部分
对于linux0.11系统而言,计算机分三批逐次加载操作系统的代码:
- 第一批由BIOS中断
int 0x19
把第一扇区bootsect的内容加载到内存。 - 第二批和第三批在 bootsect的指挥下,分别把其后的四个扇区和随后的240个扇区的内容加载至内存
- 第一批由BIOS中断
对于
int 0x19
中断向量所指向的中断服务程序将软驱0号磁头对应盘面的0磁道1扇区的内容拷贝至内存0x07C00(这个地址属于DRAM区域)处。该磁道扇区存放的是bootsect的代码。- 实模式下的最大寻址内存只有1MB,所以需要规划内存。
bootsect复制自身的操作代码如下:
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 movw jmpi go,INITSEG
首先,程序进入start这个入口点,然后将启动扇区被bios加载的位置(即BOOTSEG)进行存储在ds寄存器中,将要移动的新位置的地址(INITSEG)存入cx寄存器,下面的2条sub指令是分别使其为0x0000,cx为256,并且操作是movw,说明是字复制操作,则总共移动512个字节。(这里需要了解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
复制完毕后,执行jmpi指令,其意思是跳转到go:INITSEG, CS的值变为 INITSEG,IP的值变为从INITSEG到go:mov ax,cs的偏移。
然后将DS、ES、SS进行调整,把cs当前的值赋给他们,栈顶指针sp指向偏移地址为0xFF00处。 注意:SS和SP构成了栈数据在内存中的位置。
- 将setup程序加载进内存
需要借助BIOS提供的中断0x13中断指向的中断服务程序来完成。int 0x13
把指定的扇区的代码加载到内存的指定位置。
int 0x13是直接磁盘服务,具体的参数和用法参考如此
代码如下:
mov dx,#0x0000 ! drive 0, head 0
mov cx,#0x0002 ! sector 2, track 0
mov bx,#0x0200 ! address = 512, in INITSEG 存放偏移地址512
mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors SETUPLEN是被加载的扇区数,对应参数AL
int 0x13 ! read it 指向的是磁盘服务程序,根据AH的值对应于02,是读扇区功能,AL是扇区数
jnc ok_load_setup ! ok - continue 如果cf标志为为0,则跳转
mov dx,#0x0000
mov ax,#0x0000 ! reset the diskette 不为0则重置
int 0x13 ! 继续中断
j load_setup !返回load_setup位置
前面4个mov是先对参数进行设置,注意第4个mov,给定了AH=02,表明是功能02H读扇区。AL是扇区数。
然后调用int 0x13中断,进入中断服务程序,读取将软盘从第二扇区开始的四个扇区加载至
ES:BX代表的是缓冲区地址,所以bx偏移量为512,es的值还是当前段。
加载第三部分代码
bootsect借着BIOS中断int 0x13,将240个扇区的system模块加载进内存 加载工作由bootsect调用read it子程序完成,这个子程序将软盘第6扇区开始的240个扇区的system模块加载进内存的SYSSEG(0x10000)处往后的120kb空间中。
代码如下:ok_load_setup: ! Get disk drive parameters, specifically nr of sectors/track mov dl,#0x00 !DL=驱动器 mov ax,#0x0800 ! AH=8 is get drive parameters AH=08表示读取驱动器参数 int 0x13 !中断13向量 mov ch,#0x00 !ch=柱面 seg cs !表示下一条指令将使用段超越 mov sectors,cx mov ax,#INITSEG mov es,ax ! Print some inane message mov ah,#0x03 ! read cursor pos AH=03是入口参数,在文本坐标下,读取光标各种信息 xor bh,bh !bh是显示页码 int 0x10 mov cx,#24 !CH为光标的起始行,CL为光标的终止行 mov bx,#0x0007 ! page 0, attribute 7 (normal) !bx是显示页面 mov bp,#msg1 !ES:BP=显示字符串的地址,msg1在源码中该文件的最后面几行位置 mov ax,#0x1301 ! write string, move cursor !AH=13表示显示字符串,AL=1表示字符串只显含字符,显示后,光标位置改变。 int 0x10 !显示服务的中断,进行屏幕写操作
上述就进行了读取驱动器参数, 并且打印加载系统的文字信息。调用int 0x10中断。
接着,调用read_it函数进行读取扇区内容,代码如下:read_it: mov ax,es test ax,#0x0fff !测试ax中的某有位是否为0,如果为0,则ax为0,将zf置1,否则置0 die: jne die ! es must be at 64kB boundary zf不等于0则跳转 xor bx,bx ! bx is starting address within segment !异或指令 rp_read: mov ax,es cmp ax,#ENDSEG ! have we loaded all yet? !对两数进行相减,进行比较 jb ok1_read !判断2数大小 ret ok1_read: seg cs 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 seg cs 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 mov ax,es add ax,#0x1000 mov es,ax xor bx,bx jmp rp_read
调用kill_motor函数
/* * 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 mov al,#0 outb pop dx ret
接下来还有一段代码是确定根文件系统号的。
通过执行
jmpi 0,SETUPSEG
将跳转到0x90200处,即setup程序加载的位置,CS:IP指向setup程序的第一条指令。
现在,整个bootsect程序的任务全部完成了