0

作为我的操作系统的一部分,我编写了这个读取扇区函数。

从 BIOS 设备 ID 读取扇区地址。但是当我设置从第 19 扇区(磁头:0,磁道:1,第 2 扇区)读取时,0x1000:0x0000 处的结果可能超过了该扇区(我用十六进制查看器检查了几次)。

此外,当我读取多个扇区时,包括扇区 19,在上述地址,我可以毫无问题地读取在 0x1000:(512*19) 处复制的扇区 19。

void __NOINLINE resetDisk(const int device_id) {
    __asm__ __volatile__("" : : "d"(0x0000|device_id)); //set device id
    __asm__ __volatile__("mov $0x0000,%ax"); //function 0x02
    __asm__ __volatile__("int $0x13");
}

void __NOINLINE readDiskSector(const int sector, const int device_id) {
    resetDisk(device_id);

    int sector_count = 2880;
    int heads = 2;
    int tracks = 18;

    int h = sector/(sector_count/heads);
    int c = (sector-h*(sector_count/heads))/tracks;
    int s = sector-c*tracks-h*(sector_count/heads)+1;

    __asm__ __volatile__("push %es");

    __asm__ __volatile__("" : : "a"(c));
    __asm__ __volatile__("" : : "b"(s));
    __asm__ __volatile__("mov %al,%ch");
    __asm__ __volatile__("mov %bl,%cl");
    __asm__ __volatile__("" : : "a"(h));
    __asm__ __volatile__("" : : "b"(device_id));
    __asm__ __volatile__("mov %al,%dh");
    __asm__ __volatile__("mov %bl,%dl");

    __asm__ __volatile__("mov $0x03,%si");
    __asm__ __volatile__("try_again_reading:");
    __asm__ __volatile__("cmp $0x00,%si");
    __asm__ __volatile__("je stop_trying");
    __asm__ __volatile__("mov $0x1000,%bx");
    __asm__ __volatile__("mov %bx,%es");
    __asm__ __volatile__("mov $0x0000,%bx");
    __asm__ __volatile__("mov $0x02,%ah");
    __asm__ __volatile__("mov $0x01,%al");
    __asm__ __volatile__("int $0x13");
    __asm__ __volatile__("dec %si");
    __asm__ __volatile__("jc try_again_reading");
    __asm__ __volatile__("stop_trying:");
    __asm__ __volatile__("pop %es");
}
4

2 回答 2

2

从正确的 GCC 基本内联汇编和扩展内联汇编的角度来看,该代码存在一些严重的问题,但从根本上来说,访问逻辑块地址 19 (LBA) 的问题在于计算。LBA 19 是CHS (Cylinder, Head, Sector) = (0, 1, 2),其中 OP 建议它是(Head: 0, Track: 1, Sector 2),这是不正确的。

Int 13h/ah=2CHS值。您可以使用以下公式(或等效公式)将LBA转换为CHS值:

C = (LBA ÷ SPT) ÷ HPC
H = (LBA ÷ SPT) mod HPC
S = (LBA mod SPT) + 1
HPC = Heads per cylinder (aka Number of Heads)
SPT = Sectors per Track, 
LBA = logical block address
"mod" is the modulo operator (to get the remainder of a division)

我在LBA 到 CHS 的翻译部分的其他Stackoverflow 答案中写了更多关于LBACHS计算的内容。

一个观察结果是,最大扇区数根本没有考虑到等式中。这里真正的问题是 OP 的公式不正确:

int sector_count = 2880;
int heads = 2;   /* Head per cylinder */
int tracks = 18; /* Sectors per Track */

int h = sector/(sector_count/heads);
int c = (sector-h*(sector_count/heads))/tracks;
int s = sector-c*tracks-h*(sector_count/heads)+1;

等式中唯一产生正确结果的部分(以循环方式)是s(扇区)。c(圆柱)和h(头部)计算不正确。因此,它导致了在问题和 OP 的后续答案中观察到的问题。为了了解 OP 方程产生的值,我编写了一个程序,使用适当的公式将它们的值与正确的值进行比较:

LBA =    0:   CHS = ( 0,  0,  1)    |    CHS = ( 0,  0,  1)
LBA =    1:   CHS = ( 0,  0,  2)    |    CHS = ( 0,  0,  2)
LBA =    2:   CHS = ( 0,  0,  3)    |    CHS = ( 0,  0,  3)
LBA =    3:   CHS = ( 0,  0,  4)    |    CHS = ( 0,  0,  4)
LBA =    4:   CHS = ( 0,  0,  5)    |    CHS = ( 0,  0,  5)
LBA =    5:   CHS = ( 0,  0,  6)    |    CHS = ( 0,  0,  6)
LBA =    6:   CHS = ( 0,  0,  7)    |    CHS = ( 0,  0,  7)
LBA =    7:   CHS = ( 0,  0,  8)    |    CHS = ( 0,  0,  8)
LBA =    8:   CHS = ( 0,  0,  9)    |    CHS = ( 0,  0,  9)
LBA =    9:   CHS = ( 0,  0, 10)    |    CHS = ( 0,  0, 10)
LBA =   10:   CHS = ( 0,  0, 11)    |    CHS = ( 0,  0, 11)
LBA =   11:   CHS = ( 0,  0, 12)    |    CHS = ( 0,  0, 12)
LBA =   12:   CHS = ( 0,  0, 13)    |    CHS = ( 0,  0, 13)
LBA =   13:   CHS = ( 0,  0, 14)    |    CHS = ( 0,  0, 14)
LBA =   14:   CHS = ( 0,  0, 15)    |    CHS = ( 0,  0, 15)
LBA =   15:   CHS = ( 0,  0, 16)    |    CHS = ( 0,  0, 16)
LBA =   16:   CHS = ( 0,  0, 17)    |    CHS = ( 0,  0, 17)
LBA =   17:   CHS = ( 0,  0, 18)    |    CHS = ( 0,  0, 18)
LBA =   18:   CHS = ( 1,  0,  1)    |    CHS = ( 0,  1,  1)
LBA =   19:   CHS = ( 1,  0,  2)    |    CHS = ( 0,  1,  2)
LBA =   20:   CHS = ( 1,  0,  3)    |    CHS = ( 0,  1,  3)
LBA =   21:   CHS = ( 1,  0,  4)    |    CHS = ( 0,  1,  4)
LBA =   22:   CHS = ( 1,  0,  5)    |    CHS = ( 0,  1,  5)
LBA =   23:   CHS = ( 1,  0,  6)    |    CHS = ( 0,  1,  6)
LBA =   24:   CHS = ( 1,  0,  7)    |    CHS = ( 0,  1,  7)
LBA =   25:   CHS = ( 1,  0,  8)    |    CHS = ( 0,  1,  8)
LBA =   26:   CHS = ( 1,  0,  9)    |    CHS = ( 0,  1,  9)
LBA =   27:   CHS = ( 1,  0, 10)    |    CHS = ( 0,  1, 10)
LBA =   28:   CHS = ( 1,  0, 11)    |    CHS = ( 0,  1, 11)
LBA =   29:   CHS = ( 1,  0, 12)    |    CHS = ( 0,  1, 12)
LBA =   30:   CHS = ( 1,  0, 13)    |    CHS = ( 0,  1, 13)
LBA =   31:   CHS = ( 1,  0, 14)    |    CHS = ( 0,  1, 14)
LBA =   32:   CHS = ( 1,  0, 15)    |    CHS = ( 0,  1, 15)
LBA =   33:   CHS = ( 1,  0, 16)    |    CHS = ( 0,  1, 16)
LBA =   34:   CHS = ( 1,  0, 17)    |    CHS = ( 0,  1, 17)
LBA =   35:   CHS = ( 1,  0, 18)    |    CHS = ( 0,  1, 18)
LBA =   36:   CHS = ( 2,  0,  1)    |    CHS = ( 1,  0,  1)
LBA =   37:   CHS = ( 2,  0,  2)    |    CHS = ( 1,  0,  2)
LBA =   38:   CHS = ( 2,  0,  3)    |    CHS = ( 1,  0,  3)
LBA =   39:   CHS = ( 2,  0,  4)    |    CHS = ( 1,  0,  4)
LBA =   40:   CHS = ( 2,  0,  5)    |    CHS = ( 1,  0,  5)
LBA =   41:   CHS = ( 2,  0,  6)    |    CHS = ( 1,  0,  6)
LBA =   42:   CHS = ( 2,  0,  7)    |    CHS = ( 1,  0,  7)
LBA =   43:   CHS = ( 2,  0,  8)    |    CHS = ( 1,  0,  8)
LBA =   44:   CHS = ( 2,  0,  9)    |    CHS = ( 1,  0,  9)
LBA =   45:   CHS = ( 2,  0, 10)    |    CHS = ( 1,  0, 10)
LBA =   46:   CHS = ( 2,  0, 11)    |    CHS = ( 1,  0, 11)
LBA =   47:   CHS = ( 2,  0, 12)    |    CHS = ( 1,  0, 12)
LBA =   48:   CHS = ( 2,  0, 13)    |    CHS = ( 1,  0, 13)
LBA =   49:   CHS = ( 2,  0, 14)    |    CHS = ( 1,  0, 14)
LBA =   50:   CHS = ( 2,  0, 15)    |    CHS = ( 1,  0, 15)
LBA =   51:   CHS = ( 2,  0, 16)    |    CHS = ( 1,  0, 16)
LBA =   52:   CHS = ( 2,  0, 17)    |    CHS = ( 1,  0, 17)
LBA =   53:   CHS = ( 2,  0, 18)    |    CHS = ( 1,  0, 18)
LBA =   54:   CHS = ( 3,  0,  1)    |    CHS = ( 1,  1,  1)
LBA =   55:   CHS = ( 3,  0,  2)    |    CHS = ( 1,  1,  2)
LBA =   56:   CHS = ( 3,  0,  3)    |    CHS = ( 1,  1,  3)
LBA =   57:   CHS = ( 3,  0,  4)    |    CHS = ( 1,  1,  4)
LBA =   58:   CHS = ( 3,  0,  5)    |    CHS = ( 1,  1,  5)
LBA =   59:   CHS = ( 3,  0,  6)    |    CHS = ( 1,  1,  6)
LBA =   60:   CHS = ( 3,  0,  7)    |    CHS = ( 1,  1,  7)
LBA =   61:   CHS = ( 3,  0,  8)    |    CHS = ( 1,  1,  8)
LBA =   62:   CHS = ( 3,  0,  9)    |    CHS = ( 1,  1,  9)
LBA =   63:   CHS = ( 3,  0, 10)    |    CHS = ( 1,  1, 10)
LBA =   64:   CHS = ( 3,  0, 11)    |    CHS = ( 1,  1, 11)
LBA =   65:   CHS = ( 3,  0, 12)    |    CHS = ( 1,  1, 12)
LBA =   66:   CHS = ( 3,  0, 13)    |    CHS = ( 1,  1, 13)
LBA =   67:   CHS = ( 3,  0, 14)    |    CHS = ( 1,  1, 14)
LBA =   68:   CHS = ( 3,  0, 15)    |    CHS = ( 1,  1, 15)
LBA =   69:   CHS = ( 3,  0, 16)    |    CHS = ( 1,  1, 16)
LBA =   70:   CHS = ( 3,  0, 17)    |    CHS = ( 1,  1, 17)
LBA =   71:   CHS = ( 3,  0, 18)    |    CHS = ( 1,  1, 18)
LBA =   72:   CHS = ( 4,  0,  1)    |    CHS = ( 2,  0,  1)
LBA =   73:   CHS = ( 4,  0,  2)    |    CHS = ( 2,  0,  2)
LBA =   74:   CHS = ( 4,  0,  3)    |    CHS = ( 2,  0,  3)
LBA =   75:   CHS = ( 4,  0,  4)    |    CHS = ( 2,  0,  4)
LBA =   76:   CHS = ( 4,  0,  5)    |    CHS = ( 2,  0,  5)
LBA =   77:   CHS = ( 4,  0,  6)    |    CHS = ( 2,  0,  6)
LBA =   78:   CHS = ( 4,  0,  7)    |    CHS = ( 2,  0,  7)
LBA =   79:   CHS = ( 4,  0,  8)    |    CHS = ( 2,  0,  8)
...

OP的结果在左边,正确的在右边。LBA 0 到LBA 17 是正确的。如果您开始读取LBA小于 18的一个或多个扇区,那将是正确的。如果您使用为LBA 19 计算的CHS值,它们是不正确的。

OP 在他们的回答中建议柱面和磁头值的文档不正确,并且寄存器是相反的。文档是正确的:

AL = number of sectors to read (must be nonzero)
CH = low eight bits of cylinder number
CL = sector number 1-63 (bits 0-5)
high two bits of cylinder (bits 6-7, hard disk only)
DH = head number
DL = drive number (bit 7 set for hard disk)
ES:BX -> data buffer

OP的回答建议解决方法是交换磁头和气缸。事实上,这恰好使他的代码意外地为LBA 0 到LBA 35工作。LBA >= 36 是不正确的。

解决方法是在 OP 的代码中使用正确的计算:

c = (sector / tracks) / heads;
h = (sector / tracks) % heads;
s = (sector % tracks) + 1;

测试 LBA 到 CHS 方程的代码

#include <stdio.h>

int main()
{
    const int sector_count = 2880;
    const int heads = 2;
    const int tracks = 18; /* tracks per sector */

    unsigned char h, h2;
    unsigned char c, c2;
    unsigned char s, s2;

    int sector; /* LBA */
    for (sector=0; sector < sector_count; sector++) {
        /* Improper calculation */
        h = sector/(sector_count/heads);
        c = (sector-h*(sector_count/heads))/tracks;
        s = sector-c*tracks-h*(sector_count/heads)+1;

        /* Proper calculation */
        c2 = (sector / tracks) / heads;
        h2 = (sector / tracks) % heads;
        s2 = (sector % tracks) + 1;

        printf ("LBA = %4d:   CHS = (%2d, %2d, %2d)    |    CHS = (%2d, %2d, %2d)\n",
                sector, c, h, s, c2, h2, s2);
    }
    return 0;
}

使用内联汇编执行磁盘读取的示例 GCC 代码

biosdisk.h

#ifndef BIOSDISK_H
#define BIOSDISK_H

#include <stdint.h>

/* BIOS Parameter Block (BPB) on floppy media */
typedef struct __attribute__((packed)) {
    char     OEMname[8];
    uint16_t bytesPerSector;
    uint8_t  sectPerCluster;
    uint16_t reservedSectors;
    uint8_t  numFAT;
    uint16_t numRootDirEntries;
    uint16_t numSectors;
    uint8_t  mediaType;
    uint16_t numFATsectors;
    uint16_t sectorsPerTrack;
    uint16_t numHeads;
    uint32_t numHiddenSectors;
    uint32_t numSectorsHuge;
    uint8_t  driveNum;
    uint8_t  reserved;
    uint8_t  signature;
    uint32_t volumeID;
    char     volumeLabel[11];
    char     fileSysType[8];
} disk_bpb_s;

/* State information for CHS disk accesses */
typedef struct __attribute__((packed)) {
    uint16_t segment;
    uint16_t offset;
    uint16_t status;
    /* Drive geometry needed to compute CHS from LBA */
    uint16_t sectorsPerTrack;
    uint16_t numHeads;
    /* Disk parameters */
    uint16_t cylinder;
    uint8_t  head;
    uint8_t  sector;
    uint8_t  driveNum;
    uint8_t  numSectors;    /* # of sectors to read */
    /* Number of retries for disk operations */
    uint8_t  retries;
} disk_info_s;

extern fastcall uint8_t
reset_disk (disk_info_s *const disk_info);
extern fastcall uint8_t
read_sector_chs (disk_info_s *const disk_info);

/* Forced inline version of reset_sector */
static inline fastcall always_inline uint8_t
reset_disk_i (disk_info_s *const disk_info)
{
    uint16_t temp_ax = 0x0000;
    uint8_t  carryf;

    __asm__ __volatile__ (
            "int $0x13\n\t"
#ifdef __GCC_ASM_FLAG_OUTPUTS__
            : [cf]"=@ccc"(carryf),
#else
            "setc %[cf]\n\t"
            : [cf]"=qm"(carryf),
#endif
              "+a"(temp_ax)
            : "d"(disk_info->driveNum)
            : "cc");

    disk_info->status = temp_ax;

    return (carryf);

}

/* Forced inline version of read_sector */
static inline fastcall always_inline uint8_t
read_sector_chs_i (disk_info_s *const disk_info)
{
    uint16_t temp_ax;
    uint16_t temp_dx;
    uint8_t  carryf = 0;
    uint8_t  retry_count = 0;

#ifndef BUGGY_BIOS_SUPPORT
    temp_dx = (disk_info->head << 8) | disk_info->driveNum;
#endif

    do {
        /* Only reset disk if error detected previously */
        if (carryf)
            reset_disk_i (disk_info);

        /* Need to reload AX during each iteration since a previous
         * int 0x13 call will destroy its contents. There was a bug on
         * earlier BIOSes where DX may have been clobbered.
         */

        temp_ax = (0x02 << 8) | disk_info->numSectors;
#ifdef BUGGY_BIOS_SUPPORT
        temp_dx = (disk_info->head << 8) | disk_info->driveNum;
#endif

        __asm__ __volatile__ (
                "push %%es\n\t"
                "mov %w[seg], %%es\n\t"
#ifdef BUGGY_BIOS_SUPPORT
                "stc\n\t"        /* Some early bioses have CF bug */
                "int $0x13\n\t"
                "sti\n\t"        /* Some early bioses don't re-enable interrupts */
#else
                "int $0x13\n\t"
#endif
                "pop %%es\n\t"
#ifdef __GCC_ASM_FLAG_OUTPUTS__
                : [cf]"=@ccc"(carryf),
#else
                "setc %[cf]\n\t"
                : [cf]"=qm"(carryf),
#endif
#ifdef BUGGY_BIOS_SUPPORT
                  "+a"(temp_ax),
                  "+d"(temp_dx)
                  :
#else
                  "+a"(temp_ax)
                  :
                  "d"(temp_dx),
#endif
                  "c"(((disk_info->cylinder & 0xff) << 8) |
                     ((disk_info->cylinder >> 2) & 0xC0) |
                     (disk_info->sector & 0x3f)),
                  "b"(disk_info->offset),
                  [seg]"r"(disk_info->segment)
                : "memory", "cc");

    } while (carryf && (++retry_count < disk_info->retries));

    disk_info->status = temp_ax;
    return (carryf);
}

/* Forced inline version of read_sector_lba */
static inline fastcall always_inline uint8_t
read_sector_lba_i (disk_info_s *const disk_info, const uint32_t lba)
{
    disk_info->cylinder = lba / disk_info->sectorsPerTrack / disk_info->numHeads;
    disk_info->head     = (lba / disk_info->sectorsPerTrack) % disk_info->numHeads;
    disk_info->sector   = (lba % disk_info->sectorsPerTrack) + 1;

    return read_sector_chs_i (disk_info);
}
#endif

biosdisk.c

#include <stdint.h>
#include "biosdisk.h"

fastcall uint8_t
reset_disk (disk_info_s *const disk_info)
{
    return reset_disk_i (disk_info);
}

fastcall uint8_t
read_sector_chs (disk_info_s *const disk_info)
{
    return read_sector_chs_i (disk_info);
}

fastcall uint8_t
read_sector_lba (disk_info_s *const disk_info, const uint32_t lba)
{
    return read_sector_lba_i (disk_info, lba);
}

x86helper.h

#ifndef X86HELPER_H
#define X86HELPER_H

#define fastcall  __attribute__((regparm(3)))

/* noreturn lets GCC know that a function that it may detect
   won't exit is intentional */
#define noreturn      __attribute__((noreturn))
#define always_inline __attribute__((always_inline))
#define used          __attribute__((used))
    
#endif

可以在我的网站上找到一个在 GCC 中创建 2 阶段引导加载程序的小型概念验证项目


笔记

  • Int 13h/AH=0h重置磁盘系统。此操作在软盘等真实硬件上可能需要相当长的时间,因为它还会重新校准驱动器磁头。您应该只在检测到错误之后重试磁盘操作之前重置磁盘。

  • 使用 GCC 创建将在实模式下运行的代码充其量是有问题的。生成的代码-m16一般只能在 80386 或更高版本的处理器上运行。

  • 编译器不保证多个asm语句按照它们在代码中出现的顺序发出。您应该将多个asm语句合并为一个。GCC 文档是这样说的:

不要期望一系列asm语句在编译后保持完全连续,即使您使用的是 volatile 限定符。如果某些指令需要在输出中保持连续,请将它们放在单个多指令asm语句中。

  • 如果你在 GCC 的内联汇编中修改了一个寄存器,你应该告诉编译器。使用 GCC 的扩展内联汇编并在clobber列表中列出修改后的寄存器。

  • 尽量减少内联汇编,并尽可能多地使用C代码。David Wohlferd 写了一篇很好的文章,说明了不使用内联汇编的理由。如果您不了解内联汇编的细微差别,那么您可以考虑在单独的汇编语言模块中编写代码并将其链接到您的C程序。

  • GCC 没有实模式的20 位段偏移寻址的概念,这使事情变得过于复杂和臃肿。除了使用 GCC,还有其他选择可以用 C 语言开发 16 位代码,例如Open Watcom C/C++;Alexey Frunze 的Smaller C编译器;或 GCC 的实验性ia16-gcc 交叉编译器端口

于 2018-08-27T21:48:14.487 回答
0

如果操作失败,ah将更改 in 的值。您的代码假定它不会被更改。

于 2014-07-05T21:13:42.440 回答