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



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

教授简要地表示freadfwrite实现用户空间缓冲区,因此比直接调用系统函数更有效read/write并且可以节省磁盘访问,但没有解释原因。

想象一下这两个场景:我们需要读/写 16 个字节,用户缓冲区是 4 字节,场景一使用fread/fwrite,场景二使用read/write进行定向,每次处理一个字节

我的问题是:

  1. 由于fread调用read下面,因此将分别调用多少个read函数调用?
  2. 数据传输,无论是用户空间缓冲区和内核空间缓冲区之间的一个字节还是 1mb,是否全部由内核完成,传输过程中不涉及用户/内核模式切换?
  3. 分别执行多少次磁盘访问?在场景二中,内核缓冲区不会发挥作用吗?
  4. read函数ssize_t read(int fd, void *buf, size_t count)也有缓冲区和计数参数,这些参数可以取代用户空间缓冲区的作用吗?
由于下面
  1. 读取了 fread 调用,因此将分别调用多少个 read 函数调用?

因为fread()主要只是在read()前面拍打一个缓冲区(在用户空间中,可能在共享库中),所以"read()系统调用的最佳情况数量"将取决于缓冲区的大小。

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

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

    数据传输,
  1. 无论是用户空间缓冲区和内核空间缓冲区之间的一个字节还是 1mb,是否全部由内核完成,并且在传输过程中不涉及用户/内核模式切换?

通常,read()(在C标准库中)调用内核提供的某种"sys_read()"函数。在这种情况下,当调用"sys_read()"时,有一个切换到内核,然后内核做任何它需要的事情来获取和传输数据,然后有一个从内核切换到用户空间的开关。

例如,内核只能提供"sys_mmap()"(不提供任何"sys_read()"),而read()(在C标准库中)可以使用"sys_mmap()"。再举一个例子;使用 exo-kernel,文件系统可能实现为共享库(共享内存中带有"文件系统缓存"),因此 C 库("文件系统缓存"中的文件数据)完成的read()可能根本不涉及内核。

  1. 分别执行了多少次磁盘访问?在场景二中,内核缓冲区不会发挥作用吗?

可能性太多了。 例如:

a) 如果您从管道读取(其中数据位于内核的缓冲区中,并且以前由不同的进程写入),则不会有磁盘访问(因为数据从未在任何磁盘上开始)。

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

c) 如果您正在从文件中读取并且操作系统已经缓存了该文件的数据;但是文件系统需要更新元数据(例如,文件目录条目中的"访问时间"字段),那么可能会有多个与文件数据无关的磁盘访问。

d) 如果您正在读取文件并且操作系统尚未缓存文件的数据;然后至少需要一次磁盘访问。无论是由fread()尝试读取整个缓冲区、read()尝试一次读取所有 6 个字节,还是操作系统由于在一系列六个单独的"一个字节read()"请求中的第一个"一个字节read()"而获取整个磁盘块,这并不重要。如果操作系统根本不执行缓存,则六个单独的"一个字节read()"请求将至少是 6 个单独的磁盘访问。

e) 文件系统代码可能需要访问磁盘的某些部分,以确定文件数据的实际位置,然后才能读取文件的数据;并且请求的文件数据可以在磁盘上的多个块/扇区之间拆分;因此,从文件中读取 2 个或更多字节(无论是由fread()还是"2 个或更多字节的read()"引起的)可能会导致多次磁盘访问。

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

    读取
  1. 函数ssize_t read(int fd, void *buf, size_t count)也有缓冲区和计数参数,这些可以代替用户空间缓冲区的作用吗?

是的; 但是如果你用它来替换用户空间缓冲区的角色,那么你主要只是实现你自己的fread()副本。

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

使用。举个随机的例子;也许你正在使用一个BMP文件;所以你读了"根据文件格式的规范保证为14个字节"的标题;然后检查/解码/处理标题;然后(在确定它在文件中的位置,它有多大以及它是什么格式之后)你可能会seek()像素数据并将其全部读入数组(然后可能生成8个线程来处理数组中的像素数据)。

最新更新