Как данные копируются из пользовательского пространства в пространство ядра и наоборот во время задач ввода-вывода?

#operating-system #system-calls

Вопрос:

Я изучаю курс по операционной системе и на слайде 32: https://people.eecs.berkeley.edu/~кубитрон/курсы/cs162-S19/sp19/статика/лекции/3.pdf

профессор кратко сказал fread и fwrite реализовал буфер пользовательского пространства, что более эффективно, чем прямой вызов системных функций read / write и может сэкономить доступ к диску, но не объяснил, почему.

Представьте себе эти два сценария: нам нужно прочитать/записать 16 байт, пользовательский буфер 4 байта, сценарий один использует fread / fwrite , сценарий два направляет использование read / write , который обрабатывает один байт каждый раз

Мои вопросы таковы:

  1. Поскольку fread вызовы read ниже, сколько read вызовов функций будет вызвано соответственно?
  2. Является ли передача данных, будь то один байт или 1 МБ, между буфером пространства пользователя и буфером пространства ядра, выполняемой ядром, и во время передачи не задействован переключатель режима пользователя/ядра?
  3. Сколько обращений к диску выполняется соответственно? Разве буфер ядра не вступит в игру во время второго сценария?
  4. read Функция ssize_t read(int fd, void *buf, size_t count) также имеет параметры буфера и количества, могут ли они заменить роль буфера пользовательского пространства?

Ответ №1:

  1. Поскольку fread вызывает чтение снизу, сколько вызовов функций чтения будет вызвано соответственно?

Поскольку fread() в основном это просто заполнение буфера (в пользовательском пространстве, вероятно , в общей библиотеке) перед read() тем, как «в лучшем случае количество read() системных вызовов» будет зависеть от размера буфера.

Например; с буфером в 8 КБ; если вы читаете 6 байтов одним fread() или если вы читаете 6 отдельных байтов с 6 fread() вызовами; затем read() , вероятно, будет вызван один раз (чтобы получить до 8 КБ данных в буфер).

Однако; read() может возвращать меньше данных, чем было запрошено (и это очень часто встречается в некоторых случаях — например stdin , если пользователь вводит недостаточно быстро). Это означает, что fread() он может read() попытаться заполнить свой буфер, но read() может прочитать только несколько байтов; поэтому fread() ему нужно позвонить read() позже, когда ему понадобится больше данных в своем буфере. В худшем случае (когда read() каждый раз возвращается только 1 байт) чтение 6 байтов с одним fread() может привести read() к вызову 6 раз.

  1. Является ли передача данных, будь то один байт или 1 МБ, между буфером пространства пользователя и буфером пространства ядра, выполняемой ядром, и во время передачи не задействован переключатель режима пользователя/ядра?

Часто read() (в стандартной библиотеке C) вызывает какую sys_read() -либо функцию»», предоставляемую ядром. В этом случае при вызове «» происходит переключение на ядро sys_read() , затем ядро делает все необходимое для получения и передачи данных, затем происходит один переход обратно из ядра в пользовательское пространство.

Однако; ничто не говорит о том, как должно работать ядро. Например, ядро может предоставлять только « sys_mmap() » (и не предоставлять никаких» sys_read() «), а read() (в стандартной библиотеке C) может использовать» sys_mmap() «. Для другого примера; с экзо-ядром файловые системы могут быть реализованы как общие библиотеки (с «кэшем файловой системы» в общей памяти), поэтому read() работа библиотеки C (с данными файла, находящимися в «кэше файловой системы») может вообще не включать ядро.

  1. Сколько обращений к диску выполняется соответственно? Разве буфер ядра не вступит в игру во время второго сценария?

Существует слишком много возможностей. Например.:

а) Если вы читаете из канала (где данные находятся в буфере ядра и ранее были записаны другим процессом), то доступа к диску не будет (потому что данные никогда не были на каком-либо диске с самого начала).

б) Если вы читаете из файла, а ОС уже кэшировала данные файла; тогда может не быть доступа к диску.

c) Если вы читаете из файла, и ОС уже кэшировала данные файла; но файловой системе необходимо обновить метаданные (например, поле «время доступа» в записи каталога файла), то может быть несколько обращений к диску, которые не имеют ничего общего с данными файла.

d) Если вы читаете из файла, а ОС не кэшировала данные файла; тогда потребуется по крайней мере один доступ к диску. Не имеет значения, вызвано ли это fread() попыткой прочитать весь буфер, read() попыткой прочитать все 6 байтов сразу или ОС извлекает весь дисковый блок из-за первого « read() одного байта» в серии из шести отдельных запросов « read() одного байта». Если ОС вообще не кэширует, то шесть отдельных запросов « read() по одному байту» будут по меньшей мере 6 отдельными обращениями к диску.

е) файловой системы код может потребоваться для доступа к некоторым частям диска, чтобы определить, где файл данных, на самом деле, прежде чем он сможет прочитать файл данных, и запрошенный файл данных может быть разделен между несколькими блоков/секторов на диске; так что значение 2 или более байт из файла (независимо от того, было ли это вызвано fread() или « read() 2 или более байт») может привести несколько обращений к диску.

f) с массивом RAID 5/6, включающим 2 или более физических диска (где чтение «логического блока» включает чтение блока с одного диска, а также чтение информации о четности с другого диска), количество обращений к диску может быть удвоено.

  1. Функция чтения ssize_t read(int fd, void *buf, количество size_t) также имеет параметры буфера и количества, могут ли они заменить роль буфера пользовательского пространства?

Да; но если вы используете его для замены роли буфера пользовательского пространства, то вы в основном просто реализуете свой собственный дубликат fread() .

Его чаще используют fread() , когда вы хотите обрабатывать данные как поток байтов и read() (или, возможно mmap() ), когда вы не хотите обрабатывать данные как поток байтов.

Для случайной пример; может быть, вы работаете с BMP-файлом, так что вы читайте на «гарантированную 14 байт в файл формата спец» в заголовке, а затем проверить/декодирования/обработки заголовка; затем (после выяснения, где он находится в файле, насколько он большой и какой формат в) вы можете seek() получить пиксельные данные и прочитать все это в массив (тогда, возможно, породит 8 потоков для обработки пиксельных данных в массиве).

Комментарии:

1. Спасибо за ваше подробное объяснение, просто понял, что мне нужно вручную нажать на награду за ваш ответ.