大地点
Linux102系列会详细讲解Linux0.11版本中的102个文件,本文讲解linux0.11的第41个文件
【Linux102】41-kernel/blk_drv/hd.c的文件源码。
1. hd.c的主要作用
hd.c程序是硬盘控制器驱动程序,提供对硬盘控制器块设备的读写驱动和硬盘初始化处理。程序中所有函数按照功能不同可分为5类:
-
初始化硬盘和设置硬盘所用数据结构信息的函数,如sys_setup()和hd_init()。
-
向硬盘控制器发送命令的函数hd_out()。
-
处理硬盘当前请求项的函数do_hd_request()。
-
硬盘中断处理过程中调用的C函数,如read_intr()、write_intr()、bad_rw_intr()和recal intr()。do_hd_request()函数也将在read intr()和write_intr()中被调用。
-
硬盘控制器操作辅助函数,如controler_ready()、drive_busy()、win_result()、hd_out()和reset_controller()等。
sys_setup() 函数利用 boot/setup.s 程序提供的信息对系统中所含硬盘驱动器的参数进行
了设置。
然后读取硬盘分区表,并尝试把启动引导盘上的虚拟盘根文件系统的映像文件复制到内存虚拟盘中,若成功则加载虚拟盘中的根文件系统,否则就继续执行普通根文件系统加载操作。
hd_init() 函数用于在内核初始化时设置硬盘控制器中断描述符,并复位硬盘控制器中断屏蔽码,以允许硬盘控制器发送中断请求信号。
hd_out()是硬盘控制器操作命令发送函数。该函数带有一个中断过程中调用的C函数指
针参数,在向控制器发送命令之前,它首先使用这个参数预置好中断过程中会调用的函数指针(do_hd),然后它按照规定的方式依次向硬盘控制器 0x1f0 至 0x1f7 发送命令参数块。除控制
器诊断(WIN_DIAGNOSE)和建立驱动器参数(WIN_SPECIFY)两个命令以外,硬盘控制器在接收到任何其他命令并执行了命令以后,都会向 CPU发出中断请求信号,从而引发系统去执行硬盘中断处理过程(在 system_call.s 中的 221 行)。
do_hd_request() 是硬盘请求项的操作函数。其操作流程如下:
-
首先判断当前请求项是否存在,若当前请求项指针为空,则说明目前硬盘块设备已经没有待处理的请求项,因此**立刻退出程序。**这是在宏 INIT_REQUEST 中执行的语句。否则就继续处理当前请求项。
-
对当前请求项中指明的设备号和请求的盘起始扇区号的合理性进行验证。
-
根据当前请求项提供的信息计算请求数据的磁盘磁道号、磁头号和柱面号。
-
如果复位标志(reset)已被设置,则也设置硬盘重新校正标志(recalibrate),并对硬盘执行复位操作,向控制器重新发送**“建立驱动器参数”**命令(WIN_SPECIFY)。该命令不会引发硬盘中断。
-
如果重新校正标志被置位的话,就向控制器发送硬盘重新校正命令(WIN_RESTORE),并在发送之前预先设置好该命令引发的中断中需要执行的 C 函数(
recal_intr()),并退出。recal_intr() 函数的主要作用是:当控制器执行该命令结束并
引发中断时,能重新(继续)执行本函数。 -
如果当前请求项指定是写操作,则首先设置硬盘控制器调用的 C 函数为 write_intr() ,向控制器发送写操作的命令参数块,并循环查询控制器的状态寄存器,以判断请求服务标志(DRQ)是否置位。若该标志置位,则表示控制器已“同意”接收数据,于是接着就把请求项所指缓冲区中的数据写入控制器的数据缓冲区中。若循环查询超时后该标志仍然没有置位,则说明此次操作失败。于是调用 bad_rw_intr() 函数,根据处理当前请求项发生的出错次数来确定是放弃继续处理当前请求项还是需要设置复位标志,以继续重新处理当前请求项。
-
如果当前请求项是
读操作,则设置硬盘控制器调用的C 函数为 read_intr(),并向控制器发送`读盘操作命令。
write_intr() 是在当前请求项是写操作时被设置成中断过程`调用的 C 函数。控制器完成写盘命令后会立刻向 CPU 发送中断请求信号,于是在控制器写操作完成后就会立刻调用该函数。
该函数首先调用 win_result() 函数,读取控制器的状态寄存器,以判断是否有错误发生。
若在写盘操作时发生了错误,则调用
bad_rw_intr(),根据处理当前请求项发生的出错次数来确定是放弃继续处理当前请求项还是需要设置复位标志,以继续重新处理当前请求项。
若没有发生错误,则根据当前请求项中指明的·
需写扇区总数,判断是否已经把此请求项要求的所有数据写盘了。
若还有数据需要写盘,则再把一个扇区的数据复制到控制器缓冲区中。
若数据已经全部写盘,则处理当前请求项的结束事宜:唤醒等待本请求项完成的进程、唤醒等待空闲请求项的进程(若有的话)、设置当前请求项所指缓冲区数据已更新标志、释放当前请求项(从块设备链表中删除该项)。最后继续调用 do_hd_request() 函数,以继续处理硬盘设备的其他请求项。
read_intr() 则是在当前请求项是读操作时被设置成中断过程中调用的 C 函数。控制器在把指定的扇区数据从硬盘驱动器读入自己的缓冲区后,就会立刻发送中断请求信号。而该函数的主要作用就是把控制器中的数据复制到当前请求项指定的缓冲区中。
与 write_intr() 开始的处理方式相同,该函数首先也调用 ·win_result() 函数,读取控制器的状态寄存器,以判断是否有错误发生。若在读盘时发生了错误,则执行与 write_intr() 同样的处理过程。若没有发生任何错误,则从控制器缓冲区把一个扇区的数据复制到请求项指定的缓冲区中。然后根据当前请求项中指明的欲读扇区总数,判断是否已经读取了所有的数据。若还有数据要读,则退出,以等待下一个中断的到来。若数据已经全部获得,则处理当前请求项的结束事宜:唤醒等待当前请求项完成的进程、唤醒等待空闲请求项的进程(若有的话)、设置当前请求项所指缓冲区数据已更新标志、释放当前请求项(从块设备链表中删除该项)。最后继续调用 do_hd_request() 函数,以继续处理硬盘设备的其他请求项。
为了能更清晰地看清楚硬盘读写操作的处理过程,我们可以把这些函数、中断处理过程以及硬盘控制器三者之间的执行时序关系用下图表示出来:

由以上分析可以看出,本程序中最重要的4个函数是 hd_out()、do_hd_request()、read_intr()和 write_intr()。理解了这4个函数的作用也就理解了硬盘驱动程序的操作过程。
2.源码用到的文件
必看硬件知识补充!
详见此文:hd.c的硬件补充知识

| 😉【Linux102】15-include/linux/sched.h | 本程序主要定义了进程调度的相关数据结构,如任务数据结构等等,理解这些数据结构是理解进程调度机制的关键,也是理解内核的关键。一句话:**我们常说的进程是XXX,但是进程究竟是什么?**就定义在这个文件里面!源码面前了,没有秘密! |
| 😉【Linux102】20-include/linux/kernel.h | 本程序主要定义了内核中最常用的一些基础函数声明,是内核运行最基本保障,比如malloc、printk、free、tyy_write、panic等函数声明,这些函数具体的实现则分布在各个.c文件中。 |
| 😉【Linux102】21-include/asm/segment.h | 本程序定义了一系列用于在 Linux 内核中访问用户空间内存的内联函数,主要通过内嵌汇编操作段寄存器fs来实现内核与用户空间的数据交互。 |
| 😉【Linux102】24-include/linux/fs.h | 这个文件(fs.h)是操作系统内核中文件系统模块的核心头文件,它的主要作用是定义文件系统实现所需的数据结构、常量、宏和函数接口,为整个文件系统的运作提供基础框架。 |
| 😉【Linux102】36-include/asm/system.h | 该文件中定义了设置或修改描述符/中断门等的嵌入式汇编宏。其中,函数 move_to_user_mode() 用于内核在初始化结束时 “切换”到初始进程(任务0)。所使用的方法是模拟中断调用返回过程,即利用指令iret运行初始任务0。 |
| 😉【Linux102】38-include/linux/fdreg.h | 这个头文件为操作软盘控制器提供了统一的符号定义和函数接口声明,是底层软盘驱动程序开发的基础,使得开发者可以通过这些抽象的符号和函数来与软盘控制器硬件进行交互,而无需直接使用具体的数值地址和命令码,提高了代码的可读性和可维护性。 |
| 😉【Linux102】39-include/asm/io.h | io.h定义了一系列宏,这些宏是 x86 架构下直接操作硬件 I/O 端口的底层工具。 |
| 😉【Linux102】42-include/linux/config.h | config.h 是一个配置文件,用于设置键盘布局和硬盘参数(备用)。设计目的:提供可配置的选项,适配不同的硬件环境。 |
| 😉【Linux102】43-include/linux/hdreg.h | 这段代码是 hdreg.h 头文件,主要作用是定义与AT 硬盘控制器交互的核心常量、命令和数据结构,为硬盘驱动程序(如 hd.c)提供底层硬件接口的抽象。 |
| 😉【Linux102】40-kernel/blk_drv/blk.h | 这是有关硬盘块设备参数的头文件,因为只用于块设备,所以与块设备代码放在同一个地方。其中主要定义了请求等待队列中项的数据结构request,用宏语句定义了电梯搜索算法,并对内核目前支持的虚拟盘、硬盘和软盘三种块设备,根据它们各自的主设备号分别设定了常数值。 |
3.源码版
/*
* 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 = 0;
static int reset = 0;
/*
* 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))
#define port_write(port, buf, nr) \
__asm__("cld;rep;outsw" ::"d"(port), "S"(buf), "c"(nr))
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++)
{

最低0.47元/天 解锁文章
685

被折叠的 条评论
为什么被折叠?



