L10 生磁盘的使用

  • 1 磁盘的结构
  • 2 生磁盘的使用
    • 2.1 从扇区号到盘块号
    • 2.2 生磁盘的使用过程
  • 参考

1 磁盘的结构

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

图1.1 磁盘正视图

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

图1.2 磁盘俯视图

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

图1.3 磁盘示意图

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

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

图1.4 扇区号排列

注意这里画出的扇区号只是逻辑上的,和真实磁盘的物理扇区号不一样。磁盘上的扇区号排序应该是:0磁道的扇区排列为扇区1,扇区2,… 扇区n;1磁盘的扇区排列为扇区1,扇区2,… 扇区n;即每个磁道都有自己的排序。向磁盘控制器内写入的扇区号是真实磁盘的物理扇区号,而不是图1.4的扇区号

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

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

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

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

2 生磁盘的使用

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

2.1 从扇区号到盘块号

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

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

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

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

2.2 生磁盘的使用过程

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

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

图2.1 生磁盘的使用过程

下面根据 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); } elsepanic("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,%edxxchgl do_hd,%edx    #do_hd是一个函数指针,被赋值为 write_intr() 或 read_intr(),#看一下前面提到的 hd_out() 调用过程就会明白了。这里将edx设置为 do_hdtestl %edx,%edxjne 1fmovl $unexpected_hd_interrupt,%edx
1:  outb %al,$0x20call *%edx        # "interesting" way of handling intr.# 调用 “edx” 函数
......iret

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

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

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

参考

本文中的所有插图都截取自哈工大操作系统课件。

[1] 简单理解磁盘结构
[2]Linux-0.11操作系统实验8理论-proc文件系统的实现
[3]操作系统_哈尔滨工业大学_中国大学MOOC
[4]《Linux内核完全剖析——基于0.12内核》

L10 生磁盘的使用相关推荐

  1. 哈工大李治军老师操作系统笔记【27】:从生磁盘到文件(Learning OS Concepts By Coding Them !)

    文章目录 0 回顾 1 引入文件 1.1 映射 1.2 链式结构实现文件 1.3 索引结构实现文件 2 总结 0 回顾 盘块号就是连续的扇区 得到盘块号就能进行下列操作 1 引入文件 普通用户使用生磁 ...

  2. 【linux】ubuntu系统硬盘操作:创建删除磁盘分区,更改磁盘分区类型,删除磁盘签名

    1.查看服务器上所有磁盘信息 fdisk -l 2.删除分区 fdisk /dev/sdc d # 输入要删除的分区id号,我这只有1个分区,所以自动删除了partition 1 w 3.更改磁盘分区 ...

  3. 24.原生磁盘的使用

    [README] 1.本文内容总结自 B站 <操作系统-哈工大李治军老师>,内容非常棒,墙裂推荐: 2.磁盘操作抽象 第1层抽象:通过盘块号读写磁盘(或逻辑盘块号): 第2层抽象:用队列缓 ...

  4. 【操作系统系列】磁盘基本原理与盘块编号

    磁盘的基本原理 磁盘工作的原理 (1)从 CPU 开始,当用户想要使用磁盘时,由 CPU 发送命令给磁盘设备,最终通过"out ax, 端口号"指令告诉磁盘具体的动作细节. (2) ...

  5. L11 通过文件使用磁盘

    L11 通过文件使用磁盘 1 从生磁盘到文件 2 文件名与盘块的映射 2.1 连续结构 2.2 索引结构 2.3 多级索引结构 3 文件使用磁盘的实现 参考 Linux0.11 通过文件来使用磁盘的过 ...

  6. 我是如何学习写一个操作系统(九):文件系统

    前言 这个应该是这个系列的尾声了,一个完整的操作系统可能最主要的也就是分成这几大模块:进程管理.内存管理和文件系统.计算机以进程为基本单位进行资源的调度和分配:而与用户的交互,基本单位则是文件 生磁盘 ...

  7. 操作系统笔记(1.5w字耐心整理)

    文章目录 操作系统 一些常见名词解释: 操作系统概述 什么是操作系统 什么是系统调用 进程与线程 进程的切换 内存管理的引入 一个程序的运行 用户态线程切换 创建一个线程 内核级线程切换 内核级线程是 ...

  8. 【操作系统-哈工大李治军】---学习笔记(下)---操作系统管理内存

    # 操作系统-内存 ----------------操作系统如何管理CPU------>操作系统如何管理内存----------------------------------- 1 内存使用 ...

  9. expdp与impdp导出导入特定表

    oracle里导入导出特定的表,原本在10g或以前,很简单的: 一.10g或以前 1.导出指定表 exp 'sys/pwd@server1 as sysdba' file=c:\temp\tables ...

最新文章

  1. usaco Overfencing 穿越栅栏(BFS)
  2. 剑指offer 算法 (知识迁移能力2)
  3. python二进制、字符编码及文件操作
  4. 怎么在windows上启动python_Windows下如何安装和运行Python
  5. 第二章 变量和基本类型
  6. .net core 部署应用程序注意事项
  7. 题目1008:最短路径问题(SPFA算法)
  8. 好的数据分析平台有多重要
  9. html 获取本机ip_爬取快代理免费ip,构建自己的代理ip池,不再怕反爬(附代码)...
  10. 64-bit and iOS 8 Requirements for New Apps
  11. STM32F407——蓝牙模块CC2541
  12. 贵州省中职学校计算机教材电子版,中职计算机基础课件贵州省中职学校计算机应用基础教学工作计划.doc...
  13. 记录一下, 破解某搜题软件
  14. 操作系统学习笔记:操作系统基础知识
  15. js从地址栏获取参数
  16. 大小写字母ASCII码对照表
  17. 7z删除_7Zip免费的文件压缩/解压软件,包括独有的7z文件
  18. JS中attr和prop区别
  19. AI 作画:Stable Diffusion 模型原理与实践
  20. 习题2.9 编写程序,由键盘输入20个整数,分别炸出其中的最大正整数、最小正整数、最大负整数、最小负整数

热门文章

  1. P1536 村村通 并查集
  2. currentTimeMillis()方法
  3. 缓存(cache)和缓冲(buffer)
  4. java怎么用switch求闰年_2. 用switch结构实现输入某年某月某日,判断这一天是这一年的第几天。(考虑闰年) 源程序命名为: 完整学号姓名2.c 。_电子商务物流答案_学小易找答案...
  5. 5.15 使用is语句检查实例的类型 [Swift原创教程]
  6. 清华硕士炮轰字节恶意开低薪:“月薪2万,硕士白读还倒贴”!
  7. C++和Java应该怎么选择(转)
  8. 菜刀过狗连接webshell的技巧
  9. 基于Sphinx构建准实时更新的分布式通用搜索引擎平台
  10. 软件测试教程基础知识,零基础如何学软件测试