【问题标题】:Assembly: Unable to read sectors after the first track程序集:第一条磁道后无法读取扇区
【发布时间】:2014-08-26 15:09:38
【问题描述】:

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

从 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");
}

【问题讨论】:

    标签: gcc x86 inline-assembly bios floppy


    【解决方案1】:

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

    Int 13h/ah=2 采用 CHS 值。您可以使用以下公式(或等效公式)将 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 answer 中写了更多关于 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
    

    可以在on my website找到一个在 GCC 中创建 2 阶段引导加载程序的小型概念验证项目。


    注意事项

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

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

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

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

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

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

    • GCC 没有实模式20-bit segment offset addressing 的概念,这使事情变得过于复杂和臃肿。除了使用 GCC,还有其他选择可以在 C 中开发 16 位代码,例如 Open Watcom C/C++; Alexey Frunze 的Smaller C 编译器;或实验性的ia16-gcc cross compiler port of GCC

    【讨论】:

      【解决方案2】:

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

      【讨论】:

        猜你喜欢
        • 2020-10-11
        • 1970-01-01
        • 2014-10-16
        • 2010-12-17
        • 2023-03-23
        • 2016-03-04
        • 2020-02-03
        • 2013-11-20
        • 2013-06-04
        相关资源
        最近更新 更多