百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

操作系统——L9-生磁盘的使用

liebian365 2024-11-27 17:07 2 浏览 0 评论

1、磁盘的结构

以下三幅图片为磁盘结构的简图:

磁盘的中间有一个主轴,它可以带动盘片转动,让所有的盘片都围绕着主轴旋转。有些磁盘的盘片上会有两个盘面,即上下两个盘面。

盘面被划分成许多个狭窄的同心圆环,每一个同心圆环都被称为一个磁道,磁道的编号是由外向内的,即最外圈的为0号磁道。 将盘面看成一个圆形,可以在盘面上划分出多个相同的扇形,分配到每一个磁道上的弧段被称为扇区。可以看出每一个磁道上的扇区数是相同的。注意上图右半边的注解。 每一个磁道上的扇区数目相同,且每个扇区的大小都为512字节,但外圈的扇区占用的物理面积更大,因此外圈的磁道上的扇区存在更多的浪费。

盘片旁边为机械磁臂,用于移动磁头。上图中用蓝色标出的圆柱体为一个柱面,例如所有盘面的0磁道可以组成一个柱面。只要往磁盘控制器中写柱面(cylinder)、磁头(head)、扇区(sector),就可以使用磁盘了。

扇区号的排列会影响磁盘的访问速度,Linux0.11使用的磁盘的扇区号排列方式如下:

从图中可以看出:扇区号是从一个柱面最上面的一个磁道从上往下开始排列,若一个磁道上有7个扇区,最上面磁道的扇区排列为0,1,2,3,4,5,6;7号扇区在下面一个磁道,且位于0号扇区的正下方。下一个柱面的扇区号从上一个柱面结束的扇区号+1开始排列,即扇区号的排列是连续的。这样数据的读写就变为了按柱面进行的,而不是按盘面进行。

磁盘容量 = 盘面数 × 柱面数 × 扇区数 × 512字节; //柱面数也可以理解为一个盘面上的磁道数

磁盘访问时间 = 写入控制器时间 + 寻道时间 + 旋转延迟时间 + 传输时间

硬盘读取数据时,读写磁头沿径向移动,移到要读取的扇区所在磁道的上方,这段时间称为寻道时间(seek time)。因读写磁头的起始位置与目标位置之间的距离不同,寻道时间也不同。磁头到达指定磁道后,然后通过盘片的旋转,使得要读取的扇区转到读写磁头的下方,这段时间称为旋转延迟时间(rotational latencytime)。然后再读写数据,读写数据也需要时间,这段时间称为传输时间(transfer time)

2、生磁盘的使用

直接通过三维参数(柱面、磁头、扇区)使用磁盘是非常麻烦的。从扇区号到盘块号,是对磁盘使用的第一层抽象,本章节主要介绍如何通过盘块号来使用磁盘,其中2.1节介绍盘块号如何转换为三维参数,2.2节分析Linux0.11中生磁盘的使用过程。

2.1 从扇区号到盘块号

扇区大小固定,但操作系统可以每次读/写连续的几个扇区,这几个连续的扇区可以组成一个盘块。为方便理解如何通过盘块号来访问磁盘,假定一个盘块的大小为一个扇区,盘块号排列方式与扇区号一样(即盘块号与图1.4的扇区号排列一致)。根据这种排列方式,就可以根据盘块号计算出要访问的三维参数(C,H,S):

// 根据要访问的三维参数可以计算出对应的盘块号
block = C * (Heads * Sectors) + H * Sectors + S;  
 
// 也可以通过盘块号计算出要访问的三维参数
S = block % Sectors;    // 注意向磁盘控制器内写入的扇区号是真实磁盘的物理扇区号,不是上图中的扇区号
C = block / ( Heads * Sectors);
H = (block % ( Heads * Sectors)) / Sectors;

其中 C,H,S 表示访问磁盘所需要的三维参数;block 表示盘块号;Heads 为常量,表示磁盘的磁头数(也就是盘面数);Sectors 为常量,表示一个磁道上的扇区数;若一个盘块对应n个扇区,计算方式也与之类似。

盘块大小的分配也会影响到磁盘使用的效率:盘块越大,读写的速度会越快,但碎片也会越大(这些碎片无法使用);盘块越小读写速度会越慢,但碎片会越小。

2.2 磁盘调度

假设磁头的初始位置是100号磁道,有多个进程先后陆续的请求访问55,58,39,18,90,160,150,38,184号磁道

2.2.1 FCFS

根据进程请求访问磁盘的先后顺序进行调度

按照FCFS的规则,按照请求到达的顺序,磁头需要一次移动到55,58,39,90,160,150,38,184号磁道

磁头总共移动的磁道个数为45+3+19+21+72+70+10+112+146=498

平均寻道长度为498/9=55.3个磁道

  • 优点:公平,性能还可以
  • 缺点:如果有大量进程竞争使用磁盘,请求访问磁道很分散,则FCFS在性能上很差,寻道时间长。

2.2.2 SSTF

最短寻道时间优先,其要求访问的磁道与当前磁头所在的磁道距离最近,以便每次的寻道时间最短,但这种调度算法却不能保证平均寻道时间最短

假设磁头的初始位置是100号磁道,有多个进程先后陆续的请求访问55,58,39,18,90,160,150,38,184号磁道

按照SSTF的规则,请求到达的

磁头总共移动了(100-18)+(184-18)=248个磁道

平均寻道长度为248/9=27.5个磁道

  • 优点:性能较好,平均寻道时间短
  • 缺点:可能产生“饥饿”现象

2.2.3 SCAN

当磁头正在由里向外移动时,SCAN算法所选择的下一个访问对象应是其欲访问的磁道,既在当前磁道之外,又是距离最近的。这样由里向外地访问,直至再无更外的磁道需要访问时,才将磁臂换向,由外向里移动。也叫电梯算法。

磁头总共移动了(184-100)+(184-18)=250个磁道

平均寻道长度为250/9=27.8个磁道

  • 优点:性能较好,平均寻道时间短,不会产生饥饿现象
  • 缺点:1,只有到最边上的磁道才能改变磁头的移动方向。2.SCAN对于各个位置磁道响应频率不平均。

2.2.4 C-SCAN

为了减少SCAN算法造成的某些进程的请求被严重推迟,CSCAN算法规定磁头单向移动。

磁头总共移动了(184-100)+(184-18)+(90-18)=322个磁道

平均寻道长度为322/9=35.8个磁道

  • 优点:比起SCAN算法,对于各位置磁道的响应频率很平均。
  • 缺点:只有到边上才能改变磁头移动方向,比起SCAN算法来,平均寻道时间更长。


2.3 生磁盘的使用过程

本节主要介绍 Linux0.11 中生磁盘的使用过程。本节内容只是对生磁盘的使用过程进行了粗糙的分析,主要是为了了解生磁盘的大致使用过程,如对 请求项队列的数据结构:struct buffer_head struct request struct blk_dev_struct三个结构体是怎么配合工作的等等, 这些细节部分都没有进行说明,对于这些细节建议参考《Linux内核完全剖析——基于0.12内核》。

生磁盘的使用过程简图如下:

下面根据 Linux0.11 的程序对生磁盘的使用过程进行分析。

(1)进程向缓冲区管理程序提出读写磁盘申请。缓冲区管理程序做出一个磁盘请求,在将请求加入请求队列中,最后让进程进入睡眠等待状态

/*创建请求项,并加入到请求队列中*/
/*major为主设备号,rw为命令(读/写),bh为存放数据的缓冲区头指针*/
static void make_request(int major,int rw, struct buffer_head * bh)
{
	struct request * req;
	int rw_ahead;
    
......

/* fill up the request-info, and add it to the queue */
/*前面主要是在寻找空闲请求项,并将请求项插入到请求项队列的指定位置*/
/*接下来就是做出请求项,从下面这段程序可以看出 bh 中所包含的信息*/
	req->dev = bh->b_dev;             /*数据源的主设备号*/
	req->cmd = rw;                    
	req->errors=0;
    /*bh->b_blocknr 应该就是图1.4中说的扇区号,而req->sector就是盘块号。
      从这里可以看出一个盘块的大小为2个扇区(2*512Bety)*/
	req->sector = bh->b_blocknr<<1;  
	req->nr_sectors = 2;     /*本次请求的扇区数*/
	req->buffer = bh->b_data;/*将请求项的缓冲区指针指向需要读写的数据缓冲区*/
	req->waiting = NULL;
	req->bh = bh;
	req->next = NULL;
	add_request(major+blk_dev,req);/*将请求项加入队列*/
}

在将请求项插入请求队列时,为了让磁盘使用的更加高效,这里采用了电梯算法将请求项插入请求队列。

/*本函数是将已经做好的请求项(req),加入到请求队列(dev)中*/
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)) {/*将tmp指向请求队列的队首*/
    /*若当前设备的请求项列表为空则设置 req 为当前请求项,并立即调用设备请求项处理函数*/
		dev->current_request = req;
		sti();
		(dev->request_fn)();/*设备请求项处理函数指针,若当前请求读写的为硬盘,则它是 do_hd_request() */
		return;
	}
    /*如果当前设备的请求项列表不为空则将 req 插入请求队列中*/
	/*下面这个for循环将利用 电梯算法 将req插到dev的合适位置*/
	for ( ; tmp->next ; tmp=tmp->next)/*从前往后扫描整个请求队列*/
		if ((IN_ORDER(tmp,req) || /*IN_ORDER应该是要比较tem的扇区号是否小于req的扇区号的,不过这里简化了,比较的是柱面号*/
		    !IN_ORDER(tmp,tmp->next)) &&
		    IN_ORDER(req,tmp->next))
			break;
	req->next=tmp->next;
	tmp->next=req;
	sti();/*开中断*/
}

将请求项插入请求队列后就会让进程进入睡眠等待状态,不过我还没找到相关代码,找到后在补充这一段。

(2) 根据(1)中算出的盘块号计算出要访问的三维参数(扇区号,柱面,磁头)并写入磁盘控制器
从(1)中可以看出,对于硬盘的请求项,设备请求项处理函数为 do_hd_request() 。

/*本函数执行磁盘读写请求操作*/
/*该函数首先根据请求项中的设备号和盘块号等信息计算出要访问的磁盘三维参数。
   然后根据请求项中的读写命令,向磁盘控制器发出相应的读写命令。*/
void do_hd_request(void)
{
	int i,r = 0;
	unsigned int block,dev;
	unsigned int sec,head,cyl;
	unsigned int nsect;

	INIT_REQUEST;
	dev = MINOR(CURRENT->dev);
	block = CURRENT->sector;/*CURRENT->sector为盘块号*/
	if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) {
		end_request(0);
		goto repeat;
	}
	block += hd[dev].start_sect;/*start_sect 是分区在磁盘中的起始物理(绝对)扇区,这个和磁盘分区有关,先不管它*/
	dev /= 5;
	/*下面这段内嵌汇编将根据盘块号算出cyl, head, sec(CHS)*/
	__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;/*nr_sectors 是分区中的扇区总数*/
......
	if (CURRENT->cmd == WRITE) {
        /*发送写磁盘命令。write_intr 中断调用函数,当前中断为写操作时被设置成中断过程中调用的 C 函数。
        磁盘完成写盘命令后会向CPU发送中断请求信号,于是在磁盘控制器完成写操作后会立刻调用该函数*/
		hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr);
......
	} else if (CURRENT->cmd == READ) {
        /*发送读磁盘命令。read_intr 的用法与 write_intr 类似*/
		hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr); 
	} else
		panic("unknown hd-command");
}
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");

......

	do_hd = intr_addr;                   /*do_hd = intr_addr 在磁盘中断处理函数(hd_interrupt:)中被调用*/
	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);                  //参数,柱面号低8位
	outb_p(cyl>>8,++port);               //参数,柱面号高8位
	outb_p(0xA0|(drive<<4)|head,++port); //参数,驱动器号加磁头号
	outb(cmd,++port);                    //命令,磁盘控制命令
}

outb_p() 会执行一段汇编代码, 里面很重要的一句 :outb %%al,%%dx,就是向磁盘端口写数据。

(3)磁盘中断请求处理
当磁盘处理完成或发生错误是就会发出中断信号,此时CPU响应中断请求,并调用磁盘中断处理程序:hd_interrupt:

hd_interrupt:

......

1:	jmp 1f
1:	xorl %edx,%edx
	xchgl do_hd,%edx    #do_hd是一个函数指针,被赋值为 write_intr() 或 read_intr(),
                        #看一下前面提到的 hd_out() 调用过程就会明白了。这里将edx设置为 do_hd
	testl %edx,%edx
	jne 1f
	movl $unexpected_hd_interrupt,%edx
1:	outb %al,$0x20
	call *%edx		# "interesting" way of handling intr.
                    # 调用 “edx” 函数
......

	iret

(4)磁盘处理完成产生中断,CPU处理中断并将磁盘返回数据加入缓冲区,最后唤醒进程

read_intr() 函数会将磁盘控制器中的数据复制到请求项指定的缓冲区中。在执行read_intr() 时会调用函数 end_request(1), 该函数会将进程唤醒。write_intr() 的处理过程与 read_intr() 类似。

到此如何通过盘块使用磁盘就分析完毕了。不过用盘块号来使用磁盘是相当麻烦的,程序员忍忍也就算了,用户怎能受得了如此折磨。下一章将介绍如何通过文件来使用磁盘。

图解

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: