在 I/O 任务期间,数据如何从用户 space 复制到内核 space,反之亦然?

How is data copied from user space to kernel space and vice versa during I/O tasks?

我正在学习操作系统课程,在幻灯片 32 上: https://people.eecs.berkeley.edu/~kubitron/courses/cs162-S19/sp19/static/lectures/3.pdf

教授简单说了freadfwrite实现用户space缓冲区,比直接调用系统函数read/write更高效,可以保存磁盘访问,但没有解释原因。

想象一下这两种情况:我们需要 read/write 16 字节,用户缓冲区是 4 字节,情况一是使用 fread/fwrite,情况二是直接使用 read/write 每次处理一个字节

我的问题是:

  1. 由于下面fread调用了read,分别调用了多少read个函数?
  2. 用户space缓冲区和内核space缓冲区之间的数据传输,无论是单个字节还是1mb,都是由内核完成的,传输过程中不涉及user/kernel模式切换吗?
  3. 分别进行了多少次磁盘访问?内核缓冲区不会在场景二中发挥作用吗?
  4. read函数ssize_t read(int fd, void *buf, size_t count)也有buffer和count参数,这些可以代替用户spacebuffer的作用吗?
  1. Since fread calls read underneath, how many read function calls will be invoked respectively?

因为 fread() 大部分只是在 read() 前面拍打缓冲区(在 user-space 中,可能在共享库中),[=11= 的“最佳案例数” ]系统调用”将取决于缓冲区的大小。

例如;带有 8 KiB 缓冲区;如果您使用单个 fread() 读取 6 个字节,或者如果您使用 6 个 fread() 调用读取 6 个单独的字节;那么 read() 可能会被调用一次(将最多 8 KiB 的数据放入缓冲区)。

但是; read() 可能 return 比请求的数据少(这在某些情况下很常见 - 例如 stdin 如果用户输入的速度不够快)。这意味着 fread() 可能会使用 read() 来尝试填充它的缓冲区,但 read() 可能只会读取几个字节;所以 fread() 需要稍后在其缓冲区中需要更多数据时再次调用 read()。对于最坏的情况(其中 read() 每次只发生在 return 1 个字节)用单个 fread() 读取 6 个字节可能会导致 read() 被调用 6 次。

  1. Is data transfer, whether one single byte or 1mb between user space buffer and kernel space buffer all done by the kernel and no user/kernel mode switch involved during transferring?

通常,read()(在 C 标准库中)调用内核提供的某种“sys_read()”函数。在这种情况下,当调用“sys_read()”时会切换到内核,然后内核会做任何它需要的事情来获取和传输数据,然后会从内核切换回 user-space.

但是;没有什么能说明内核必须如何工作。例如。内核只能提供“sys_mmap()”(而不提供任何“sys_read()”),而 read()(在 C 标准库中)可以使用“sys_mmap()”。再举个例子;使用 exo-kernel,文件系统可以实现为共享库(在共享内存中具有“文件系统缓存”),因此 read() 由 C 库完成(在“文件”中的文件数据) system cache") 可能根本不涉及内核。

  1. How many disk accesses are performed respectively? Won't the kernel buffer come into play during scenario two?

可能性太多了。例如:

a) 如果你正在从管道读取(数据在内核的缓冲区中并且之前由不同的进程写入)那么将不会有磁盘访问(因为数据从来没有在任何磁盘开头)。

b) 如果您正在从文件中读取并且 OS 已经缓存了该文件的数据;那么可能没有磁盘访问。

c) 如果您正在从文件中读取并且 OS 已经缓存了该文件的数据;但是文件系统需要更新meta-data(例如文件目录条目中的“访问时间”字段)然后可能有多个与文件数据无关的磁盘访问。

d) 如果您正在从文件中读取并且 OS 没有缓存文件的数据;那么至少需要一次磁盘访问。不管是 fread() 试图读取整个缓冲区,read() 试图一次读取所有 6 个字节,还是 OS 获取整个磁盘块,因为六个单独的“read() 个字节”请求中的第一个“read() 个字节”请求。如果 OS 根本没有缓存,那么六个单独的“read() 一个字节”请求将至少是 6 个单独的磁盘访问。

e) 文件系统代码可能需要访问磁盘的某些部分以确定文件数据的实际位置,然后才能读取文件数据;并且请求的文件数据可能在磁盘上被分割成多个blocks/sectors;因此从文件中读取 2 个或更多字节(无论它是由 fread() 还是“read() of 2 或更多字节”引起的)都可能导致多次磁盘访问。

f) 对于包含 2 个或更多物理磁盘的 RAID 5/6 阵列(其中读取“逻辑块”涉及从一个磁盘读取块并从另一个磁盘读取奇偶校验信息),数量磁盘访问可以加倍。

  1. The read function ssize_t read(int fd, void *buf, size_t count) also has buffer and count parameters, can these replace the role of user space buffer?

是;但是如果你用它来代替用户 space 缓冲区的角色,那么你主要只是在实现你自己的 fread().

副本

当您希望将数据视为字节流时使用 fread() 更为常见,而当您不想将数据视为字节流时使用 read()(或者 mmap())作为字节流。

举个随机的例子;也许您正在使用 BMP 文件;所以您阅读了“文件格式规范保证为 14 个字节”header;然后 check/decode/process header;然后(在确定它在文件中的位置、它有多大以及它的格式之后)您可能 seek() 到像素数据并将其全部读入一个数组(然后可能产生 8 个线程来处理像素数组中的数据)。