文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

读取硬盘前的准备工作有哪些?

2024-12-02 10:10

关注

读取硬盘数据到内存中,是操作系统的一个基础功能。

读取硬盘需要有块设备驱动程序,而以文件的方式来读取则还有要再上面包一层文件系统。

把读出来的数据放到内存,就涉及到内存中缓冲区的管理。

上面说的每一件事,都是一个十分庞大的体系,我们今天的文章一个都不展开讲,哈哈。

我们就讲讲,读取块设备与内存缓冲区之间的桥梁,块设备请求项的初始化工作。

我们以 Linux 0.11 源码为例,发现进入内核的 main 函数后不久,有这样一行代码。

  1. void main(void) { 
  2.     ... 
  3.     blk_dev_init(); 
  4.     ... 

看到这个方法的全部代码后,你可能会会心一笑,也可能一脸懵逼。

  1. void blk_dev_init(void) { 
  2.     int i; 
  3.     for (i=0; i<32; i++) { 
  4.         request[i].dev = -1; 
  5.         request[i].next = NULL
  6.     } 

这也太简单了吧?

就是给 request 这个数组的前 32 个元素的两个变量 dev 和 next 附上值,看这俩值 -1 和 NULL 也可以大概猜出,这是没有任何作用时的初始化值。

我们看下 request 结构体。

  1.  
  2. struct request { 
  3.     int dev;         
  4.     int cmd;         
  5.     int errors; 
  6.     unsigned long sector; 
  7.     unsigned long nr_sectors; 
  8.     char * buffer; 
  9.     struct task_struct * waiting; 
  10.     struct buffer_head * bh; 
  11.     struct request * next
  12. }; 

注释也附上了。

哎哟,这就有点头大了,刚刚的函数虽然很短,但看到这个结构体我们知道了,重点在这呢。

这也侧面说明了,学习操作系统,其实把遇到的重要数据结构牢记心中,就已经成功一半了。比如主内存管理结构 mem_map,知道它的数据结构是什么样子,其功能也基本就懂了。

收,继续说这个 request 结构,这个结构就代表了一次读盘请求,其中:

dev 表示设备号,-1 就表示空闲。

cmd 表示命令,其实就是 READ 还是 WRITE,也就表示本次操作是读还是写。

errors 表示操作时产生的错误次数。

sector 表示起始扇区。

nr_sectors 表示扇区数。

buffer 表示数据缓冲区,也就是读盘之后的数据放在内存中的什么位置。

waiting 是个 task_struct 结构,这可以表示一个进程,也就表示是哪个进程发起了这个请求。

bh 是缓冲区头指针,这个后面讲完缓冲区就懂了,因为这个 request 是需要与缓冲区挂钩的。

next 指向了下一个请求项。

这里有的变量看不懂没关系。

不过我们倒是可以基于现有的重点参数猜测一下,比如读请求时,cmd 就是 READ,sector 和 nr_sectors 这俩就定位了所要读取的块设备(可以简单先理解为硬盘)的哪几个扇区,buffer 就定位了这些数据读完之后放在内存的什么位置。

这就够啦,想想看,这四个参数是不是就能完整描述了一个读取硬盘的需求了?而且完全没有歧义,就像下面这样。

而其他的参数,肯定是为了更好地配合操作系统进行读写块设备操作嘛,为了把多个读写块设备请求很好地组织起来。这个组织不但要有这个数据结构中 hb 和 next 等变量的配合,还要有后面的电梯调度算法的配合,仅此而已,先点到为止。

总之,我们这里就先明白,这个 request 结构可以完整描述一个读盘操作。然后那个 request 数组就是把它们都放在一起,并且它们又通过 next 指针串成链表。

好,本文讲述的两行代码,其实就完成了上图所示的工作而已。

但讲到这就结束的话,很多同学可能会不太甘心,那我就简单展望一下,后面读盘的全流程中,是怎么用到刚刚初始化的这个 request[32] 结构的。

读操作的系统调用函数是 sys_read,源代码很长,我给简化一下,仅仅保留读取普通文件的分支,就是如下的样子。

  1. int sys_read(unsigned int fd,char * buf,int count) { 
  2.     struct file * file = current->filp[fd]; 
  3.     struct m_inode * inode = file->f_inode; 
  4.     // 校验 buf 区域的内存限制 
  5.     verify_area(buf,count); 
  6.     // 仅关注目录文件或普通文件 
  7.     return file_read(inode,file,buf,count); 

看,入参 fd 是文件描述符,通过它可以找到一个文件的 inode,进而找到这个文件在硬盘中的位置。

另两个入参 buf 就是要复制到的内存中的位置,count 就是要复制多少个字节,很好理解。

钻到 file_read 函数里继续看。

  1. int file_read(struct m_inode * inode, struct file * filp, char * buf, int count) { 
  2.     int left,chars,nr; 
  3.     struct buffer_head * bh; 
  4.     left = count
  5.     while (left) { 
  6.         if (nr = bmap(inode,(filp->f_pos)/BLOCK_SIZE)) { 
  7.             if (!(bh=bread(inode->i_dev,nr))) 
  8.                 break; 
  9.         } else 
  10.             bh = NULL
  11.         nr = filp->f_pos % BLOCK_SIZE; 
  12.         chars = MIN( BLOCK_SIZE-nr , left ); 
  13.         filp->f_pos += chars; 
  14.         left -= chars; 
  15.         if (bh) { 
  16.             char * p = nr + bh->b_data; 
  17.             while (chars-->0) 
  18.                 put_fs_byte(*(p++),buf++); 
  19.             brelse(bh); 
  20.         } else { 
  21.             while (chars-->0) 
  22.                 put_fs_byte(0,buf++); 
  23.         } 
  24.     } 
  25.     inode->i_atime = CURRENT_TIME
  26.     return (count-left)?(count-left):-ERROR; 

整体看,就是一个 while 循环,每次读入一个块的数据,直到入参所要求的大小全部读完为止。

直接看 bread 那一行。

  1. int file_read(struct m_inode * inode, struct file * filp, char * buf, int count) { 
  2.     ... 
  3.     while (left) { 
  4.         ... 
  5.         if (!(bh=bread(inode->i_dev,nr))) 
  6.     } 

这个函数就是去读某一个设备的某一个数据块号的内容,展开进去看。

  1. struct buffer_head * bread(int dev,int block) { 
  2.     struct buffer_head * bh = getblk(dev,block); 
  3.     if (bh->b_uptodate) 
  4.         return bh; 
  5.     ll_rw_block(READ,bh); 
  6.     wait_on_buffer(bh); 
  7.     if (bh->b_uptodate) 
  8.         return bh; 
  9.     brelse(bh); 
  10.     return NULL

其中 getblk 先申请了一个内存中的缓冲块,然后 ll_rw_block 负责把数据读入这个缓冲块,进去继续看。

  1. void ll_rw_block(int rw, struct buffer_head * bh) { 
  2.     ... 
  3.     make_request(major,rw,bh); 
  4.  
  5. static void make_request(int major,int rw, struct buffer_head * bh) { 
  6.     ... 
  7. if (rw == READ
  8.         req = request+NR_REQUEST; 
  9.     else 
  10.         req = request+((NR_REQUEST*2)/3); 
  11.  
  12.     while (--req >= request) 
  13.         if (req->dev<0) 
  14.             break; 
  15.     ... 
  16.  
  17.     req->dev = bh->b_dev; 
  18.     req->cmd = rw; 
  19.     req->errors=0; 
  20.     req->sector = bh->b_blocknr<<1; 
  21.     req->nr_sectors = 2; 
  22.     req->buffer = bh->b_data; 
  23.     req->waiting = NULL
  24.     req->bh = bh; 
  25.     req->next = NULL
  26.     add_request(major+blk_dev,req); 

看,这里就用到了刚刚说的结构咯。

具体说来,就是该函数会往刚刚的设备的请求项链表 request[32] 中添加一个请求项,只要 request[32] 中有未处理的请求项存在,都会陆续地被处理,直到设备的请求项链表是空为止。

具体怎么读盘,就是与硬盘 IO 端口进行交互的过程了,可以继续往里跟,直到看到一个 hd_out 函数为止,本讲不展开了。

 

具体读盘操作,后面会有详细的章节展开讲解,本讲你只需要知道,我们在 main 函数的 init 系列函数中,通过 blk_dev_init 为后面的块设备访问,提前建立了一个数据结构,作为访问块设备和内存缓冲区之间的桥梁,就可以了。

 

来源:低并发编程内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯