【问题标题】:Cannot read sectors from disk in C无法从C中的磁盘读取扇区
【发布时间】:2020-10-11 16:21:33
【问题描述】:

我正在使用i686-elf-gcc 交叉编译器生成代码以在实模式下运行。

我正在尝试从我的内核中读取一个扇区。我知道这是我的内核所在的位置,在第二扇区,第 0 个驱动器,第 0 个轨道。它构建得很好,但是在我调用 read 之后,sectors_read 仍然是 0。

u8 status;
u8 sectors_read;
read(1, 0, 1, 0, 0, status, sectors_read);
kprint("STATUS: ");
hex_to_ascii(status, num_str_buffer);
kprint(num_str_buffer);
kprint("\nSECTORS READ: ");
num_str_buffer[0] = '\0';
hex_to_ascii(sectors_read, num_str_buffer);
kprint(num_str_buffer);
void read(u8 sector_size, u8 track, u8 sector, u8 head, u8 drive, u8 status, u8 sectors_read)
{
    asm volatile("mov $2, %AH");
    asm volatile("mov %0, %%AL" : : "r"(sector_size) : );
    asm volatile("mov %0, %%CH" : : "r"(track) : );
    asm volatile("mov %0, %%CL" : : "r"(sector) : );
    asm volatile("mov %0, %%DH" : : "r"(head));
    asm volatile("mov %0, %%DL" : : "r"(drive));
    asm volatile("int $0x13");
    asm volatile("mov %%AH, %0":"=r"(status) : );
    asm volatile("mov %%AL, %0":"=r"(sectors_read) : );
}

【问题讨论】:

  • 在汇编中编写整个磁盘读取例程可能更容易。 E:这完全是个人喜好,但我发现内联汇编非常容易出错
  • 您需要所有这些都成为 one asm 语句的一部分;零保证编译器不会为 "r"(track) 操作数选择 AL,例如,覆盖您之前编写的 AL。您也没有告诉编译器您修改的寄存器(缺少一个clobber),所以它完全不安全。这与如何使用 GNU C 内联 asm、阅读教程 (stackoverflow.com/tags/inline-assembly/info) 和/或查看通过编译得到的编译器生成的 asm 正好相反。
  • 另请注意,C 中的参数一如既往地按值传递。您似乎期望调用函数中sectors_read 的值将通过对read 的调用而改变,但由于它是按值传递的,因此无法更改。因此,无论您在函数之后在其中看到什么值,都与之前在其中的未初始化垃圾值相同。你的编译器应该给你各种关于未初始化变量的警告(如果你打开警告和优化)——注意它!
  • 顺便说一句,您似乎没有提供将数据读入任何地方的缓冲区。您希望将数据读取到哪里?
  • 我有一个小项目,它是一个在实模式下运行的 2 阶段引导加载程序(需要 386+ 才能运行)。 github.com/mpetch/OSDev/blob/master/examples/… 。 biosdsk.h 有一个可能有用的内联代码示例。我实现了一些功能,例如磁盘读取(重试)和磁盘重置。我通过结构指针传递磁盘数据,以节省必须将许多参数传递给函数。我还有一个 SO 答案,其中包含适用于此问题的 notes 部分中的信息:stackoverflow.com/a/52047408/3857942

标签: gcc x86-16 inline-assembly disk bios


【解决方案1】:

你的read 函数有很多问题。由于您没有发布可重现的示例,甚至没有足够的细节来了解您正在为什么工具链编程,因此我无法对您的问题给出明确的答案。以下是一些错误的地方:

  • 在 C 中,所有函数参数都是按值传递的。在read 中分配给statussectors_read 不会影响您在被调用者中传递的参数。而是传递一个指针。
  • 您不要告诉编译器您破坏了axcxdx,或者这些寄存器的内容很重要。因此,编译器可能会将它们用于它喜欢的任何目的,从而干扰您想要的语义。要解决此问题,请使用寄存器约束而不是 mov 指令将参数放入正确的寄存器中。
  • 您似乎没有将es:bx 设置为要读取数据的缓冲区地址。因此,BIOS 会将数据读入es:bx 指向的任何地址,实质上是覆盖随机内存。

这是一个如何在read 函数中解决这些问题的示例。请注意,根据您的情况,可能需要进行更多更改。

typedef unsigned char u8;
typedef unsigned short u16;

void read(u8 sector_size, u8 track, u8 sector, u8 head, u8 drive,
    u8 *buffer, u8 *status, u8 *sectors_read)
{
    u16 result;

    asm volatile("push %%cs; pop %%es; int $0x13"
        : "=a"(result)
        : "a"(0x200|sector_size), "b"(buffer),
          "c"(track<<8|sector), "d"(head<<8|drive)
        : "es", "memory");

    *status = result >> 8;
    *sectors_read = result >> 0;
}

上面的代码假设 ia16-gcc。对于 i686-gcc,使用

    asm volatile("int $0x13"
        : "=a"(result)
        : "a"(0x200|sector_size), "b"(buffer),
          "c"(track<<8|sector), "d"(head<<8|drive)
        : "memory");

改为。

请理解,如果没有您的合作以及您对人们在您的问题下所写的 cmets 的回复,不可能提供比这更好的帮助。

【讨论】:

  • 这是使用 ia16-gcc 吗?我问是因为我认为普通的 GCC 不允许 ES 作为破坏者。常规 GCC 需要 CS=DS=ES=SS 并且它不会保存/恢复 ES。我还没有拿出一份 ia16-gcc 来测试它是如何处理事情的。
  • @MichaelPetch 此代码旨在作为 OP 的示例。无论 OP 考虑什么目的,它很可能需要更正。但如果 OP 拒绝回应,我不能说到底需要什么。
  • 你错过了一个"memory" clobber; gcc 需要知道这个 asm 语句已经写入了指向的内存。在我的 error: unsupported size for integer register 版本中,我修复了该问题并删除了 ES 内容,因为常规 GCC 需要 ES=DS=SS 并且不允许 ES clobber。
  • @PeterCordes 谢谢。我在编辑中解决了这个问题。
  • @PeterCordes 我之前确实尝试过,但 gcc 似乎没有兑现它。 Clang当然做到了。与Q 约束相同。
猜你喜欢
  • 2010-12-17
  • 2020-02-03
  • 2023-03-23
  • 2013-11-20
  • 1970-01-01
  • 2013-06-04
  • 2014-04-30
  • 2019-12-05
  • 1970-01-01
相关资源
最近更新 更多