《ORANGE’S:一个操作系统的实现》读书笔记(二十六)文件系统(一)

2024-01-08 12:46:27

到目前为止,我们一直都是在使用软盘,还从来没有接触过硬盘,我们的操作系统也是在软盘上的。那么这篇文章开始,就记录如何实现一个简单的文件系统。这个文件系统将建立在真正的硬盘之上,它通过硬盘驱动程序直接操作硬盘。在编写这个文件系统的过程中,我们在上一篇文章中所实现的IPC机制将发挥作用,你会发现,通过几个消息,用户进程、文件系统和驱动程序之间可以方便地协同工作。顺带着,还将建立一个驱动程序框架的雏形。

硬盘简介

既然我们对硬盘进行操作,我们需要先来认识一下硬盘。虽然大家想必都已经比较熟悉硬盘,但对于一些术语,我们还是我必要来说明一下的。书上这里对硬盘进行了一些说明,把它记录下来,以供阅读和扩展知识。

PC发展到今天,每一个部分细讲起来都是个很长的故事,无论其发展历史还是技术细节,都一言难尽,硬盘也是如此。更加令人迷惑的是,硬盘标准中有太多的术语和名词,让人眼花缭乱,甚至不知所云。在这里,书上将一些重要的术语进行了描述,当然,如果想要了解所有细节,可能需要翻阅一些标准文档,最重要的文档目前由T13技术委员会维护,他们的官方网站位于?http://www.t13.org

很久以前,硬盘控制器和硬盘本身是分离的,直到有一天,西数(Western Digital)、康柏、CDC的一个事业部Imprimis(后来被希捷收购)等合作开发了一种新的接口标准,将硬盘控制器和硬盘合在一起,这一标准被称为IDE(Intergrated Drive Electronics)或者ATA(At Attachment,AT指的是IBM的PC/AT个人计算机)。事实上ATA这一名称更加“正确”,它是接口的“真正的”名字,而IDE这一名称仅仅是在当时区分开了那些控制器和硬盘分离的设计,它不是个标准的名称。不过IDE这个名称被使用得非常广泛,或许比ATA流传还广,有些人干脆称这一接口为IDE/ATA或者ATA/IDE。后来ATA被标准化,今天我们称之为ATA-1。

再后来ATA标准不断发展,陆续出现了ATA-2、ATA-3、ATA/ATAPI-4、ATA/ATAPI-5、ATA/ATAPI-6,以及ATA/ATAPI-7。所谓ATAPI,其实是CD-ROM的接口的真正名称,它是AT Attachment Packet Interface的缩写。ATAPI使得硬盘和CD-ROM的接口统一起来。

你可能还听过其它一些名词,诸如EIDE、FASTATA、FASTATA-2、ULTRAATA等,不要被迷惑,所有这些都是为了让产品听上去比较有新意而起的名字,也就是说,它们都是营销手段,或者是遵循某个标准的产品别名。它们并不是另外的标准。

你或许还听过“温盘”这个名称,它所指代的温彻斯特硬盘(Winchester Disk)其实是1973年IBM一款硬盘驱动器的代号。我们现在的硬盘都是直接或间接建立在温彻斯特技术(Winchester Technology)之上。了解这一事实很重要,因为在一些操作系统的代码中,仍使用Winchester这一名称指代硬盘驱动器。

近几年,一种新的标准出现了,它就是SATA(Serial ATA),或被称为“串口硬盘”,而且它正在逐渐取代原先的ATA。为了区分开来,之前的硬盘则被称为“并口硬盘”,原先ATA的叫法也相应地改为PATA(Parallel ATA)。ATA/ATAPI-7的文档包含了SATA的内容。从我们想要编写相当低级的驱动程序的角度来看,由于SATA通常兼容PATA的操作方式,所以暂时地,我们姑且认为所有的硬盘都是无差别的ATA硬盘。

如果你下载了ATA/ATAPI的技术文档,会看到有很多标准内容,但是我们能用到的却很少。

硬盘操作的 I/O 端口

跟我们之前接触过的键盘控制器、VGA控制器等硬件类似,对硬盘控制器的操作仍是通过I/O端口来进行,这些端口分为两组,它们对应命令块寄存器(Command Block Registers)和控制块寄存器(Control Block Register),如下表所示。

组别I/O 端口读时写时
PrimarySecondary
Command Block Registers1F0h170hDataData
1F1h171hErrorFeatures
1F2h172hSector CountSector Count
1F3h173hLBA LowLBA Low
1F4h174hLBA MidLBA Mid
1F5h175hLBA HighLBA High
1F6h176hDeviceDevice
1F7h177hStatusCommand
Control Block Register3F6h376hAlternateStatusDeviceControl

上表中的Primary和Secondary指的是ATA接口通道(Channel),通俗地说就是主板上的IDE口。一个普通的PC主板上通常有两个IDE口,分别对应两个IDE通道:Primary和Secondary,它们有时也被标注为IDE0和IDE1。每个IDE通道又能连接两个设备,设为主设备(Master)和从设备(Slave)。对不同的IDE通道的访问是通过I/O端口来区分的,对同一IDE通道上的主从设备的访问是通过Device寄存器上的第4位的值来区分的——第4位为0是操作主设备,为1时操作从设备。事实上一个机器不只允许有两个IDE通道,但超过两个的情况非常罕见,在此不做介绍。在书中,只考虑硬盘接在Primary通道的情况。

对硬盘的操作并不复杂,只需先往命令块寄存器(Command Block Registers)写入正确的值,再通过控制块寄存器(Control Block Register)发送命令就可以了。当然或许每个命令会有不同的编程细节,我们在遇见具体情况时再做介绍。

硬盘驱动程序

驱动程序的作用在于隐藏硬件细节,向上层进程提供统一的接口。由于我们的进程通过收发消息来相互通信,那么驱动程序的接口自然也是消息了。所以只要我们定义了驱动程序可以接收什么消息,也就定义了驱动程序的接口。为简单起见,我们先只定义一种消息:DEV_OPEN。我们稍后通过FS任务向硬盘驱动程序发送一个DEV_OPEN消息。可是硬盘驱动程序收到这个消息之后干点什么呢?我们还是先干点简单工作:向硬盘驱动器发送一个IDENTIFY命令,这个命令可以用来获取硬盘参数。

向硬盘发送IDENTIFY命令很简单,只需要通过Device寄存器的第4位指定驱动器——0表示Master,1表示Slave——然后往Command寄存器写入十六进制ECh就可以。硬盘准备好参数后,会产生一个中断,这时我们就可以通过Data寄存器读取数据了。参数有很多,总共是256个字(WORD),在下面的程序中,我们仅仅取出其中的几个值来显示。我们来看代码。

代码 kernel/hd.c,硬盘驱动,这是一个新建的文件。

PRIVATE void init_hd();
PRIVATE void hd_cmd_out(struct hd_cmd* cmd);
PRIVATE int waitfor(int mask, int val, int timeout);
PRIVATE void interrupt_wait();
PRIVATE void hd_identify(int drive);
PRIVATE void print_identify_info(u16* hdinfo);

PRIVATE u8 hd_status;
PRIVATE u8 hdbuf[SECTOR_SIZE * 2];

/* task_hd */
/* Main loop of HD driver */
PUBLIC void task_hd()
{
    MESSAGE msg;

    init_hd();

    while (1) {
        send_recv(RECEIVE, ANY, &msg);

        int src = msg.source;

        switch (msg.type) {
            case DEV_OPEN:
                hd_identify(0);
                break;
            default:
                dump_msg("HD driver::unknown msg", &msg);
                spin("FS::main_loop (invalid msg.type)");
                break;
        }

        send_recv(SEND, src, &msg);
    }
}

/**
 * <Ring 1> Check hard drive, set IRQ handler, enable IRQ and initialize data structures.
 */
PRIVATE void init_hd()
{
    /* Get the number of drives from the BIOS data area */
    u8 * pNrDrives = (u8*)(0x475);
    printl("NrDrives:%d.\n", *pNrDrives);
    assert(*pNrDrives);

    put_irq_handler(AT_WINI_IRQ, hd_handler);
    enable_irq(CASCADE_IRQ);
    enable_irq(AT_WINI_IRQ);
}

/**
 * <Ring 1> Get the disk information.
 * 
 * @param drive Drive Nr. 
 */
PRIVATE void hd_identify(int drive)
{
    struct hd_cmd cmd;
    cmd.device = MAKE_DEVICE_REG(0, drive, 0);
    cmd.command = ATA_IDENTIFY;
    hd_cmd_out(&cmd);
    interrupt_wait();
    port_read(REG_DATA, hdbuf, SECTOR_SIZE);

    print_identify_info((u16*)hdbuf);
}

/**
 * <Ring 1> Print the hdinfo retrieved via ATA_IDENTIFY command.
 * 
 * @param hdinfo The buffer read from the disk i/o port.
 */
PRIVATE void print_identify_info(u16* hdinfo)
{
    int i, k;
    char s[64];

    struct iden_info_ascii {
        int idx;
        int len;
        char * desc;
    } iinfo[] = {{10, 20, "HD SN"}, /* Serial Number in ASCII */
                 {27, 40, "HD Model"} /* Model number in ASCII */
                };
    
    for (k = 0; k < sizeof(iinfo)/sizeof(iinfo[0]); k++) {
        char * p = (char*)&hdinfo[iinfo[k].idx];
        for (i = 0; i < iinfo[k].len/2; i++) {
            s[i * 2 + 1] = *p++;
            s[i * 2] = *p++;
        }
        s[i * 2] = 0;
        printl("%s: %s\n", iinfo[k].desc, s);
    }

    int capabilities = hdinfo[49];
    printl("LBA supported: %s\n", (capabilities & 0x0200 ? "Yes" : "No"));

    int cmd_set_supported = hdinfo[83];
    printl("LBA48 supported: %s\n", (cmd_set_supported & 0x0400) ? "Yes" : "No");

    int sectors = ((int)hdinfo[61] << 16) + hdinfo[60];
    printl("HD size: %dMB\n", sectors * 512 / 1000000);
}

/**
 * <Ring 1> Output a command to HD controller.
 * 
 * @param cmd The command struct ptr.
 */
PRIVATE void hd_cmd_out(struct hd_cmd * cmd)
{
    /**
     * For all commands, the host first check if BSY=1,
     * and should proceed no further unless and unitl BSY=0
     */
    if (!waitfor(STATUS_BSY, 0, HD_TIMEOUT)) {
        panic("hd error");
    }

    /* Active the Interrupt Enable (nIEN) bit */
    out_byte(REG_DEV_CTRL, 0);
    /* Load required parameters in the Command Block Registers */
    out_byte(REG_FEATURES, cmd->features);
    out_byte(REG_NSECTOR, cmd->count);
    out_byte(REG_LBA_LOW, cmd->lba_low);
    out_byte(REG_LBA_MID, cmd->lba_mid);
    out_byte(REG_LBA_HIGH, cmd->lba_high);
    out_byte(REG_DEVICE, cmd->device);
    /* Write the command code to the Command Register */
    out_byte(REG_CMD, cmd->command);
}

/**
 * <Ring 1> Wait until a disk interrupt occurs.
 */
PRIVATE void interrupt_wait()
{
    MESSAGE msg;
    send_recv(RECEIVE, INTERRUPT, &msg);
}

/**
 * <Ring 1> Wait for a certain status.
 * 
 * @param mask      Status mark.
 * @param val       Required status.
 * @param timeout   Timeout in milliseconds.
 * 
 * @return One if success, zero if timeout.
 */
PRIVATE int waitfor(int mask, int val, int timeout)
{
    int t = get_ticks();

    while (((get_ticks() - t) * 1000 / HZ) < timeout) {
        if ((in_byte(REG_STATUS) & mask) == val) {
            return 1;
        }
    }

    return 0;
}

/**
 * <Ring 0> Interrupt handler.
 * 
 * @param irq   IRQ nr of the disk interrupt
 */
PUBLIC void hd_handler(int irq)
{
    /**
     * Interrupts are cleared when the host
     *      - reads the Status Register,
     *      - issues a reset, or
     *      - writes to the Command Register.
     */
    hd_status = in_byte(REG_STATUS);

    inform_int(TASK_HD);
}

在代码中总共有这么几个函数:

  • task_hd()
  • init_hd()
  • hd_identify()
  • print_identify_info()
  • hd_cmd_out()
  • interrupt_wait()
  • waitfor()
  • hd_handler()

task_hd()是硬盘驱动的主循环,它跟我们之前的任务没什么区别。开头调用init_hd()做一些初始化工作,然后开始不停地处理消息。我们目前只接收DEV_OPEN消息,收到它之后将会调用hd_identify()来获取并打印部分硬盘参数。

init_hd()中所做的工作分两部分。它显示从物理地址0x475处获取系统内硬盘数量——这个地址是由BIOS指定的。然后它指定hd_handler()为硬盘中断处理程序,并且打开硬盘中断。注意,这里不仅仅需要打开硬盘对应的中断信号线,还要打开主8259A用于级联从片的信号线。

hd_identify()便是获取硬盘参数的函数了。我们先来看看看Device寄存器的格式,如下图所示。

可以看到,寄存器主要有三部分:LBA模式位、DRV位和低四位。我们之前已经提到两次。DRV位(第4位)用于指定主盘或从盘,0表示主盘,1表示从盘。LBA模式位用于指定操作模式,当此位为0时,对磁盘的操作使用CHS模式,也即用“柱面/磁头/扇区号”来定位扇区;当此位为1时,对磁盘的操作使用LBA(Logical Block Address)模式。由于现代硬盘都支持LBA模式,在书中使用的也是LBA模式。寄存器的低四位,在CHS模式中表示磁头号,在LBA模式中表示LBA的24到27位。也就是说,LBA的地址被拆分成了四部分,第0到7为由LBA Low寄存器指定,第8到15位由LBA Mid寄存器指定,第16到23位由LBA High寄存器指定。这个LBA是28位的,经过计算可得知,2的28次方是268435456,这么多个扇区意味着128GB的容量,这也是28位LBA的最大寻址能力。

可是目前市面上的硬盘容量早就超过了128GB了,怎么突破限制呢?原理也很简单,每次读写的时候,三个寄存器——LBA Low、LBA Mid、LBA High——每个使用两次,这样就相当于有了6个寄存器来存储地址,这就是LBA48(ATA-6开始支持)。48位LBA的寻址上限为128PB,我们觉得并希望它是足够了,但是谁知道呢,或许不久的以后每个寄存器将不得不使用三次——我们已经见到太多例子,人们不停在想办法突破自己之前设下的限制,但当下一次做决定时,还是会低估技术的发展速度。

好了言归正传,由于Device这个寄存器有这么一点复杂,我们于是用一个宏来合成它,见下面代码。

代码 include/hd.h,MAKE_DEVICE_REG。

/* for DEVICE register. */
#define MAKE_DEVICE_REG(lba, drv, lba_highest) (((lba) << 6) | ((drv) << 4) |   (lba_highest & 0xF) | 0xA0)

我们在代码61行用到了这个宏。

在接下来的63行,我们用了一个函数hd_cmd_out()来进行实际的向硬盘驱动器发送命令的工作,因为今后我们还会用到它。当发送命令之后,我们用interrupt_wait()这一函数来等待中断的发生,而等待的方法是调用了一个接收消息的函数。说到这里,在上一篇文章的代码里,我们在进行消息处理时,有一种单独的消息来源叫做INTERRUPT,在这里就用到了。之所以发生中断后还要通过消息来通知驱动程序,是因为驱动程序是运行在非内核态的独立进程,而发生中断这一事件是独立于进程的。驱动程序获知中断发生的情形可以参考下图,图中列出了两种情形。

第一种情况中各时间点/段的状况是这样的:

ω硬盘驱动调用hd_cmd_out()。
ω~α硬盘已处于工作状态,同时硬盘驱动也在运行——执行hd_cmd_out()到send_recv()之间的少量代码。
α硬盘驱动调用send_recv(),由于此时硬盘尚未完成工作,所以被阻塞,控制权于是交给Proc X。
α~βProc X运行中,同时硬盘也在工作。
β硬盘完成工作,并触发中断,执行中断处理程序hd_handler()(它应被视为内核的一部分,因为它工作在Ring0),它最终调用inform_int(),这会解除硬盘驱动的阻塞。
γhd_handler()结束,控制权回到Proc X手中。
δProc X的时间片用完,调度程序将控制权交给硬盘驱动(它早在β时已被解除阻塞)。

第二种情况是这样的:

ω‘硬盘驱动执行完hd_cmd_out()之后便由调度程序将控制权交给另一个进程Proc X。
ω‘~β’Proc X运行的同时,硬盘在工作。
β’硬盘完成工作,并触发中断,执行中断处理程序hd_handler(),它最终调用inform_int()。
γ’hd_handler()结束,控制权还给Proc X。
δ’Proc X的时间片用完,调用程序将控制权交给硬盘驱动。
α’硬盘驱动执行send_recv()得到消息,并继续执行。

这两种情况的区别在于,第一种情况调用send_recv()时无法立刻返回,因为那时硬盘还没有完成任务,而第二种情况调用send_recv()时直接返回,因为硬盘已经把数据准备好了。不管是哪种情况,从驱动程序的角度来看,都是等待一个中断的发生,而且它应该能够等到。

当中断发生以后,接下来的第65行从DATA端口读取数据,这样硬盘参数就得到了。

接下来的print_identify_info()选取了其中几个参数打印了出来。它的偏移和解释见下表所示,如果大家有兴趣,可以从T13网站上下载AT Attachment with Packet Interface文档以获得更详细的参数列表。

通过IDENTIFY命令得到的硬盘参数(节选)。

偏移描述
10~19序列号(20个ASCII字符)
27~46型号(40个ASCII字符)
60~61用户可用的最大扇区数
49功能(Capabilities)(bit 9 为 1 表示支持 LBA)
83支持的命令集(bit 10 为 1 表示支持 48 位寻址)

到这里我们获取硬盘参数并打印的整体流程已经清楚了。下面我们就来看看用来向硬盘发送命令的函数hd_cmd_out()。它的一开头先判断硬盘Status寄存器(参考下图9.3)的BSY位,因为只有这一位为0时我们才能行动。若BSY为零则先通过Device Control寄存器(参考下图9.4)打开中断,再依次写Features、Sector Count、LBA Low、LBA Mid、LBA High、Device和Command等寄存器。一旦写入Command寄存器,命令便被执行了。函数hd_cmd_out()接收的参数类型为一结构体,它里面保存的是我们要写入的寄存器应有的值。

代码第172行处是一个只有两行代码的硬盘中断处理程序,它显示通过读Status寄存器来恢复中断响应,然后调用inform_int()来通知驱程序。

再次说明一下,硬盘中断处理程序虽然也放在hd.c中,但它跟其它代码有着本质的不同。中断处理程序运行在RIng0,是内核的一部分,而其它代码都是驱动程序进程运行时调用,都运行在RIng1。

好了,现在我们来看一下代码中用到的其它几个函数的内容,首先是在函数task_hd()中调用的dump_msg()函数,该函数的作用是用来打印消息结构体MESSAGE的内容,具体代码如下所示。

代码 kernel/proc.c,dump_msg。

PUBLIC void dump_msg(const char * title, MESSAGE* m)
{
    int packed = 0;
    printl("{%s}<0x%x>{%ssrc:%s(%d),%stype:%d,%s(0x%x, 0x%x, 0x%x, 0x%x, 0x%x, 0x%x)%s}%s",
           title,
           (int)m,
           packed ? "" : "\n        ",
           proc_table[m->source].p_name,
           m->source,
           packed ? " " : "\n        ",
           m->type,
           packed ? " " : "\n        ",
           m->u.m3.m3i1,
           m->u.m3.m3i2,
           m->u.m3.m3i3,
           m->u.m3.m3i4,
           (int)m->u.m3.m3p1,
           (int)m->u.m3.m3p2,
           packed ? "" : "\n",
           packed ? "" : "\n"
        );
}

第二个是函数inform_int(),作用是用来通知驱动程序,代码如下所示。

/**
 * <Ring 0> Inform a proc that an interrupt has occured.
 * 
 * @param task_nr  The task which will be informed.
 *****************************************************************************/
PUBLIC void inform_int(int task_nr)
{
    PROCESS* p = proc_table + task_nr;

    if ((p->p_flags & RECEIVING) && /* dest is waiting for the msg */
        ((p->p_recvfrom == INTERRUPT) || (p->p_recvfrom == ANY))) {
        p->p_msg->source = INTERRUPT;
        p->p_msg->type = HARD_INT;
        p->p_msg = 0;
        p->has_int_msg = 0;
        p->p_flags &= ~RECEIVING; /* dest has received the msg */
        p->p_recvfrom = NO_TASK;
        assert(p->p_flags == 0);
        unblock(p);

        assert(p->p_flags == 0);
        assert(p->p_msg == 0);
        assert(p->p_recvfrom == NO_TASK);
        assert(p->p_sendto == NO_TASK);
    }
    else {
        p->has_int_msg = 1;
    }
}

第三个函数是port_read(),用于从指定的端口中读取数据,代码如下所示。

代码 lib/kliba.asm,port_read。

...
global port_read
...
; void port_read(u16 port, void* buf, int n);
port_read:
    mov edx, [esp + 4] ; port
    mov edi, [esp + 4 + 4] ; buf
    mov ecx, [esp + 4 + 4 + 4] ; n
    shr ecx, 1
    cld
    rep insw
    ret

最后我们来看一下结构体hd_cmd的定义和代码中用到的和硬盘驱动相关宏的定义。

代码 include/hd.h,hd_cmd和宏定义。

struct hd_cmd {
    u8  features;
    u8  count;
    u8  lba_low;
    u8  lba_mid;
    u8  lba_high;
    u8  device;
    u8  command;
};

/* Command Block Registers */
#define REG_DATA        0x1F0
#define REG_FEATURES    0x1F1
#define REG_ERROR       REG_FEATURES
#define REG_NSECTOR     0x1F2 /* Sector Count I/O */
#define REG_LBA_LOW     0x1F3 /* Sector Number / LBA Bits 0-7 I/O */
#define REG_LBA_MID     0x1F4 /* Cylinder Low / LBA Bits 8-15 I/O */
#define REG_LBA_HIGH    0x1F5 /* Cylinder High / LBA Bits 16-23 I/O  */
#define REG_DEVICE      0x1F6
#define REG_STATUS      0x1F7
#define REG_CMD         REG_STATUS
#define REG_DEV_CTRL    0x3F6
#define REG_ALT_STATUS  REG_DEV_CTRL
#define REG_DRV_ADDR    0x3F7

#define STATUS_BSY  0x80

/* DEFINITIONS */
#define HD_TIMEOUT              10000 /* in millisec */
#define PARTITION_TABLE_OFFSET  0x1BE
#define ATA_IDENTIFY            0xEC
#define ATA_READ                0x20
#define ATA_WRITE               0x30

硬盘中断的信号线是连在8259A从片上的,这是我们第一次用到从片,之前的文章中记录了处理主片的宏,现在我们就按照处理主片宏的方式写一个处理从片的宏,代码如下所示。

代码 kernel/kernel.asm,hwint_slave。

%macro  hwint_slave     1
        call    save
        in      al, INT_S_CTLMASK       ; `.
        or      al, (1 << (%1 - 8))     ; | 屏蔽当前中断
        out     INT_S_CTLMASK, al       ; /
        mov     al, EOI                 ; `. 置EOI位(master)
        out     INT_M_CTL, al           ; /
        nop                             ; `. 置EOI位(slave)
        out     INT_S_CTL, al           ; / 一定注意:salve和master都要置EOI
        sti                             ; CPU在响应中断的过程中会自动关闭中断,这句之后就允许响应新的中断
        push    %1                      ; `.
        call    [irq_table + 4 * %1]    ; | 中断处理程序
        pop     ecx                     ; /
        cli
        in      al, INT_S_CTLMASK       ; `.
        and     al, ~(1 << (%1 - 8))    ; | 恢复接受当前中断
        out     INT_S_CTLMASK, al       ; /
        ret
%endmacro

一定要注意,从片连接的设备发生中断之后,从片和主片都要置EOI,忘掉主片的话,硬盘就再也响应不了中断了。

好了,硬盘驱动程序暂时就这些内容了,我们来看看使用它的FS进程,见下面代码。

代码 fs/main.c,新建立的文件系统进程,该.c文件是一个新文件。

/**
 * <Ring 1> The main loop of TASK FS.
 */
PUBLIC void task_fs()
{
    printl("Task FS begins.\n");

    /* open the device: hard disk */
    MESSAGE dirver_msg;
    dirver_msg.type = DEV_OPEN;
    send_recv(BOTH, TASK_HD, &dirver_msg);

    spin("FS");
}

可以看到,我们新建立的文件系统进程其实就是个空壳,它什么都没有做,只是向硬盘驱动程序发送一个DEV_OPEN消息。跟之前的所有进程一样,FS进程会在系统启动时开始运行,所以我们在启动操作系统之后很快硬盘驱动就会接收到DEV_OPEN消息,并且获取硬盘参数并进行一些打印工作。

现在我们把写好的硬盘驱动程序和FS进程配置到global.c的task_table[]数组中:

PUBLIC TASK task_table[NR_TASKS] = {{task_tty, STACK_SIZE_TTY, "task_tty"},
                                    {task_sys, STACK_SIZE_SYS, "task_sys"},
                                    {task_hd, STACK_SIZE_HD, "task_hd"},
                                    {task_fs, STACK_SIZE_FS, "task_fs"}};

不要忘记在相应的文件中定义有关的宏和声明方法。

下面我们就该运行查看效果了,不过我们的机器目前还没有硬盘,这不要紧,我们使用 bximage 命令造一个硬盘出来:

硬盘的大小和名称可以根据你的想法进行设置。好了,简单的操作就创建出了一块硬盘。更好的是,bximage把应该把什么放进Bochs配置文件都给列出来了,我们马上来修改bochsrc,相当于把新造好的硬盘插入机器。

ata0: enabled=true, ioaddr1=0x1f0, ioaddr2=0x3f0, irq=14
ata0-master: type=disk, path="hd.img", mode=flat

下面我们就可以运行查看效果了,但还是要提醒一下,我们新添加了两个C文件,不要忘记更改Makefile。运行效果如下图所示。

真是太棒了,我们的操作系统成功获取了硬盘参数。

如果你有兴趣,完全可以对照ATA文档,打印出更多硬盘参数。

欢迎关注我的公众号

文章来源:https://blog.csdn.net/zorro_z/article/details/135453470
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。