@@ -5,7 +5,7 @@ categories:
55 - 内核层
66abbrlink : f548d964
77date : 2024-12-24 16:05:00
8- updated : 2024-12-30 16:05 :00
8+ updated : 2024-12-30 17:50 :00
99---
1010
1111<meta name =" referrer " content =" no-referrer " />
@@ -1272,18 +1272,75 @@ open 具体查找文件 inode 的过程,即是 path lookup 的过程。
12721272
12731273所谓通用,是指某些文件系统不单独写 read() 或 read_iter() 回调,而是调 VFS 实现的默认 read 函数 generic_file_read_iter()。
12741274
1275- 读分为两种,一种是 DIRECT_IO,另一种是走 address_space 。
1275+ 在读缓存的过程中,如果不允许进程阻塞,且需要的数据不在内存中,会立即返回失败 。
12761276
1277- ### DIRECT_IO
1277+ 读分为两种,一种是 direct_io,另一种是走 address_space。
12781278
1279- 如果这个 read 操作不能陷入等待(NO_WAIT),且要读取的文件范围内有缓存,则返回 -EAGAIN。否则,先将缓存的数据刷下去,再调用 `mapping->a_ops-> direct_io` 读取数据。
1279+ 如果走 direct_io:
12801280
1281- ### address_space
1281+ 1. 如果这个 read 操作不能陷入等待(NO_WAIT),且要读取的文件范围内有缓存,则返回 -EAGAIN。
12821282
1283- TODO
1283+ 2. 否则,先通过 address_space 将缓存的数据刷下去。
1284+
1285+ 3. 再调用 `mapping->a_ops->direct_io()` 读取数据。
1286+
1287+ 如果走 address_space,用户需要的数据量可能很大,需要一页一页地处理。对于每一页:
1288+
1289+ 1. 从 address_space 中查找对应 page。如果找不到,则以同步方式进行预读,如果这样也拿不到 page,跳转到步骤 6。
1290+
1291+ 2. 如果拿到的 page 带有 readahead 标记,说明我们需要自己预读一些页面。
1292+
1293+ 3. 如果 page 带有 uptodate 标志,则跳到下一步,否则:
1294+ - 等待 page 的 lock 标志被清零(等待 page 被解锁)。
1295+ - 如果 page 带有 uptodate 标志,则跳转到步骤 4。
1296+ - 现在,文件可能被 truncate 了,需要进行检查。如果有 `mapping->a_ops->is_partially_uptodate()` 回调,且通过该回调发现我们需要读的范围内数据是 uptodate 的,则跳转到步骤 4,否则跳转到步骤 5。
1297+
1298+ 4. 现在,数据是确保在内存中的,且是 uptodate 的。将 page 里面的数据拷贝到用户的 buffer 里面,然后进行下个 page 的处理或者退出循环。
1299+ 5. 到这一步,说明有 page,但数据没有 uptodate。
1300+ - 如果 `page->mapping` 为空,则说明这整个页都被 truncate 了,即可以考虑下一块页面的处理(进入 continue)。
1301+ - 接下来需要调用 `mapping->a_ops->readpage()` 读取数据。
1302+ - 回到步骤 4,进行数据拷贝。
1303+
1304+ 6. 到这一步,说明没有对应的 page,需要先分配一个 page,加入到 address_space 和 lru 结构中,然后回到步骤 5。
1305+
1306+ ## 通用 write 流程
1307+
1308+ write 操作会更新 inode 的 mtime 和 ctime,以及 version。同理分为 direct_io 和 address_space 两种。
1309+
1310+ 如果走 direct_io:
1311+
1312+ 1. 如果 address_space 中缓存有要写入范围的数据,且当前进程不能阻塞,则立即返回错误。
1313+
1314+ 2. 否则,先通过 address_space 将缓存的数据刷下去。
1315+
1316+ 3. 现在处理缓存数据的其他问题。对于处于 write 范围内的每一个被缓存的 page 而言:
1317+ - 首先,确保 page 的数据被刷到了磁盘上(上一步已经确保了这一步)。
1318+ - 如果这个 page 做了 mmap,取消这一页的 mmap。
1319+ - 接下来将这个 page 从 address_page 中取下,分为两步:
1320+ - 如果这个页是 dirty 的话,先调用 `mapping->a_ops->launder_page()` 将脏数据刷下去。这一回调与 writepage() 回调的不同在于,它不允许文件系统通过 redirty 的方式跳过对这一页的 flush 操作。
1321+ - 接下来将 page 从 address_page 中取下,然后调用 `mapping->a_ops->freepage()` 释放掉这一 page。
1322+
1323+ 4. 接下来,调用 `mapping->a_ops->direct_io()` 写数据。
1324+
1325+ 5. 然后,继续调用步骤 3 来刷一次 page。这是因为可能有其他进程预读了这一部分的数据,或者因为 mmap 了,然后在访问时出现 page fault 导致这一部分的数据被拉进来了。
1326+
1327+ 6. 如果 direct_io 调用失败了,则通过写 Cache、刷 Cache、再无效化 Cache 的方式写数据。
1328+ - 写 Cache。对于写入范围内的每一页:
1329+ - 调用 `mapping->a_ops->write_begin()`,通知文件系统准备往 page 上数据了。
1330+ - kmap page 后,将数据从用户空间拷贝到 page 上,然后 kunmap page,刷 tlb。
1331+ - 调用 `mapping->a_ops->write_end()`,通知文件系统往 page 上写数据的过程结束。
1332+ - 判断脏数据是否超过某一阈值,以决定是否需要后台刷数据下去。
1333+ - 刷 Cache 和无效化 Cache 的过程与步骤 2、3 类似。
1334+
1335+ 7. 至此,direct_io 的过程结束。
1336+
1337+ 如果是普通的写 Cache,而不是 direct_io,则与上述步骤 6 的写 Cache 步骤相同。
1338+
1339+ 如果写入数据成功,且用户指定了需要 fsync,则通过 `file->f_op->fsync()` 回调将更新的数据刷下去。
12841340
12851341# 参考文章
12861342
128713431. [Linux Kernel Teaching — The Linux Kernel documentation](https://linux-kernel-labs.github.io/refs/heads/master/)
1344+
128813452. [Linux 内核教学 — Linux 系统内核文档](https://linux-kernel-labs-zh.xyz/)
12891346
0 commit comments