kernel源码(二十二)块设备

操作系统所有设备可分为两类:块设备和字符设备。

块设备是一种可以以固定大小的数据块为单位进行寻址和访问的设备,例如硬盘、软盘。

字符设备是一种以字符流作为操作对象的设备,不能进行寻址操作,例如打印机、网卡、终端设备。

为便于管理,操作系统将这些设备统一的以设备号进行分类。linux0.11内核将设备分成7类

主设备号类型说明
0
1块/字符ram,内存设备
2fd,软盘
3hd,硬盘
4字符ttyx,虚拟或串行终端
5字符tty设备
6字符lp打印机设备

代码路径:kernel/blk_drv

1 总体介绍

对hd和fd块设备上的数据的读写操作是通过中断处理程序进行的。内核每次读写的数据量以一个逻辑块(1024byte)为单位。

1.1 一次块设备读写流程

  1. 程序要读取硬盘上的某个块
  2. 向缓冲区管理程序提出申请
  3. 进程进入睡眠等待状态
  4. 首先在缓冲区中寻找这个数据块
  5. 找到则将对应缓冲区块头指针返回给程序
  6. 唤醒进程
  7. 如果缓冲区中未找到该区块
  8. 调用ll_rw_block()向硬盘驱动发送读请求
  9. ll_rw_block()会为此创建一个请求结构项,插入到请求队列里面
  10. 若对应块设备请求项队列为空,说明该设备空闲。内核向该块设备控制器发送读数据命令
  11. 块设备控制器将数据读入指定的缓冲块中
  12. 块设备控制器继续读取其他块或结束本次请求项
  13. 该请求项结束之后,关闭块设备,设置缓冲区数据已更新标志
  14. 唤醒等待该数据块的进程

1.2 块设备请求项

从上面的块设备读取数据的过程我们知道,ll_rw_block()是通过请求项来与各种块设备建立联系并发出读写请求的。

块设备请求结构

struct request {
    int dev;        /* -1 if no request */ //设备号
    int cmd;        /* READ or WRITE */ //读或写
    int errors; //读操作时产生的错误次数
    unsigned long sector; //起始扇区
    unsigned long nr_sectors; //扇区数
    char * buffer; //数据缓冲区
    struct task_struct * waiting; //等待该块设备的任务
    struct buffer_head * bh; //缓冲区头指针
    struct request * next; //指向下一请求项
};

块设备请求项

struct blk_dev_struct {
    void (*request_fn)(void); //处理对应块设备的函数指针,比如do_hd_request()
    struct request * current_request; //当前请求项
};

共有7种设备,使用一个数组blk_dev来存储各种块设备的请求项,数组大小为7

extern struct blk_dev_struct blk_dev[7];

1.3 请求队列

请求项除了在上述blk_dev[i]->current_request中有存放外,还专门设置了一个数组来存放所有设备的请求项,共可容纳32个请求项。这里的request[]就是请求队列

extern struct request request[32];

blk_dev[]数组和request[]队列之间的关系如下图所示

1.4 系统-设备控制器-设备驱动

系统-设备控制器-设备驱动他们之前的关系如下图所示

写盘:

  • 系统首先向控制器发出写请求(hd_out()函数)
  • 控制器查询其内部的寄存器状态位DRQ,决定是否允许写入并应答系统
  • 如果DRQ置位,系统向控制器缓冲区发送一个扇区的数据
  • 控制器把数据写入hd驱动器,接着控制器产生中断信号,此处的中断处理函数是write_intr(),这个函数会查询是否还有数据要写。如果有,则系统再把一个扇区的数据传到控制器缓冲区,然后循环上述过程。
  • 如果所有数据都已经写入驱动器,则write_intr()函数将会执行后处理工作,即唤醒等待该请求项的进程、释放当前请求项并从请求队列(request[])中删除该请求项、释放锁定的相关缓冲区
  • 调用请求项操作函数,执行下一个读/写盘请求。

读盘:

  • 系统向设备控制器发送读请求(起始扇区、扇区数量),等待设备控制器发出中断信号
  • 设备控制器读取一个扇区的数据,放到缓冲区,发出中断请求,这里的中断处理函数是read_intr(),该函数把设备控制器缓冲区的一个扇区的数据放到系统缓冲区中。
  • 递减要读的扇区数量,如果还有扇区要读,则继续等待设备控制器发出下一个中断信号,重复以上过程
  • 如果待读扇区数减为0,则read_intr()函数将会执行后处理工作,即唤醒等待该请求项的进程、释放当前请求项并从请求队列(request[])中删除该请求项、释放锁定的相关缓冲区

2 hd.c

hd.c是硬盘控制器驱动程序,其功能包括:

  • 硬盘初始化程序
  • 对硬盘控制器块设备的读写驱动程序

具体的功能如下

  • 初始化硬盘和设置硬盘所用数据结构信息的函数,sys_setup()和hd_init()
  • 向硬盘控制器(也就是上面提到的设备控制器)发送命令的函数hd_out()
  • 处理硬盘当前请求项的函数do_hd_request()
  • 硬盘中断处理过程中调用的函数:read_init()、write_init()等
  • 一些硬盘控制器操作辅助函数

2.1 源码

/*
 *  linux/kernel/hd.c
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 * This is the low-level hd interrupt support. It traverses the
 * request-list, using interrupts to jump between functions. As
 * all the functions are called within interrupts, we may not
 * sleep. Special care is recommended.
 * 
 *  modified by Drew Eckhardt to check nr of hd's from the CMOS.
 */

#include <linux/config.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/hdreg.h>
#include <asm/system.h>
#include <asm/io.h>
#include <asm/segment.h>

#define MAJOR_NR 3
#include "blk.h"

#define CMOS_READ(addr) ({ \
outb_p(0x80|addr,0x70); \
inb_p(0x71); \
})

/* Max read/write errors/sector */
#define MAX_ERRORS    7
#define MAX_HD        2

static void recal_intr(void);

static int recalibrate = 1;
static int reset = 1;

/*
 *  This struct defines the HD's and their types.
 */
struct hd_i_struct {
    int head,sect,cyl,wpcom,lzone,ctl;
    };
#ifdef HD_TYPE
struct hd_i_struct hd_info[] = { HD_TYPE };
#define NR_HD ((sizeof (hd_info))/(sizeof (struct hd_i_struct)))
#else
struct hd_i_struct hd_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} };
static int NR_HD = 0;
#endif

static struct hd_struct {
    long start_sect;
    long nr_sects;
} hd[5*MAX_HD]={{0,0},};

#define port_read(port,buf,nr) \
__asm__("cld;rep;insw"::"d" (port),"D" (buf),"c" (nr):"cx","di")

#define port_write(port,buf,nr) \
__asm__("cld;rep;outsw"::"d" (port),"S" (buf),"c" (nr):"cx","si")

extern void hd_interrupt(void);
extern void rd_load(void);

/* This may be used only once, enforced by 'static int callable' */
int sys_setup(void * BIOS)
{
    static int callable = 1;
    int i,drive;
    unsigned char cmos_disks;
    struct partition *p;
    struct buffer_head * bh;

    if (!callable)
        return -1;
    callable = 0;
#ifndef HD_TYPE
    for (drive=0 ; drive<2 ; drive++) {
        hd_info[drive].cyl = *(unsigned short *) BIOS;
        hd_info[drive].head = *(unsigned char *) (2+BIOS);
        hd_info[drive].wpcom = *(unsigned short *) (5+BIOS);
        hd_info[drive].ctl = *(unsigned char *) (8+BIOS);
        hd_info[drive].lzone = *(unsigned short *) (12+BIOS);
        hd_info[drive].sect = *(unsigned char *) (14+BIOS);
        BIOS += 16;
    }
    if (hd_info[1].cyl)
        NR_HD=2;
    else
        NR_HD=1;
#endif
    for (i=0 ; i<NR_HD ; i++) {
        hd[i*5].start_sect = 0;
        hd[i*5].nr_sects = hd_info[i].head*
                hd_info[i].sect*hd_info[i].cyl;
    }

    /*
        We querry CMOS about hard disks : it could be that 
        we have a SCSI/ESDI/etc controller that is BIOS
        compatable with ST-506, and thus showing up in our
        BIOS table, but not register compatable, and therefore
        not present in CMOS.

        Furthurmore, we will assume that our ST-506 drives
        <if any> are the primary drives in the system, and 
        the ones reflected as drive 1 or 2.

        The first drive is stored in the high nibble of CMOS
        byte 0x12, the second in the low nibble.  This will be
        either a 4 bit drive type or 0xf indicating use byte 0x19 
        for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS.

        Needless to say, a non-zero value means we have 
        an AT controller hard disk for that drive.

        
    */

    if ((cmos_disks = CMOS_READ(0x12)) & 0xf0)
        if (cmos_disks & 0x0f)
            NR_HD = 2;
        else
            NR_HD = 1;
    else
        NR_HD = 0;
    for (i = NR_HD ; i < 2 ; i++) {
        hd[i*5].start_sect = 0;
        hd[i*5].nr_sects = 0;
    }
    for (drive=0 ; drive<NR_HD ; drive++) {
        if (!(bh = bread(0x300 + drive*5,0))) {
            printk("Unable to read partition table of drive %d\n\r",
                drive);
            panic("");
        }
        if (bh->b_data[510] != 0x55 || (unsigned char)
            bh->b_data[511] != 0xAA) {
            printk("Bad partition table on drive %d\n\r",drive);
            panic("");
        }
        p = 0x1BE + (void *)bh->b_data;
        for (i=1;i<5;i++,p++) {
            hd[i+5*drive].start_sect = p->start_sect;
            hd[i+5*drive].nr_sects = p->nr_sects;
        }
        brelse(bh);
    }
    if (NR_HD)
        printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":"");
    rd_load();
    mount_root();
    return (0);
}

static int controller_ready(void)
{
    int retries=10000;

    while (--retries && (inb_p(HD_STATUS)&0xc0)!=0x40);
    return (retries);
}

static int win_result(void)
{
    int i=inb_p(HD_STATUS);

    if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT))
        == (READY_STAT | SEEK_STAT))
        return(0); /* ok */
    if (i&1) i=inb(HD_ERROR);
    return (1);
}

static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
        unsigned int head,unsigned int cyl,unsigned int cmd,
        void (*intr_addr)(void))
{
    register int port asm("dx");

    if (drive>1 || head>15)
        panic("Trying to write bad sector");
    if (!controller_ready())
        panic("HD controller not ready");
    do_hd = intr_addr;
    outb_p(hd_info[drive].ctl,HD_CMD);
    port=HD_DATA;
    outb_p(hd_info[drive].wpcom>>2,++port);
    outb_p(nsect,++port);
    outb_p(sect,++port);
    outb_p(cyl,++port);
    outb_p(cyl>>8,++port);
    outb_p(0xA0|(drive<<4)|head,++port);
    outb(cmd,++port);
}

static int drive_busy(void)
{
    unsigned int i;

    for (i = 0; i < 10000; i++)
        if (READY_STAT == (inb_p(HD_STATUS) & (BUSY_STAT|READY_STAT)))
            break;
    i = inb(HD_STATUS);
    i &= BUSY_STAT | READY_STAT | SEEK_STAT;
    if (i == READY_STAT | SEEK_STAT)
        return(0);
    printk("HD controller times out\n\r");
    return(1);
}

static void reset_controller(void)
{
    int    i;

    outb(4,HD_CMD);
    for(i = 0; i < 100; i++) nop();
    outb(hd_info[0].ctl & 0x0f ,HD_CMD);
    if (drive_busy())
        printk("HD-controller still busy\n\r");
    if ((i = inb(HD_ERROR)) != 1)
        printk("HD-controller reset failed: %02x\n\r",i);
}

static void reset_hd(int nr)
{
    reset_controller();
    hd_out(nr,hd_info[nr].sect,hd_info[nr].sect,hd_info[nr].head-1,
        hd_info[nr].cyl,WIN_SPECIFY,&recal_intr);
}

void unexpected_hd_interrupt(void)
{
    printk("Unexpected HD interrupt\n\r");
}

static void bad_rw_intr(void)
{
    if (++CURRENT->errors >= MAX_ERRORS)
        end_request(0);
    if (CURRENT->errors > MAX_ERRORS/2)
        reset = 1;
}

static void read_intr(void)
{
    if (win_result()) {
        bad_rw_intr();
        do_hd_request();
        return;
    }
    port_read(HD_DATA,CURRENT->buffer,256);
    CURRENT->errors = 0;
    CURRENT->buffer += 512;
    CURRENT->sector++;
    if (--CURRENT->nr_sectors) {
        do_hd = &read_intr;
        return;
    }
    end_request(1);
    do_hd_request();
}

static void write_intr(void)
{
    if (win_result()) {
        bad_rw_intr();
        do_hd_request();
        return;
    }
    if (--CURRENT->nr_sectors) {
        CURRENT->sector++;
        CURRENT->buffer += 512;
        do_hd = &write_intr;
        port_write(HD_DATA,CURRENT->buffer,256);
        return;
    }
    end_request(1);
    do_hd_request();
}

static void recal_intr(void)
{
    if (win_result())
        bad_rw_intr();
    do_hd_request();
}

void do_hd_request(void)
{
    int i,r;
    unsigned int block,dev;
    unsigned int sec,head,cyl;
    unsigned int nsect;

    INIT_REQUEST;
    dev = MINOR(CURRENT->dev);
    block = CURRENT->sector;
    if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) {
        end_request(0);
        goto repeat;
    }
    block += hd[dev].start_sect;
    dev /= 5;
    __asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
        "r" (hd_info[dev].sect));
    __asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
        "r" (hd_info[dev].head));
    sec++;
    nsect = CURRENT->nr_sectors;
    if (reset) {
        reset = 0;
        recalibrate = 1;
        reset_hd(CURRENT_DEV);
        return;
    }
    if (recalibrate) {
        recalibrate = 0;
        hd_out(dev,hd_info[CURRENT_DEV].sect,0,0,0,
            WIN_RESTORE,&recal_intr);
        return;
    }    
    if (CURRENT->cmd == WRITE) {
        hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
        for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
            /* nothing */ ;
        if (!r) {
            bad_rw_intr();
            goto repeat;
        }
        port_write(HD_DATA,CURRENT->buffer,256);
    } else if (CURRENT->cmd == READ) {
        hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
    } else
        panic("unknown hd-command");
}

void hd_init(void)
{
    blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST;
    set_intr_gate(0x2E,&hd_interrupt);
    outb_p(inb_p(0x21)&0xfb,0x21);
    outb(inb_p(0xA1)&0xbf,0xA1);
}
hd.c

在介绍main.c(https://www.cnblogs.com/zhenjingcool/p/15999035.html)时,我们介绍过如下代码

void main(void)        /* This really IS void, no error here. */
{
    mem_init(main_memory_start,memory_end);
    trap_init();
    blk_dev_init();
    chr_dev_init();
    tty_init();
    time_init();
    sched_init();
    buffer_init(buffer_memory_end);
    hd_init();
    floppy_init();
    sti();
    move_to_user_mode();
    if (!fork()) {        /* we count on this going ok */
        init();
    }

    for(;;) pause();
}

在main.c的main函数中,我们进行了一系列初始化工作,其中上面标红的地方是进行硬盘初始化hd_init()和创建任务1,因为子进程和父进程有相同的代码段,fork调用父进程返回新进程pid,子进程返回0。因此init()函数只会在任务1中执行。我们看一下init()函数,稍后再看hd_init()函数

2.2 sys_setup()

void init(void)
{
    int pid,i;

    setup((void *) &drive_info); //#define DRIVE_INFO (*(struct drive_info *)0x90080)。这里的drive_info是设备信息,在前面介绍的章节中,我们知道,硬盘信息被写到0x90080的地址处
    (void) open("/dev/tty0",O_RDWR,0);
    ...
}

这里setup是一个宏,这个宏最终调用的是sys_setup系统调用函数,也就是hd.c中定义的这个函数

/* This may be used only once, enforced by 'static int callable' */
int sys_setup(void * BIOS) //参数BIOS为指向硬盘参数表结构的指针
{
    static int callable = 1;
    int i,drive;
    unsigned char cmos_disks;
    struct partition *p;
    struct buffer_head * bh;

    if (!callable)
        return -1;
    callable = 0;
#ifndef HD_TYPE //如果没有定义该参数,则,在内存0x90080处加载硬盘信息。
    for (drive=0 ; drive<2 ; drive++) {
        hd_info[drive].cyl = *(unsigned short *) BIOS;
        hd_info[drive].head = *(unsigned char *) (2+BIOS);
        hd_info[drive].wpcom = *(unsigned short *) (5+BIOS);
        hd_info[drive].ctl = *(unsigned char *) (8+BIOS);
        hd_info[drive].lzone = *(unsigned short *) (12+BIOS);
        hd_info[drive].sect = *(unsigned char *) (14+BIOS);
        BIOS += 16;
    }
    if (hd_info[1].cyl)
        NR_HD=2;
    else
        NR_HD=1;
#endif 
    for (i=0 ; i<NR_HD ; i++) {
        hd[i*5].start_sect = 0; //硬盘起始扇区号
        hd[i*5].nr_sects = hd_info[i].head* //硬盘总扇区数
                hd_info[i].sect*hd_info[i].cyl;
    }

    /*
        We querry CMOS about hard disks : it could be that 
        we have a SCSI/ESDI/etc controller that is BIOS
        compatable with ST-506, and thus showing up in our
        BIOS table, but not register compatable, and therefore
        not present in CMOS.

        Furthurmore, we will assume that our ST-506 drives
        <if any> are the primary drives in the system, and 
        the ones reflected as drive 1 or 2.

        The first drive is stored in the high nibble of CMOS
        byte 0x12, the second in the low nibble.  This will be
        either a 4 bit drive type or 0xf indicating use byte 0x19 
        for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS.

        Needless to say, a non-zero value means we have 
        an AT controller hard disk for that drive.

        
    */

    if ((cmos_disks = CMOS_READ(0x12)) & 0xf0) //这个判断是为了检测硬盘是不是AT控制器兼容硬盘
        if (cmos_disks & 0x0f)
            NR_HD = 2;
        else
            NR_HD = 1;
    else
        NR_HD = 0;
    for (i = NR_HD ; i < 2 ; i++) {
        hd[i*5].start_sect = 0;
        hd[i*5].nr_sects = 0;
    }
    for (drive=0 ; drive<NR_HD ; drive++) {
        if (!(bh = bread(0x300 + drive*5,0))) { //bread()函数为读块函数,这里作用是读取硬盘第一个数据块,其中0x300和0x305为两个硬盘的设备号(0x300为第一个硬盘设备号,0x301-0x304为第1-4分区,0x305为第二个硬盘设备号,0x306-0x308为第1-4分区)
            printk("Unable to read partition table of drive %d\n\r",
                drive);
            panic("");
        }
        if (bh->b_data[510] != 0x55 || (unsigned char) //一个扇区512字节,这里判断第一个扇区最后两个字节是否为0x55AA来判断该磁盘是否为启动盘
            bh->b_data[511] != 0xAA) { 
            printk("Bad partition table on drive %d\n\r",drive);
            panic("");
        }
        p = 0x1BE + (void *)bh->b_data; //第一个扇区的0x1BE处存放的是该块硬盘的分区表
        for (i=1;i<5;i++,p++) { //将硬盘分区表信息写入放入硬盘分区结构数组hd[]中
            hd[i+5*drive].start_sect = p->start_sect;
            hd[i+5*drive].nr_sects = p->nr_sects;
        }
        brelse(bh); //释放bh缓冲区,至此,完成硬盘分区结构数组hd[]的任务
    }
    if (NR_HD)
        printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":"");
    rd_load(); //尝试在系统内存虚拟盘中加载启动盘中包含的根文件系统映像
    mount_root(); //安装根文件系统,将在讲解文件系统时详细介绍
    return (0);
}

2.3 hd_init()

在main.c中调用了hd_init()函数进行硬盘的初始化

void hd_init(void)
{
    blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; //设置硬盘设备的请求项处理函数指针为do_hd_request()
    set_intr_gate(0x2E,&hd_interrupt); //设置硬盘中断门描述符,其中hd_interrupt是硬盘中断处理程序。硬盘中断号为0x2E(46),对应8259A芯片的中断请求信号为IRQ13
    outb_p(inb_p(0x21)&0xfb,0x21); //复位8259A主片 int2的屏蔽位
    outb(inb_p(0xA1)&0xbf,0xA1); //复位8259A从片上的硬盘中断请求屏蔽位
}

其中,设置中断门的代码在system.h中定义,如下

#define set_intr_gate(n,addr) \
    _set_gate(&idt[n],14,0,addr) //这里type=14(二进制位110),对照IDT描述符结构我们知道,type=110表示中断门描述符;dpl=0表示特权级为0

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
    "movw %0,%%dx\n\t" \
    "movl %%eax,%1\n\t" \
    "movl %%edx,%2" \
    : \
    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \ //因为GDT表项每一项64位,dpl位于高32位的第13位处,因此dpl<<13;同样的道理type左移8位,前面的8000(1000 0000 0000 0000)表示低32位
    "o" (*((char *) (gate_addr))), \
    "o" (*(4+(char *) (gate_addr))), \
    "d" ((char *) (addr)),"a" (0x00080000))

2.4 hd_out()

该函数的目的是向硬盘控制器(也就是上面提到的设备控制器)发送命令

static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect,
        unsigned int head,unsigned int cyl,unsigned int cmd,
        void (*intr_addr)(void)) //参数drive:硬盘号 nsect:读写扇区数 sect:起始扇区 head:磁头号 cyl:柱面号 cmd:命令码 intr_addr:硬盘中断处理程序中将调用C处理函数指针
{
    register int port asm("dx"); //定义一个寄存器变量,为了能够快速访问

    if (drive>1 || head>15) //校验驱动器和磁头数,如果驱动器大于2个,则panic
        panic("Trying to write bad sector");
    if (!controller_ready()) //循环等待驱动器就绪
        panic("HD controller not ready");
    do_hd = intr_addr; //设置全局函数指针变量do_hd指向硬盘中断处理程序中将会调用的C处理函数
    outb_p(hd_info[drive].ctl,HD_CMD);//向硬盘驱动发送硬盘控制字节
    port=HD_DATA;
    outb_p(hd_info[drive].wpcom>>2,++port); //下面7行,目的是向硬盘驱动发送读写扇区数,起始扇区,柱面号低8位,柱面号高8位,驱动器号+磁头号,硬盘控制命令。这样连续向硬盘控制器(设备控制器)发送了7字节的参数命令。接下来就是硬盘
    outb_p(nsect,++port);
    outb_p(sect,++port);
    outb_p(cyl,++port);
    outb_p(cyl>>8,++port);
    outb_p(0xA0|(drive<<4)|head,++port);
    outb(cmd,++port);
}

controller_ready用于循环等待驱动器就绪,共循环判断10000次

static int controller_ready(void)
{
    int retries=10000;

    while (--retries && (inb_p(HD_STATUS)&0xc0)!=0x40); //HD_STATUS在hdreg.h中定义,表示读写硬盘状态的端口,见下面的附录1中的介绍。这里表示的是读取硬盘状态。驱动器状态用8位数表示,7-0位分别表示[驱动器忙,驱动器准备就绪,...],对照着这个分析这行代码会更容易理解
    return (retries);
}

inb_p、outb_p、inb、outb是io.h中定义的端口输入输出宏,见下面的附录1中的介绍

2.5 do_hd_request()

执行硬盘读写请求操作,在代码中,会调用hd_out()函数向硬盘控制器发送命令,会根据命令是读还是写,分别传递中断处理函数为&read_intr和&write_intr。

在中断处理函数&read_intr和&write_intr中又会调用do_hd_request()函数,直到读写请求完成读写所有扇区。

分析过程后面有时间再补充。

void do_hd_request(void)
{
    int i,r;
    unsigned int block,dev;
    unsigned int sec,head,cyl;
    unsigned int nsect;

    INIT_REQUEST;
    dev = MINOR(CURRENT->dev);
    block = CURRENT->sector;
    if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) {
        end_request(0);
        goto repeat;
    }
    block += hd[dev].start_sect;
    dev /= 5;
    __asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
        "r" (hd_info[dev].sect));
    __asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
        "r" (hd_info[dev].head));
    sec++;
    nsect = CURRENT->nr_sectors;
    if (reset) {
        reset = 0;
        recalibrate = 1;
        reset_hd(CURRENT_DEV);
        return;
    }
    if (recalibrate) {
        recalibrate = 0;
        hd_out(dev,hd_info[CURRENT_DEV].sect,0,0,0,
            WIN_RESTORE,&recal_intr);
        return;
    }    
    if (CURRENT->cmd == WRITE) {
        hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
        for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
            /* nothing */ ;
        if (!r) {
            bad_rw_intr();
            goto repeat;
        }
        port_write(HD_DATA,CURRENT->buffer,256);
    } else if (CURRENT->cmd == READ) {
        hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr);
    } else
        panic("unknown hd-command");
}

2.6 read_intr()

读操作中断处理函数。

该函数将在读取一个扇区完成后,将一个扇区的数据写到设备控制器的缓冲区后,触发中断并调用该中断处理函数。

详细解说将在后面补充

static void read_intr(void)
{
    if (win_result()) {
        bad_rw_intr();
        do_hd_request();
        return;
    }
    port_read(HD_DATA,CURRENT->buffer,256);
    CURRENT->errors = 0;
    CURRENT->buffer += 512;
    CURRENT->sector++;
    if (--CURRENT->nr_sectors) {
        do_hd = &read_intr;
        return;
    }
    end_request(1);
    do_hd_request();
}

2.7 write_intr()

写扇区中断调用函数,将在后面补充详细解说

static void write_intr(void)
{
    if (win_result()) {
        bad_rw_intr();
        do_hd_request();
        return;
    }
    if (--CURRENT->nr_sectors) {
        CURRENT->sector++;
        CURRENT->buffer += 512;
        do_hd = &write_intr;
        port_write(HD_DATA,CURRENT->buffer,256);
        return;
    }
    end_request(1);
    do_hd_request();
}

附录

附1 inb_p、outb_p、inb、outb

这四个函数是硬盘读取和写入函数,在io.h中定义,目的是通过端口号读取和写入硬盘。

io.h

#define outb(value,port) \
__asm__ ("outb %%al,%%dx"::"a" (value),"d" (port))


#define inb(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al":"=a" (_v):"d" (port)); \
_v; \
})

#define outb_p(value,port) \
__asm__ ("outb %%al,%%dx\n" \
        "\tjmp 1f\n" \
        "1:\tjmp 1f\n" \
        "1:"::"a" (value),"d" (port))

#define inb_p(port) ({ \
unsigned char _v; \
__asm__ volatile ("inb %%dx,%%al\n" \
    "\tjmp 1f\n" \
    "1:\tjmp 1f\n" \
    "1:":"=a" (_v):"d" (port)); \
_v; \
})

这里的端口号指的是硬盘控制器(设备控制器)的内存地址(注意这里暂时还不清楚硬盘控制器是独立编址还是统一编址,但是不管哪种编址方式,访问特定的端口,干特定的事儿)。

这些端口号的定义在hdreg.h中

/* Hd controller regs. Ref: IBM AT Bios-listing */
#define HD_DATA        0x1f0    /* _CTL when writing */
#define HD_ERROR    0x1f1    /* see err-bits */
#define HD_NSECTOR    0x1f2    /* nr of sectors to read/write */
#define HD_SECTOR    0x1f3    /* starting sector */
#define HD_LCYL        0x1f4    /* starting cylinder */
#define HD_HCYL        0x1f5    /* high byte of starting cyl */
#define HD_CURRENT    0x1f6    /* 101dhhhh , d=drive, hhhh=head */
#define HD_STATUS    0x1f7    /* see status-bits */
#define HD_PRECOMP HD_ERROR    /* same io address, read=error, write=precomp */
#define HD_COMMAND HD_STATUS    /* same io address, read=status, write=cmd */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值