16位装载程序 - 第一阶段装载之探测内存

正常情况下,在MBR程序和分区启动程序运行完后,就开始执行位于0800:0100的16位装载程序,这个程序就是被分区启动程序加载到这个位置的。

这一阶段要完成的工作比较多,因此相应的源代码也比较多了。所以我就分成几个部分来写,
这个第一阶段装载要完成内存探测、加载配置文件、加载第二阶段的32位装载程序、加载各种必须的系统文件(主要是各种驱动,因为进入32位后,就没有BIOS可用了),最后给硬盘做标记。

1. 装载程序

我的装载程序分成两个阶段执行,第一阶段是在实模式下进行装载,主要是探测内存和加载必要的文件,第二阶段是在32位不分页的保护模式下装载,主要是装载核心、初始化物理内存布局、初始化分页,完成这些后,才开始运行真正的系统。

2. 16位装载程序的作用

16位装载程序起着非常关键的作用,因为有很多事情要在实模式完成,比如把各种文件加载进内存。为什么要在实模式下加载,而不是到32位保护模式下进行加载呢?因为进入32位保护模式后,就没有BIOS可用了(不考虑V86模式,因为编程比在实模式下麻烦,还不如直接在实模式下完成),要完成探测内存、读取磁盘什么的工作,就要自己先写好这些硬件的驱动,至少是实现部分功能的。但是这些功能在BIOS中提供了,所以,我就选择在实模式下把需要的文件先加载进内存。

3. 获取内存容量

获取内存容量有几种方法,不能只用一种方法,因为BIOS不一定支持这种方法,如果都不支持,那就只能用最原始的读写比较来探测内存了。 int 15h的具体使用方法放到书里。

3.1 EAX=0E820H调用INT 15H

这个BIOS功能的调用规范就留到书里了,其实下面的代码已经把它封装成C函数,调用的方法也在里面,输出结果也贴在后面了。

#include "stdio.h"
typedef struct _reg_t
{
    unsigned short  _DI, _SI, _BP, _SP, _BX, _DX, _CX, _AX;
    unsigned long   _EDI, _ESI, _EBP, _ESP, _EBX, _EDX, _ECX, _EAX;
    unsigned short  _CS, _DS, _ES, _SS, _FS, _GS;
}reg_t;
/*Values for System Memory Map address type:
01h    memory, available to OS
02h    reserved, not available (e.g. system ROM, memory-mapped device)
03h    ACPI Reclaim Memory (usable by OS after reading ACPI tables)
04h    ACPI NVS Memory (OS is required to save this memory between NVS
sessions)
other  not defined yet -- treat as Reserved
*/
typedef struct _address_range_map_t
{
    unsigned long _BaseLow,_BaseHigh,_SizeLow,_SizeHigh,_Type;
}arm_t;
arm_t arm;
char * strType[] =
{
    "undefined",
    "memory, available to OS",
    "reserved, not available",
    "ACPI Reclaim Memory",
    "ACPI NVS Memory"
};
#define LOW_WORD(dw)                ((unsigned short)(dw))
#define HIGH_WORD(dw)               ((unsigned short)((dw)>>16))
#define SEGMENT(farptr)             HIGH_WORD((unsigned long)(farptr))
#define OFFSET(farptr)              LOW_WORD((unsigned long)(farptr))
int int_15h_E820(void * far pARD, unsigned int nSizeARD, unsigned long * pLeftCnt)
{
    reg_t           reg;
    ;
    reg._EAX = 0xE820;
    reg._EBX = *pLeftCnt;
    reg._ECX = nSizeARD;
    reg._EDX = 0x534D4150;
    reg._ES  = SEGMENT(pARD);
    reg._DI  = OFFSET(pARD);
    asm{
        push    si
        push    di
        lea     si, reg
        mov     di, [si]._DI
        mov     ax, [si]._ES
        mov     es, ax
        db      066h
        mov     ax, [si]._EAX   ;// mov eax, reg._EAX
        db      066h
        mov     bx, [si]._EBX   ;// mov ebx, reg._EBX
        db      066h
        mov     cx, [si]._ECX   ;// mov ecx, reg._ECX
        db      066h
        mov     dx, [si]._EDX   ;// mov edx, reg._EDX
        int     015h
        mov     si, pLeftCnt
        db      066h
        mov     [si], bx        ;// mov [pLeftCnt], ebx
        pop     di
        pop     si
        jc      error
    }
    return 1;
 error:
    return 0;
}
int main(void)
{
    unsigned long   nLeftCnt = 0;
    int             i = 0;
    printf(" ##    base address           size            type\n");
    printf(" --  -----------------  -----------------  --------------------------\n");
    do{
        int_15h_E820(&arm, sizeof(arm_t), &nLeftCnt);
        printf(" %2d  %08lX-%08lX  %08lX-%08lX  %s\n", ++i,
            arm._BaseHigh, arm._BaseLow, 
            arm._SizeHigh, arm._SizeLow,strType[arm._Type % 5]);
    }while( nLeftCnt );
    return 0;
}

使用EAX=0xE820探测内存
上图是一个8G内存虚拟机的探测情况,可以看到有几个地方是需要保留的,有一些地址是断开的
0x000A0000 - 0x000DFFFF 这里有256K
0xF8000000 - 0xFFFFFFFF 这里有128M
然后看有效地址上限,给虚拟机设定的内存是8G,但是可以访问的上限却是8G + 128M,把0xF8000000 - 0xFFFFFFFF这一段给补回来了

3.2 AX=0E801H调用INT 15H

这个方法也算不错了,但是上限是4G内存,而且数量会少256K。如果内存容量超过4G,他的返回结果有些不同。

/*
AX = E801h
Return:
CF clear if successful
AX = extended memory between 1M and 16M, in K (max 3C00h = 15MB)
BX = extended memory above 16M, in 64K blocks
CX = configured memory 1M to 16M, in K
DX = configured memory above 16M, in 64K blocks
CF set on error
*/
int int_15h_E801(unsigned short * pExtMem16M, unsigned short * pExtMem4G,
    unsigned short * pCfgMem16M, unsigned short * pCfgMem4G)
{
    asm{
        mov     ax, 0E801h
        int     015h
        jc      error
        push    si
        mov     si, pExtMem16M
        mov     [si], ax 
        mov     si, pExtMem4G
        mov     [si], bx
        mov     si, pCfgMem16M
        mov     [si], cx
        mov     si, pCfgMem4G
        mov     [si], dx
        pop     si
    }
    return 1;
error:
    return 0;
}

3.3 AX = 088H调用INT 15H

这个是BIOS最早提供的方法,只能得到64M的内存。

在Hyper-V里已经不支持方法,如果用这个方法,得到的结果是0。

unsigned short int_15h_88(void)
{
    unsigned short  nTotal = 0;
    asm{
        mov     ah, 088h
        int     015h
        jc      error
        mov     nTotal, ax
    }
    return nTotal;
error:
    return 0;
}

3.4 读写比较探测

如果以上方法都不行,只能先写入数据,然后读出来比较的方法了。这个方法以“如果这个地址能够访问,那写入后在读出的数据应该一致”为基础的。肯定会有人问,如果碰巧一样呢?那你就写入4个字节,碰巧一样的几率是四十亿分之一,如果还一样,好吧,那就在想别的办法,我懒得想了,而且我觉得在虚拟机下也没得想了。其实,经过测试,发现如果地址不能访问,读出的数据都是0。

这个方法比较麻烦,先要实现在实模式下访问4G地址空间,然后才能访问4G空间,利用在不分页的情况下,线性地址就是物理地址的情况,来测试内存是否可以使用。实际上就是在段寄存器装入一个能访问4G空间的段描述符,然后不要修改这个段寄存器,直接使用段寄存器访问内存就行了。

下面就不贴完整的代码了,贴出关键的部分

;
;void WriteByte4G(byte_t nData, uint32_t nPhyAddr)
;
_WriteByte4G PROC
    mov     bx, sp
    mov     al, [bx + 2]
    mov     ecx, [bx + 4]
    mov     ebx, ecx
    mov     fs:[ebx], al
    ret
_WriteByte4G ENDP
;
;byte_t ReadByte4G(uint32_t nPhyAddr)
;
_ReadByte4G PROC
    mov     bx, sp
    xor     ax, ax
    mov     ecx, [bx + 2]
    mov     ebx, ecx
    mov     al, fs:[ebx]
    ret
_ReadByte4G ENDP

// test memory 
    nSize32 = 0x100000l;
    while( nSize32 < 0xF8000000 ){
        _printf("  testing memory %lu ...\r",nSize32);
        nTmp = ReadByte4G(nSize32);
        WriteByte4G(0xA5, nSize32);
        if( ReadByte4G(nSize32) != 0xA5 )
            break;
        WriteByte4G(nTmp, nSize32);
        nSize32 += 0x400000l; /* detection setp is 1M */
    }

4 综合比较

用这三种方法对同一个分配了8G内存的虚拟机进行测试,代码和运行结果放在下面

int main(void)
{
    unsigned long   nLeftCnt = 0;
    unsigned short  nExtMem16M, nExtMem4G, nCfgMem16M, nCfgMem4G;
    int             i = 0;
    printf("EAX = E8020:\n");
    printf(" ##    base address           size            type\n");
    printf(" --  -----------------  -----------------  -------------------------\n");
    do{
        if( !int_15h_E820(&arm, sizeof(arm_t), &nLeftCnt)  )
            break;
        printf(" %2d  %08lX-%08lX  %08lX-%08lX  %s\n", ++i,
            arm._BaseHigh, arm._BaseLow, 
            arm._SizeHigh, arm._SizeLow,strType[arm._Type % 5]);
    }while( nLeftCnt );
    printf("\nAX = E801\n");
    if( int_15h_E801(&nExtMem16M, &nExtMem4G, &nCfgMem16M, &nCfgMem4G) ){
        printf("Extend memory: %u %u. total: %lu\n", nExtMem16M, nExtMem4G, 
            (unsigned long)nExtMem16M + (unsigned long)nExtMem4G * 64);
        printf("Config memory: %u %u. total: %lu\n", nCfgMem16M, nCfgMem4G, 
            (unsigned long)nCfgMem16M + (unsigned long)nCfgMem4G * 64);
    }
    printf("\nAH = 0x88\n");
    printf("Extend memory: %u\n", int_15h_88());
    return 0;
}

各个内存探测方法的运行结果
从这测试结果来看,我是不使用AH=088h了,如果EAX=0E820和AX=0E801都不行的话,我是采用读写比较的方法了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值