从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表中记录的信息就必需要删除了.

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

相关推荐
蜜獾云9 分钟前
docker 安装雷池WAF防火墙 守护Web服务器
linux·运维·服务器·网络·网络安全·docker·容器
小屁不止是运维10 分钟前
麒麟操作系统服务架构保姆级教程(五)NGINX中间件详解
linux·运维·服务器·nginx·中间件·架构
bitcsljl24 分钟前
Linux 命令行快捷键
linux·运维·服务器
ac.char27 分钟前
在 Ubuntu 下使用 Tauri 打包 EXE 应用
linux·运维·ubuntu
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Youkiup1 小时前
【linux 常用命令】
linux·运维·服务器
qq_297504611 小时前
【解决】Linux更新系统内核后Nvidia-smi has failed...
linux·运维·服务器
weixin_437398211 小时前
Linux扩展——shell编程
linux·运维·服务器·bash
小燚~1 小时前
ubuntu开机进入initramfs状态
linux·运维·ubuntu
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http