操作系统的第二个程序 - 分区的启动程序
MBR程序找到活动分区,并且把活动分区的启动扇区读到了600处,然后开始执行这里的程序,所以把这个程序称为操作系统的第二个程序也算正确。
1. 我使用的分区格式
分区的启动程序自然会跟分区格式紧密相关了,首先要知道分区格式,才能根据分区格式来写启动程序。因为我不能在设计了自己的分区格式之后,才在自己的分区格式上开发操作系统,所以我只能使用现有的分区格式。
使用哪个?只能是 FAT32 。简单,好用等等等,其实,是因为我只熟悉FAT32。
等我把自己的简单文件系统(Simple File System, SFS)搞出来以后,就使用SFS了。
2. 分区启动程序的作用
我认为分区启动程序的主要作用是在启动分区里找到系统装载程序的文件,然后将系统装载程序读入内存,然后跳转到系统装载程序运行。
要完成这些工作,就涉及到在分区中查找文件和读取文件,实际上就是分区启动程序需要包含文件系统驱动程序。这个驱动程序不需要很多功能,只需要具备查找文件和读取文件的功能就行了。
这里面有个小细节需要注意,一般来说,MBR程序只是将分区的启动扇区读入内存,就一个扇区。所以,最理想的状态就是在这一个扇区里完成在分区里查找文件和将装载文件读入内存。以FAT32文件系统为例,分区的启动扇区被BIOS参数块占用了开头的90个字节,签名又占用了最后4个字节,实际的可用空间就只有408个字节。要在这408字节中完成文件的查找和读入,是很有挑战性的事情。
不同的分区格式,需要不同的分区启动程序。原因是文件系统驱动不一样,如果想做一个通用的分区启动程序,就要在里面包含各种文件系统的驱动,这样的话,分区启动程序就会变得很大,一个扇区绝对装不下,这又引出了另一个问题:如果分区启动程序太大,要保存在哪里?其实因为空间有限,把系统装载程序读入内存之后,也干不了太多其他的事情。当然,如果保留空间足够大,也能干很多事情。然而,事实上不同的分区格式化程序设置的保留扇区是不一样的,如果按照某一个分区格式化软件的参数来设计分区启动程序,能正常运行,但是在其他的分区格式化软件格式化出来的分区,就有可能出问题。
各个操作系统都会有自己的分区启动程序,一般来说与其他操作的启动程序不兼容,而且也应该会故意做成不兼容。当然,如果直接使用现有的通用引导程序,比如bootloader,也就没这个问题了。
3. 分区启动程序设计
根据我认为的分区启动程序的作用,我的分区启动程序设计成两个部分的,是分开的两个部分。一个部分放在分区启动扇区,一个部分放在分区的保留扇区。
具体的要完成的工作是:1、第一部分将保存在保留分区里的第二部分程序读到9000:0100,然后跳转这个地址执行第二部分的分区启动程序。2、第二部分的分区启动程序会在分区的根目录下找到16位的系统装载文件,我现在定的文件名是RBXLDR。然后把这个文件全文读到内存0800:0100的地址处,然后跳转到0800:0100执行。
3.1 第一部分:分区启动扇区的部分
这一部分的工作是要定位保留扇区的位置,然后读入内存。定位方法是从BIOS参数块中找到保留扇区数,加上MBR程序保存在03F8的分区起始扇区。定位后,就用INT 013H把内容读到9000:0100处。
3.2 第二部分:分区保留扇区的部分
这部分要查找文件和读取文件。查找文件就要遍历目录、解析目录结构、将文件名转换成目录格式。读取文件就要解析文件簇链、从簇号解析为磁盘扇区号。这些就要学习FAT32文件系统格式了。
FAT32文件系统放到书里的文件系统部分在写了。
4. 源代码
这个程序分成了两个部分,源代码也就跟着分成两部分。
4.1 分区启动扇区的源代码
;
;==============================================================================
; ROBIX
; Robin's Operating System. Order Source Studio @ 2011 - 2019
; ALL RIGHTS RESERVED
; ***------------------------------***
;Name : FAT32BTR.ASM FAT32 Booter
;Create : 2019-09-21. robin
;Purpose:
;Remark :
; active partition start sector store at: 0000:03F8
;==============================================================================
;
.386p
.model tiny
TEXT SEGMENT USE16 PUBLIC 'CODE'
assume cs:TEXT,ds:TEXT
org 00600h
start:
jmp fat32_boot ; skip the FAT32 BPB
org 065Ah
fat32_boot:
cli
xor cx,cx
mov ds,cx
mov es,cx
mov ss,cx
mov sp,03FFCh
; check if the number of reserved sectors is greater than 18
mov si,0600h
mov cx,[si + 14] ; get the number of reserved sectors
cmp cx,18
jl failed
sub cx,16
add ax, cx
adc dx, 0
; read the real booter into 9000:0100
mov cx,08010h
mov bx,09000h
mov es,bx;
mov bx,0100h
call read_hd
test ah, ah
jnz failed
; jmp 9000:0100
DB 0EAh
DW 0100h
DW 09000h
read_hd:
; ax,dx,si,di = sect,
; ch = hd
; cl = sect
; es:bx = buf
push bp
sub sp,32;
mov bp,sp
mov [bp + 8],ax
mov [bp + 10],dx
mov [bp + 12],si
mov [bp + 14],di
mov byte ptr [bp],16
mov byte ptr [bp + 1],0
mov byte ptr [bp + 2],cl
mov byte ptr [bp + 3],0
mov ax,es
mov [bp + 4],bx
mov [bp + 6],ax
mov ax,04200h
mov dl,ch;
mov si,bp
int 013h
mov sp,bp
add sp,32
pop bp
ret
failed:
sti
push cs
pop es
mov ah,03h
xor bh,bh
int 010H
lea bp,msg
mov bx,0Fh
mov cx,51
mov ax,01301h
int 010h
xor ax,ax
int 016h
cmp al,'r'
je reboot
cmp al,'R'
je reboot
jmp failed
reboot:
; jmp FFFF:0
DB 0EAh
DW 0
DW 0FFFFh
align qword
msg DB "PARTITION OR DISK IO ERROR, PRESS 'R' TO REBOOT",
0Dh,0Ah,0Dh,0Ah
TEXT ENDS
end start
4.2 保留扇区的源代码
这部分包含一个汇编程序和一个C程序,汇编程序仅仅是提供了一个链接的入口,主要功能都在C程序中,而且这部分要编译成COM格式的可执行文件。C程序编译成COM格式的可执行文件在后面讲。
先贴汇编文件
;
;==============================================================================
; ROBIX
; Robin's Operating System. Order Source Studio @ 2011 - 2019
; ALL RIGHTS RESERVED
; ***------------------------------***
;Name : STARTUP.ASM FAT32 Booter startup
;Create : 2019-09-21. robin
;Purpose: this asm program only provide the DOS executable file entry, and
; initialize the segment register and stack pointer, and copy itself to
; 9000:0000,
;Remark :
;==============================================================================
;
.386p
.model tiny
EXTRN _main:PROC
TEXT SEGMENT USE16 PUBLIC 'CODE'
assume cs:TEXT,ds:TEXT
org 0100h
start:
cli
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,07FFCh
; here, Check if the booter is in 9000:0000. if not, copy to 9000:0000, and
; jump to that 9000:0100 to run.
mov dx,09000h
cmp ax,dx
je boot_start
mov es,dx
mov cx,08000h
xor di,di
xor si,si
rep movsb
db 0EAh
dw 0100h
dw 09000h
boot_start:
call _main
TEXT ENDS
end start
C程序太多,就不全贴了,贴扫描目录和加载文件的部分。另一方面,这个C程序需要DOS和windows下调试,为了源代码能兼容BC3.1和VS2019,用了一些条件编译,所以就写成这样了。
bool_t SearchDirectory(dword_t clus,const char * filename,fatdir_t * dir)
{
uint32_t clussect;
uint_t iSect;
int dirn;
fatdir_t * tmpdir;
for( ; clus < 0x0FFFFFF7 ; clus = NextClus(clus) ){
clussect = ClusToSector(clus);
for( iSect = 0 ; iSect < nSectPerClus ; iSect++,clussect++){
ReadHD(pBootHDD, clussect, TO_PTR32(buf), 512);
tmpdir = (fatdir_t *)buf;
for( dirn = 0 ; dirn < 16 ; dirn++, tmpdir++){
if( tmpdir->fd_Name[0] == 0xE5 )
continue;
if( tmpdir->fd_Name[0] == 0 )
return FALSE;
if( (tmpdir->fd_Attr & 0x3F) == DIR_ATTR_LONG_NAME)
continue;
if( tmpdir->fd_Attr & DIR_ATTR_DIRECTORY )
continue;
if( _memcmp(filename,tmpdir->fd_Name,11) == 0 ){
DBG_FOUND_DIR(tmpdir);
_memcpy(dir,tmpdir,sizeof(fatdir_t));
return TRUE;
}
}
}
}
return FALSE;
}
bool_t LoadFileToMem(uint32_t nFileFirstClus, uint32_t nFileSize,
ptr32_t pPhyMemAddr)
{
uint32_t nClus,nFirstClusSect;
uint_t iSect;
#if _OS_ == __DOS__
#ifdef __DEBUG__
int nSect = 0;
#endif
#elif _OS_ == __WINDOWS__
int nSect = 0;
#endif
nClus = nFileFirstClus;
for( ; nClus < 0x0FFFFFF7 && nFileSize; nClus = NextClus(nClus) ){
nFirstClusSect = ClusToSector(nClus);
#ifdef __DEBUG__
printf("read cluster %ld to memory %08lX\n", nClus, pPhyMemAddr);
#endif
for( iSect = 0 ; iSect < nSectPerClus && nFileSize; iSect++ ){
#if _OS_ == __DOS__
#ifdef __DEBUG__
printf("read sector:%ld to memory:%08lX, total sectors: %d\n",
nFirstClusSect + iSect, pPhyMemAddr, ++nSect);
#endif
#elif _OS_ == __WINDOWS__
printf("read sector:%d to memory:%08X, total sectors: %d\n",
nFirstClusSect + iSect, pPhyMemAddr, ++nSect);
#endif
if( ReadHD(pBootHDD, nFirstClusSect + iSect, pPhyMemAddr,
nBytesPerSect) != TRUE )
return FALSE;
(uint32_t)pPhyMemAddr += nBytesPerSect;
if( nFileSize < nBytesPerSect )
nFileSize = 0;
else
nFileSize -= nBytesPerSect;
}
}
return TRUE;
}
int main(void)
{
fat32bpb_t * pBPB;
fatdir_t dir;
#if _OS_ == __DOS__
Cls()
SetCursor(0,0);
#endif
prints("ROBIX FAT32 PARTITION BOOTER RUNNING\r\n");
pActPartStartSect = (uint32_t FAR *)0x000003F8L;
pActPartStartSect[0] = 0l;
pActPartStartSect[1] = 0l;
if(BuildHDDTab() != TRUE){
BootFailed(strDiskErr);
return 0;
}
/* build the FAT32 parameter */
pBPB = (fat32bpb_t *)bufFAT32BPB;
nActPartStartSect = *pActPartStartSect;
nBytesPerSect = pBPB->bpb_BytesPerSect;
nSectPerClus = pBPB->bpb_SectPerClus;
nRootClus = pBPB->bpb_RoorClus;
nRsvdSectCnt = pBPB->bpb_RsvdSectCnt;
nFirstDataSect = FirstDataSect(pBPB);
#ifdef __DEBUG__
#if _OS_ == __DOS__
printf("active partition start sector: %ld\n", nActPartStartSect);
printf("bytes per cluster: %d\n", nBytesPerSect);
printf("reserved sector count: %ld\n", nRsvdSectCnt);
printf("partition first data sector: %ld\n", nFirstDataSect);
#elif _OS_ == __WINDOWS__
printf("active partition start sector: %d\n", nActPartStartSect);
printf("bytes per cluster: %d\n", nBytesPerSect);
printf("reserved sector count: %d\n", nRsvdSectCnt);
printf("partition first data sector: %d\n", nFirstDataSect);
#else
#endif
#endif
/* search ROBIX LOADER file in root directory */
if(SearchDirectory(nRootClus, LOADER_FILE, &dir) != TRUE){
BootFailed(strLdrFileNotExist);
return 0;
}
prints("ROBIX LOADER FOUND!\r\n");
/* load the ROBIX LOADER into memory,
default at 0800:0100. 32-BIT physical address is 0x00008100
*/
LoadFileToMem(FAT32_GET_CLUSTER(&dir), dir.fd_FileSize, LOADER_BASE);
IVT[243] = 0x08000100L;
prints("ROBIX LOADER BEGIN\r\n");
#if _OS_ == __DOS__
/* run the ROBIX LOADER via IVT */
__asm int 243
#elif _OS_ == __WINDOWS__
CloseHandle(pHDDTab->hdd_IOAddr[0]);
#endif
return 0;
}
5. C语言源代码编译成COM格式的可执行文件
因为启动程序需要直接可执行程序,但一般编译链接得到的exe文件是包含格式信息的,还要经过整理才能成为可执行程序。所以只能以曲线的方式得到直接可执行的程序。
这么做是因为我只知道这种方法,并不是因为这种方法很好。至于用gcc的方法,其实吧,是我懒得学gcc了。
方法的大致是这样的:
第一步:自己准备一个汇编文件,完成寄存器初始化,包括栈。然后在里面调用main,main函数在C程序里提供。
第二步:编译C程序时,把代码段(_text)、已初始化数据段(_data)、未初始化数据段(_bss)的段名和类型都指定成一样的,并且要tiny模式编译,
第三步:链接程序的时候,指定链接成COM格式
这里把分区启动程序的第二部分的编译命令贴出来,这是在DOS下批处理
@ECHO off
ECHO ==========================================================
ECHO Order Source Studio
ECHO robin @ 2019-10-16
ECHO copyright by robin, all rights reserved
@ECHO.
ECHO this batch processing file use to compile rbxldr
ECHO ==========================================================
@ECHO.
IF EXIST rbxldr del rbxldr
IF EXIST rbxldr.exe del rbxldr.exe
IF EXIST rbxldr.com del rbxldr.com
tasm /dLOADER_ENTRY=019000h startup.asm, startup.obj
IF NOT EXIST startup.obj GOTO STARTUP_FAILED
bcc -c -mt -zCTEXT -zDTEXT -zRTEXT -zTCODE -zBCODE RBXLDR.c
IF NOT EXIST rbxldr.obj GOTO RBXLDR_C_FAILED
tlink /t startup.obj rbxldr.obj, RBXLDR.COM, RBXLDR.MAP,
IF NOT EXIST RBXLDR.COM GOTO RBXLDR_FAIELD
rename RBXLDR.COM RBXLDR
del rbxldr.obj
del startup.obj
@ECHO.
ECHO *** compile and link rbxldr.com [SUCCESS]. ***
GOTO END
:STARTUP_FAILED
ECHO compile startuo.asm failed
GOTO END
:RBXLDR_C_FAILED
ECHO compile rbxlbr.c failed
GOTO END
:RBXLDR_FAILED
ECHO link rbxldr.com failed
GOTO END
:END
@Echo on