从fread 到 磁盘驱动

author: hjjdebug

date: 2024年 03月 28日 星期四 16:49:14 CST

description: 从fread 到 磁盘驱动

文章目录

    • [1. linux 内核调用栈](#1. linux 内核调用栈)
    • [2. 读中断服务程序.](#2. 读中断服务程序.)
    • [3. 何时计算的柱面,磁头,扇区号? 现代磁盘还有柱面,磁头,扇区概念吗?](#3. 何时计算的柱面,磁头,扇区号? 现代磁盘还有柱面,磁头,扇区概念吗?)
    • [4. 固定的dev,block, 是不是每次都能找到固定的buffer缓冲区 bh ?](#4. 固定的dev,block, 是不是每次都能找到固定的buffer缓冲区 bh ?)

fread 函数到底是怎样工作的? 看下面代码

复制代码
char buf[1024];
FILE *fp=fopen("1.txt",1); // 从文件系统中找到了1.txt 所对应的磁盘位置.
int n=fread(buf,1,sizeof(buf),fp); //从磁盘中读取1024个字节到内存,返回实际读取的字节数.

fread 通过 libc 库进入内核调用, libc部分咱就不说了,过程而已,看内核调用.

1. linux 内核调用栈

// 这里向硬盘控制器芯片设置了磁道,磁头,扇区号,设置了硬盘中断服务程序回调函数,并发命令为读数据,让磁盘作出反应,

//然后一路凯歌返回

0 in hd_out of hd.c:192

1 in do_hd_request of hd.c:341 //返回

2 in add_request of ll_rw_blk.c:76 // 返回

3 in make_request of ll_rw_blk.c:143 // 返回

4 in ll_rw_block of ll_rw_blk.c:155 // 返回

// 要等待数据到达,数据更新后返回 , 当磁盘迟迟不响应时,系统就不得不在这里等待了.

// 一次只能读一块数据到磁盘缓冲块中,早期的磁盘缓冲块大小时1024bytes, 2个扇区大小.

// 这就是内存cache, 当下一次再读该块数据时,就不用读磁盘了,而是可以直接从缓存中拿数据.

//bread 把数据读取到了磁盘缓冲区,有可能需要从磁盘读,也可能不用读了(刚读过,数据还有效)

5 in bread of buffer.c:285

// 当读取数据很大时,就只能循环一次次的调用bread 函数了.

// 每次把数据读取到缓冲块,再从缓冲块中把数据copy到用户区(file_read函数)

6 in file_read of file_dev.c:27

7 in sys_read of read_write.c:77 //直接返回

8 in system_call of system_call.s:94 // 判别一下是否需要调度进程,然后返回

2. 读中断服务程序.

当磁盘把数据准备好,发磁盘中断请求, CPU 响应中断,进硬盘中断服务程序

执行设定的回调函数void read_intr(void)

从端口读取一个扇区数据

port_read(HD_DATA,CURRENT->buffer,256); //从端口读取256个word,一个扇区,当时的总线16bits!

CURRENT->buffer += 512; //缓冲指针加512,以便存下一次数据, 这个缓冲区就是bh, bread 等待的就是它.

CURRENT->sector++; // 扇区数加1

//如果请求的扇区数比较多, 会继续设定read_intr 为回调,函数返回,中断服务程序结束.

if (--CURRENT->nr_sectors) {

do_hd = &read_intr; //还有数据需要读,设置read_intr(自己) 为回调,继续读

return;

}

如果磁盘请求的扇区数较多,磁盘数据还没有读完,会再次触发硬盘中断,引起新一轮中断响应.

其实最多也就2个扇区,而且每次也必然是2个扇区,因为内核就是这么设定的. 一次一块.

众多的数据,那是靠file_read 循环来获取的.

3. 何时计算的柱面,磁头,扇区号? 现代磁盘还有柱面,磁头,扇区概念吗?

当你打开文件fopen 时, 文件系统就给你找到了inode号,它记录了文件位置(逻辑地址块号).

在bread 函数中有一个getblk 调用,根据dev(硬盘号例如0x301)和块号查找磁盘缓冲块函数

if (!(bh=getblk(dev,block))) //一定会得到一个bh,并且其bh->count>=1, >1表示不只一处使用 =1是新申请到的.

哦! buffer_head 结构中没有柱面,磁头,扇区信息.

在do_hd_request 函数中有以下代码:

复制代码
	__asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0),
			"r" (hd_info[dev].sect)); //余数在edx,为sector,商在eax,继续做除法
	__asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0),
			"r" (hd_info[dev].head)); //block/sector/head = cylinder, 余数head
	sec++;

__asm__汇编语法,有2个冒号分割,第一部分是输出寄存器,第二部分为输入寄存器,第三部分(若有的话,那就需要3个冒号了)为其它改动寄存器

第一行的汇编代码的意思是输出把eax赋值给block, 把edx赋值给sec, 输入时把block送eax, 把0送edx. 把hd_info[dev].sec送寄存器

用eax/寄存器, 商在eax, 余数在edx, 注意,做完除法后eax送给了block, edx送给了sec

第二行的汇编代码. 输出eax给cyl, 余数edx给head, 输入部分block送eax,0送edx,hd_info[dev].head送寄存器

此时的block是第一步操作得到的整数,再继续除以head, 则整数是cyl,余数是head.

如果汇编代码你看懂了, 还要理解为什么要这样算, 这涉及到硬盘的构造. 对于一片双面磁盘,上下有2个磁头可以读写上面和下面.

磁盘被划分为很多同心圆叫磁道, 每个磁道又被按512bytes 划分为扇区.

硬盘就是由多个双面磁盘摞在一起组成的,这时候这些同心圆不叫磁道了,叫柱面,对吧,很形象. 多个磁盘的相同的磁道构成柱面.

心存这个硬盘结构,就理解了为什么这样就得到了柱面号,磁头号,扇区号

我想查看一下我的磁盘参数, 发现fdisk, smartctl 都不再提供柱面,磁头参数,只提供存储容量,难道是这些参数不使用了吗?

查了一下互联网:

CHS编址方式在早期的小容量盘中非常流行.

目前的大容量硬盘的设计已经有所改变,转为LBA编址方式。不再划分柱面和磁头号, 这些数据由硬盘自身保留,

而磁盘对外提供全部为线性的地址,即LBA地址。(Logical Block Address)

是啊, 柱面,磁头,扇区是磁盘厂家的事,没必要暴露给外边, 所以改良后,直接传逻辑地址, 磁盘就可以工作了.

复制代码
sudo fdisk -L /dev/sda

命令(输入 m 获取帮助): p
Disk /dev/sda:931.53 GiB,1000204886016 字节,1953525168 个扇区
Disk model: ST1000DM010-2EP1
单元:扇区 / 1 * 512 = 512 字节
扇区大小(逻辑/物理):512 字节 / 4096 字节
I/O 大小(最小/最佳):4096 字节 / 4096 字节
磁盘标签类型:dos
磁盘标识符:0xe264da3d

设备       启动      起点       末尾       扇区   大小 Id 类型
/dev/sda1            2048  838862847  838860800   400G 83 Linux
/dev/sda2       838862848 1953525167 1114662320 531.5G 83 Linux

命令(输入 m 获取帮助): q

可见,现在大容量磁盘都是一次读取4K字节, 512字节已成过去式.

为啥?原来的磁盘数据总线才16bits,现在都是64bits的天下, 原来接口读一次才能读2bytes, 现在接口读一次就能读8bytes.

4. 固定的dev,block, 是不是每次都能找到固定的buffer缓冲区 bh ?

答, 不是. 磁盘buffer缓冲区被串成一个链表来管理, 空闲的缓冲区也被串成一个链表, 当(dev,block)对申请一块缓存存放数据时,

从闲置的缓冲区中拿一个最闲的就可以了. 就是拉郎配! 不过要把配对的bh挂在由(dev,block)构建的hash 表中. 这样当再次读取

(dev,block)数据时,先到hash表中看一下,有bh, 就不用到磁盘读了,直接去用就可以了.

那bh 什么时候失效呢?

嗯, 磁盘缓存是循环着使用的,当bh变成最闲的那一个,被新的(dev,block)拉郎配的时候,那旧hash表中记录的信息就必需要删除了.

代码也是现实世界的反映, 搞清了外部道理,看代码也就看懂了.

相关推荐
deeper_wind25 分钟前
MySQL数据库基础(小白的“升级打怪”成长之路)
linux·数据库·mysql
Raners_31 分钟前
【Linux】文件权限以及特殊权限(SUID、SGID)
linux·安全
egoist202334 分钟前
【Linux仓库】进程优先级及进程调度【进程·肆】
linux·运维·服务器·进程切换·进程调度·进程优先级·大o1调度
2301_1472583692 小时前
7月2日作业
java·linux·服务器
xuanzdhc6 小时前
Linux 基础IO
linux·运维·服务器
愚润求学6 小时前
【Linux】网络基础
linux·运维·网络
bantinghy7 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志8 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手8 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux