您的位置:首页 > 运维架构 > Linux

Linux内核学习笔记之磁盘I/O(十一)

2014-01-28 10:16 211 查看
还记得文件系统中,高速缓冲区读取盘块操作,我说过它调用的设备读写接口是

void ll_rw_block(int rw, struct buffer_head * bh)


而这个接口最后调用的磁盘操作接口就是

void rw_hd(int rw, struct buffer_head * bh)


接下来我将以这个为切入点,详细介绍磁盘读取操作

(1)计算要读取的扇区索引=盘块索引*2

block = bh->b_blocknr << 1;
一个盘块等于两个扇区,所以要右移一位

(2)获取设备号中的子设备号

dev = MINOR(bh->b_dev);

(3)如果主设备号有问题,或者当前要读取的是当前盘块的最后一块扇区(每次读取都是两个扇区)直接返回

if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects)
return;

为什么这么判断?看下linus定义的硬盘分区结构就知道了

// 定义硬盘分区结构。给出每个分区的物理起始扇区号、分区扇区总数。

// 其中 5 的倍数处的项(例如hd[0]和 hd[5]等)代表整个硬盘中的参数。

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


(4)计算要读取的扇区在磁盘上的绝对扇区号

block += hd[dev].start_sect;

(5)硬盘号0/1

dev /= 5;

(6)计算扇区的读取参数,磁头号head、柱面号cyl、扇区号sec

__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));

(7)磁盘真正读操作

rw_abs_hd(rw,dev,sec+1,head,cyl,bh);
abs表示绝对的意思,前面的rw_hd函数输入参数都是操作系统对磁盘的抽象表示,经过转换后现在的rw_abs_hd函数输入参数都是硬盘的实际读取参数。下面将详细剖析此函数

(7.1)如果高速缓冲区请求的操作不是读入或者写入就死机

if (rw!=READ && rw!=WRITE)
panic("Bad hd command, must be R/W");

(7.2)对高速缓冲区加锁

lock_buffer(bh);

(7.3)从磁盘操作请求队列中找到一个未被使用的请求结构节点

repeat:
for (req=0+request ; req<NR_REQUEST+request ; req++)
if (req->hd<0)
break;
if (req==NR_REQUEST+request) {
sleep_on(&wait_for_request);
goto repeat;
}

如果所有请求节点都被使用,则当前进程进入睡眠等待状态,当被唤醒的时候,将重新执行上面操作(进程什么时候被唤醒呢?当磁盘操作队列有请求节点完成了其写入/读取操作后就会释放请求节点,这个时候就会唤醒磁盘操作等待队列的队首任务),我们在这里没有发现什么等待解锁操作,难道不会已经存在相同扇区的读/写?其实我们一开始对高速缓冲块加锁的原因就是这个,因为高速缓冲块和磁盘的盘块是对应的,所以对高速缓冲块加锁,也就保证了不管什么时刻,都只有一个进程进行读/写磁盘操作,其他进程都是在高速缓冲块的等待队列下挂起自身然后睡眠等待唤醒(磁盘完成读写后会唤醒磁盘操作等待队列首部的任务和高速缓冲块等待队列首部的任务)

(7.4)根据输入参数设置请求内容

req->hd=nr;
req->nsector=2;
req->sector=sec;
req->head=head;
req->cyl=cyl;
req->cmd = ((rw==READ)?WIN_READ:WIN_WRITE);
req->bh=bh;
req->errors=0;
req->next=NULL;

(7.5)将请求节点加入磁盘操作请求队列中

add_request(req);
wait_on_buffer(bh);

最后当前进程要判断高速缓冲块是否已经完成读/写盘块操作,如果没有则挂起自身到高速缓冲块的等待队列中,之所以可能出现这种情况,是由于多任务切换,当进程在do_request开始被切换了,另一个进程在插入请求后进入add_request的电梯排序时又被切回去了,这个时候由于队列还未完成排序do_request将直接返回,所以add_request执行完成并不一定是完成了操作,所以需要进行一次判定。下面将浅析下add_request函数

(7.5.1)一次必须读取两个盘块

if (req->nsector != 2)
panic("nsector!=2 not implemented");
因为高速缓冲块为1k等于两个盘块大小

(7.5.2)使用电梯算法对请求队列排序

sorting=1;
if (!(tmp=this_request))
this_request=req;
else {
if (!(tmp->next))
tmp->next=req;
else {
tmp=tmp->next;
for ( ; tmp->next ; tmp=tmp->next)
if ((IN_ORDER(tmp,req) ||
!IN_ORDER(tmp,tmp->next)) &&
IN_ORDER(req,tmp->next))
break;
req->next=tmp->next;
tmp->next=req;
}
}
sorting=0;

就是请求的顺序按照设备号、磁头、柱面来排序,因为这样磁盘机械臂就能平滑的移动并读取数据,而不是来回不停的移动,这样是节省时间的读取方法

(7.5.3)最后就是依次执行磁盘操作队列中的请求

if (!do_hd)
do_request();

(7.5.3.1)请求队列必须先排序

if (sorting)
return;

非常暴力,没排序就不执行,这个当进程切换的时候会发生,前面已经举例了

(7.5.3.2)下面就是调用write_intr/read_intr通过硬盘端口直接进行读写操作

if (this_request->cmd == WIN_WRITE) {
hd_out(this_request->hd,this_request->nsector,this_request->
sector,this_request->head,this_request->cyl,
this_request->cmd,&write_intr);
for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++)
/* nothing */ ;
if (!r) {
reset_hd(this_request->hd);
return;
}
port_write(HD_DATA,this_request->bh->b_data+
512*(this_request->nsector&1),256);
} else if (this_request->cmd == WIN_READ) {
hd_out(this_request->hd,this_request->nsector,this_request->
sector,this_request->head,this_request->cyl,
this_request->cmd,&read_intr);
} else
panic("unknown hd-command");

这边顺便提下磁盘的读写操作函数write_intr/ read_intr最后会调用

wake_up(&wait_for_request);
unlock_buffer(this_request->bh);

唤醒磁盘操作等待队列和高速缓冲块等待队列,write_intr/ read_intr函数表面的意思虽然很容易懂,不过对磁盘的端口操作是在不熟悉,目前也不愿意看《Linux内核注释》的介绍,如果实在想刨根问底的童鞋自己去摸索研究下吧~

以上就是磁盘操作大致的内容,其实有些时候如果不关心细节,只需要了解功能模块提供的接口函数的作用就可以了,这边的内容和其他章节耦合度并不大,终端相关内容是真没研究,所以0.11的笔记这是最后一篇,在此祝愿大家在新的一年马到成功~
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: