磁盘的管理(一)


前言

磁盘既是输入设备又是输出设备。 输出设备(OutputDevice)是人与计算机交互的一种部件,用于数据的输出。 输入设备:向计算机输入数据和信息的设备。

所以,使用磁盘大致上与显示器和键盘一样
在这里插入图片描述


提示:以下是本篇文章正文内容

一、磁盘的介绍

磁盘由一个个盘片组成的磁盘立体结构,一个盘片上下两面都是可读写的

磁盘利用了电流的磁效应,对一些电信号进行磁化,保存在磁盘中,用来表示一些信息。

在这里插入图片描述
硬盘又划分为磁头(Heads)、柱面(Cylinder)、扇区(Sector)
在这里插入图片描述
磁头(Heads):每张盘面的正反两面各有一个磁头,一个磁头对应一张磁片的一个面。用第几磁头就可以表示数据在哪个磁面

柱面(Cylinder):所有盘面中半径相同的同心磁道构成“柱面”,这一系列的磁道垂直叠在一起,就形成一个柱面的形状

扇区(Sector):将磁道划分为若干个小的区段,就是扇区,每个扇区的容量为512字节,大小本质上是对磁盘数据的传输时间和磁盘的碎片浪费这2项参数的折中
在这里插入图片描述

硬盘容量=磁头数×柱面数×扇区数×512字节

结构概况:
在这里插入图片描述

二、生磁盘的使用

1.IO过程简介

磁盘I/O过程: 控制器–>寻道–>旋转–>传输
在这里插入图片描述

1.磁头移动到相应的磁道上
2.磁道开始旋转,转到相应的扇区
3.此时再转的时候就是磁生电,磁信号就变成电信号,然后就读取数据
4.读到内存的缓冲区,将这个内存缓冲区修改一个字节
5.然后继续里面再转,此时是电生磁,把字节写到磁道上

移动磁头,移动到对应的磁道上,然后转动磁道,移动到对应的扇区上,一边旋转一边进行磁生电,电生磁,和内存缓冲区进行数据的交互读和写

2.直接使用磁盘

只要往控制器中写柱面、 磁头、 扇区、 缓存位置
在这里插入图片描述假如要往磁盘的某个扇区写一个字节,那么需要知道这个扇区对应的哪个柱面中的哪个磁头把这些参数传到磁盘控制器,磁盘控制器再根据这些参数进行驱动磁盘写数据

(1)磁盘读写的请求函数do_hd_request()


void do_hd_request(void)
{
    
    
	..... 
	//前面一些语句就是要得到dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr数据
	// 传递给磁盘控制器
	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)磁盘驱动的核心代码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))
{
    
    
	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; //数据寄存器端口(0x1f0)

	// outb_p接口就是往外设传送数据的
	// cpu中磁盘驱动的核心代码
	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);
}

以上方法需要的传递的参数太多,不够方便

3.盘块号读写磁盘(第一层抽象)

将柱面、磁头、扇区包装成一个磁盘块
在这里插入图片描述
磁盘驱动负责从block计算出cyl, head, sec(CHS)
假设扇区的编址如下,(block相邻的盘块可以快速读出)
1号扇区在0号扇区旋转方向的下一扇区,假设一个盘面有六个扇区,则0-5扇区在第一个盘面,6号扇区在0号扇区竖直方向的下的扇区
在这里插入图片描述
计算公式:block = c * (heads * sectors) + h*heads * sectors + s

Sectors 是每个盘面的扇区数,Heads 是磁盘的磁头数量

扇区号 = 柱面号 × (一个柱面有多少扇区)+ 盘面号 ×(一个盘面有多少扇区)+ 扇区号

根据扇区号 sector 来算出 C、H、S

S = sector%Sectors
H = sector/Sectors%Heads
C = sector/Sectors/Heads

通过编址建立从 C、H、S 扇区地址到扇区号的一个映射,这就是文件系统第一层抽象的中心任务。扇区号连续的多个扇区就是一个磁盘块

磁盘的访问时间
磁盘的访问时间 = 写入控制器时间 + 寻道时间 + 旋转时间 + 传输时间
(其中主要是寻道时间, 旋转时间长,同时相邻的盘块应能快速读出)

通过磁盘号进行读写磁盘

static void make_request()
{
    
     
	struct requset *req;
	req=request+NR_REQUEST;
	req->sector=bh->b_blocknr<<1;
	add_request(major+blk_dev,req); 
}

void do_hd_request(void)
{
    
     
	unsigned int block=CURRENT->sector;
	__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));
	hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,...);
	... 
}

有了磁盘块,用户发出的磁盘读写请求就是盘块号 blocknr 了,由于磁盘块是连续的多个扇区,可以容易地算出扇区号,即:sector = blocknr × blocksize( blocksize 是描述磁盘块大小)

4.队列读写磁盘(第二层抽象)

操作系统中一般有多个进程,每个进程都会提出磁盘块访问请求,所以需要用队列来管理访问请求,这就是操作系统对磁盘管理的第二层抽象

在这里插入图片描述
多个磁盘访问请求出现在请求队列,需要对磁盘进行调度

调度目标:平均延迟小
调度算法:

(1)FCFS磁盘调度算法

最直观最公平的调度
在这里插入图片描述

(2)SSTF磁盘调度

在移动过程中把经过的请求处理(Shortest-seek-time First)
在这里插入图片描述

(3)SCAN磁盘调度

SSTF+中途不回折: 每个请求都有处理机会
在这里插入图片描述

(4)C-SCAN磁盘调度(电梯算法)

SCAN+直接移到另一端: 两端请求都能很快处理
在这里插入图片描述
这借鉴了生活中电梯的模型,电梯在运行的时候有上升和下降2种情况,当电梯上升时,本次上升的终点就是最高的请求楼层; 当电梯下降时,本次下降的终点就是最低的请求楼层

IN_ORDER()

// 核心思想是比较s1与s2中的sector大小
// 也就是比较s1与s2中的柱面号的大小
// 因为柱面的寻找是耗时最长的,所以要保证
// 寻找柱面也即寻道的时间不能太长,就要在
// 寻道上面做优化处理
#define IN_ORDER(s1,s2) \
((s1)->cmd<(s2)->cmd || ((s1)->cmd==(s2)->cmd && \
((s1)->dev < (s2)->dev || ((s1)->dev == (s2)->dev && \
(s1)->sector < (s2)->sector))))

知道了IN_ORDER()的作用,可以分析一下电梯算法

// linux-0.11/kernel/blk_drv/ll_rw_blk.c
static void add_request(struct blk_dev_struct * dev, struct request * req)
{
    
    
	struct request * tmp;

	req->next = NULL;
	cli();   // 开启临界区保护
	if (req->bh)
		req->bh->b_dirt = 0;
	if (!(tmp = dev->current_request)) {
    
    
		dev->current_request = req;
		sti();
		(dev->request_fn)();
		return;
	}
	//当符合这两种情况时就跳出循环
	// 并将req插入tmp和next之间
	//(1)当tmp的柱面号小于req的柱面号,且req小于next的柱面号(电梯上升)
	//(2)当tmp的柱面号小于next的柱面号,且req小于next的柱面号(电梯下降)
	// 不管这两种任何一种情况,
	// 下一步磁盘读写都会进入req这个对象上
	//否则就按照原有的,队列进行磁盘读写
	//这样就能更高效的使用磁盘
	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;
	sti();
}

在这里插入图片描述


总结

提示:这里对文章进行总结:

小结:生磁盘(raw disk)写过程
在这里插入图片描述

Guess you like

Origin blog.csdn.net/qq_53144843/article/details/120604633