前言
我们大多数的文件是存储在磁盘上面的
然后 我们通过 open + read/write 相关 api 是控制的是 磁盘 和 内存 之间的数据交互
磁盘 到 内存, 或者 内存 到 磁盘
我们这里 来大致看一下 磁盘到 内存的这一个过程
调试读取磁盘数据到 page 的流程
这里的流程主要是包含如下
1. 系统调用 read 封装读取数据的请求
2. scsi 中间层初始化 scsi 的命令, 做一些准备工作
3. 底层驱动[这里是 ata] 发送 ata 命令到具体的设备, 以基于 dma 将数据拷贝到内存
1. 系统调用 read 封装读取数据的请求
这个 读取磁盘的请求 封装成为一个 bio, 其中 bi_sector 表示的是需要读取的数据的 扇区
比如这里的 sector 是 18220, 其计算是通过 ext4_map_blocks 根据 inode 获取的存储的 block 的相关信息, 进而 计算的 sector
然后具体的请求的实际处理 是在这里开始处理的
轮询队列中的请求, 然后 进行处理
2. scsi 中间层初始化 scsi 的命令, 做一些准备工作
这一层主要做的事情是 基于 bio 请求, 封装 request, 初始化 scsiCmd
根据 bio 封装 scsiCmd, 两个步骤 get_request 是创建 request, init_request_from_bio 是根据 bio 初始化 request 的一部分配置
相对比较重要的数据传输主要是 __sector 字段
将 scsiCmd 传递到 底层的驱动
sd.sd_setup_read_write_cmnd 中 初始化 scsiCmd 的请求
第一个字节为 读取命令
第二个字节为 flags
第三四五六字节为 block 的编号
第七字节为 0
第八九字节为 需要读取的连续的 扇区 的数量
第十字节 为 0
第一个字节,因为是读取命令,初始化为 READ_6, 使用十字节交互 + (READ_10 - READ_6), 因此第一个字节为 READ_10 为 28
(gdb) x /10gx 0xffff88007f746868
0xffff88007f746868: 0x0000844900000028 0x0000000000000002
0xffff88007f746878: 0xffff88007f746868 0x0000000000000010
0xffff88007f746888: 0x0000000000000000 0x0000000000000000
0xffff88007f746898: 0x0000000000000000 0xffff88007f7468a0
0xffff88007f7468a8: 0xffff88007f7468a0 0x00000000000000000x28 表示的是 读取指令
0x00 flags
0x000004984 表示待读取扇区的索引, 18220
0x00 固定为 0
0x0002 为需要连续读取的 扇区的数量
0x00 固定为 0
然后接下来是将 scsiCmd 转换到底层设备
查看一下 scsiCmd.cmnd 的数据, 和上面 一致
(gdb) x /10gx 0xffff88007f746868
0xffff88007f746868: 0x0000844900000028 0x0000000000000002
0xffff88007f746878: 0xffff88007f746868 0x0000000000000010
0xffff88007f746888: 0x0000040000000000 0x0000000000000000
0xffff88007f746898: 0x000000010008a7f0 0xffff88007f8cf1e0
0xffff88007f7468a8: 0xffff88007f8cf1e0 0x0000000000001d4c
3. 底层驱动[这里是 ata] 发送 ata 命令到具体的设备, 以基于 dma 将数据拷贝到内存
将 scsiCmd 转换为 ata 命令, 并向给定的 设备 发送请求
这里 qc 就是 ata 设备的命令, 这里根据 scsiCmd 创建所需要的 ata_queue_cmd
具体的步骤大概是如下三步, sff_qc_issue, bmdma_set_up, bmdma_start
sff_tf_load 中传输 feature, block, n_blocks, scsi_command 中的 block, n_blocks 对应到 tf 中分别对应于,tf.lbah/lbam/lbal, tf.nsec
bmdma_setup 中传输 bmdma.prd_dma,reset bmdma.start,command.ATA_READ
bmdma_start 中传输 bmdma.start
sff_tf_load, 这里 tf->lbal, tf->lbam, tf->lbah 合计起来 73 * 256 + 132 = 18220
bmdma_setup, 传递了三条指令
bmdma_start 传递了 start 命令
bio 使用的 page 如何 和 dma 关联起来的?
bio 使用的 page 的初始化是在 ext4_mpage_readpages 中初始化了 bio 请求之后
将从 pagecache 中分配的 page, 挂在 bio 上面
这里将 bio 中的 bio->bi_io_vec和 scsi_cmd->sdb->table->sgl 的 scatterlist 关联起来
然后后面新建 ata_queue_cmd 之后根据 scsi_cmd 初始化 ata_cmd
如下图, 可以看到 这个对应关系
然后 最后就是 dma 将数据从 设备读取, 拷贝到该 page 的流程了
这个 目前没有找到, 作为一个 todo, 后面回来补充
拷贝的目标 page 来自于哪里?
这个 page 分配自这里的 pages, 来自于 page_pool
然后将该物理页放入 page_cache_tree, 根据 mapping 和 page->index 进行索引
外面 find_get_page 也是通过 mapping 和 index 进行索引的, 查询的就是 page_cache_tree 中的物理页
scsi 日志分析
# 打开 所有 scsi 日志
echo -1 > /proc/sys/dev/scsi/logging_level
# 关闭 所有 scsi 日志
echo 0 > /proc/sys/dev/scsi/logging_level
比如 上面的例子, 读取 block 为 18220 的数据
-- blk_peek_request 中的 scsi_setup_command 中的 sd_setup_read_write_cmnd
[ 175.242337] sd 0:0:0:0: [sda] sd_setup_read_write_cmnd: block=18820, count=2
[ 175.244203] sd 0:0:0:0: [sda] block=18820
[ 175.244203] sd 0:0:0:0: [sda] reading 2/2 512 byte blocks.
-- scsi_dispatch_cmd 中 scsi_log_send
[ 175.252363] sd 0:0:0:0: [sda] tag#0 Send: scmd 0xffff88007fab8900
[ 175.253165] sd 0:0:0:0: [sda] tag#0 CDB: Read(10) 28 00 00 00 49 84 00 00 02 00-- scsi_softirq_done 中的 scsi_log_completion
[ 175.257789] sd 0:0:0:0: [sda] tag#0 Done: SUCCESS Result: hostbyte=DID_OK driverbyte=DRIVER_OK
[ 175.258937] sd 0:0:0:0: [sda] tag#0 CDB: Read(10) 28 00 00 00 49 84 00 00 02 00
[ 175.259519] sd 0:0:0:0: [sda] tag#0 scsi host busy 1 failed 0
-- scsi_softirq_done 中的 scsi_finish_command
[ 175.260058] sd 0:0:0:0: Notifying upper driver of completion (result 0)
-- scsi_softirq_done 中的 scsi_finish_command 中的 drv->done
[ 175.260444] sd 0:0:0:0: [sda] tag#0 sd_done: completed 1024 of 1024 bytes
-- scsi_softirq_done 中的 scsi_finish_command 中的 scsi_io_completion
[ 175.260878] sd 0:0:0:0: [sda] tag#0 2 sectors total, 1024 bytes done.
完