Эта статья воспроизведена с https://0xffffff.org/2017/05/01/41-linux-io/
Автор: светлые чернила
Нажмите «Календарь программиста Xiaobing» выше и выберите «Верх или звезда».
Ваше внимание много значит!
Слова редактора
Недавно я просматривал соответствующие принципы персистентности Redis, Антирез в своей статье «Расшифровка персистентности Redis» (ссылка в конце статьи) сказал, что операция записи с персистентностью в базу данных делится на следующие этапы:
-
1. Клиент отправляет команду операции записи и данные (данные находятся в памяти клиента)
-
2. Сервер получает операцию записи и данные, отправленные клиентом по сети (данные хранятся в памяти сервера)
-
3. Сервер изменяет данные в памяти и одновременно вызывает системные функции.
write
Выполнить операцию и записать данные на диск (данные находятся в буфере системной памяти сервера) -
4. Операционная система передает данные из буфера на контроллер диска (данные находятся в кэше диска)
-
5. Контроллер диска записывает данные на физический носитель диска (данные фактически попадают на диск)
Для сохранения нам необходимо проанализировать, не теряются ли данные в следующих двух ситуациях:
-
Ненормальный сбой базы данных
-
Вся система выключена
Для такого приложения, как Redis (служба базы данных), если операция на шаге 3 выполнена успешно, даже в случае сбоя базы данных ядро гарантирует, что данные будут записаны на диск и не будут потеряны; однако если вся система выключена, сценарий То есть, вы должны дождаться, пока операция на шаге 5 не завершится успешно, прежде чем операция записи может считаться действительно успешной.
И от шага 3 до шага 5 будет задействовано множество принципов Linux IO, особенноPage Cache
иBuffer Cache
Дождитесь кеша. Я долго искал соответствующие учебные материалы в Интернете, и нашел, что эта статья очень подробная. Я прошу согласия оригинального автора, и внизу блога есть соглашение. Некоммерческое использование может быть подписано и воспроизведено по желанию, поэтому я перепечатаю его для всех, чтобы изучить вместе.
Статья относительно длинная, поэтому я вырезал ее часть и перечислил наиболее важные моменты знаний в остальной части контента.Вы можете выбрать интересующую вас часть и прочитать ее, чтобы сэкономить время каждого.
-
Система кеша ввода-вывода Linux, разница между stdio и кешем ядра,
Page Cache
иBuffer Cache
разница. -
Буферизованный ввод-вывод,
mmap(2)
, Разница прямого ввода-вывода. -
Write Through
иWrite back
Две стратегии обновления кеша.
написать впереди
Прежде чем начать формальную дискуссию, позвольте мне задать несколько вопросов:
-
Когда дело доходит до дисков, в чем самая большая разница между HDD-дисками и SSD-дисками? Влияют ли эти различия на дизайн нашей системы?
-
Запись файлов в одном потоке немного медленная. Может ли открытие нескольких потоков для совместной записи файлов ускорить его?
-
write(2)
После успешного завершения функции данные были успешно записаны на диск? Будет ли устройство выключено в это время? Потеряю ли я данные? -
write(2)
Являются ли вызовы атомарными? Нужно ли блокировать файл при многопоточной записи файлов? Существуют ли исключения, такие какO_APPEND
Способ? -
слухи,
mmap(2)
Способ чтения файла быстрее, чем традиционный, потому что на одну копию меньше. Это так? Почему на одну копию меньше?
Если вы думаете, что эти вопросы просты, вы можете четко ответить на них. Так что к сожалению эта статья не для вас, можете закрыть страницу и заняться другими более содержательными делами. Если вы чувствуете, что не можете четко ответить на эти вопросы, то терпеливо прочитайте эту статью, и я верю, что ваше время не будет потрачено впустую.
Ограничено личным временем и объемом статьи, если я не могу дать лучшее объяснение по некоторым темам или иметь профессиональную и точную информацию, я буду давать ссылки только на соответствующие ссылки, пожалуйста, обращайтесь к ним.
Ближе к делу наше обсуждение начинается с иерархии памяти.
пирамида памяти
Ограниченная скоростью доступа и стоимостью носителей данных, структура хранения данных современных компьютеров является пирамидальной [1]. Чем дальше к вершине башни, тем выше эффективность доступа, но чем выше стоимость, тем меньше пропускная способность. Благодаря принципу локальности доступа к программам [2] этот экономичный подход также может обеспечить хорошую эффективность работы. С точки зрения иерархической структуры памяти и того, как компьютер обрабатывает данные, верхний уровень обычно используется как нижний уровень кэша (кэш в широком смысле). Например, регистр кэширует данные кэша ЦП, а уровни кэша ЦП L1~L3 кэшируют друг друга или напрямую кэшируют данные памяти в зависимости от конкретной реализации, а память часто кэширует данные с локального диска.
В этой статье в основном обсуждаются операции дискового ввода-вывода, поэтому она фокусируется только на характеристиках доступа к локальному диску и взаимодействии данных между ним и DRAM.
Кэш везде
Как показано на рисунке, когда программа вызывает различные функции работы с файлами, поток пользовательских данных (User Data), достигающих диска (Disk), показан на рисунке [3]. На рисунке показана иерархическая взаимосвязь функций работы с файлами в Linux и расположение уровня кэш-памяти. Черная сплошная линия посередине — это разделительная линия между пользовательским режимом и режимом ядра.
Анализируя это изображение сверху вниз, первыми являются связанные функции работы с файлами, определенные библиотекой stdio языка C, которые представляют собой кросс-платформенные функции инкапсуляции, реализованные в пользовательском режиме. Функция работы с файлами, реализованная в stdio, имеет собственный буфер stdio, представляющий собой кеш, реализованный в пользовательском режиме. Причина использования кеша здесь проста — системные вызовы всегда дороги. Если пользовательский код постоянно читает или записывает файлы небольшого размера, библиотека stdio агрегирует несколько операций чтения или записи через буфер, что может повысить эффективность программы. Библиотека stdio также поддерживаетfflush(3)
функция для активного обновления буфера и активного вызова базового системного вызова для немедленного обновления
данные в буфере. Особенно,setbuf(3)
Функция может установить буфер пользовательского режима библиотеки stdio или даже отменить использование буфера.
системный вызовread(2)/write(2)
Существует также слой буфера между чтением и записью реального диска, и термин «кэш буфера ядра» используется здесь для обозначения этого уровня кеша. В Linux файловый кеш обычно называетсяPage Cache
, а кэш устройства нижнего уровня называетсяBuffer Cache
Эти два понятия легко спутать, вот краткое введение в концептуальные различия:Page Cache
Он используется для кэширования содержимого файла, который больше связан с файловой системой. Содержимое файла должно быть сопоставлено с фактическим физическим диском, и это отношение сопоставления выполняется файловой системой;Buffer Cache
Используется для кэширования данных блоков запоминающих устройств (например, секторов диска) вне зависимости от наличия файловой системы (метаданные файловой системы кэшируются вBuffer Cache
середина).
Подводя итог, поскольку мы обсуждаем операцию ввода-вывода под Linux, мы, естественно, опускаем пользовательский режим библиотеки stdio и напрямую обсуждаем концепцию уровня системных вызовов. Учащиеся, интересующиеся уровнем ввода/вывода библиотеки stdio, могут изучить его самостоятельно. Из приведенного выше описания также известно, что кеш файла на уровне ядра хранится в файловой системе.Page Cache
середина. Таким образом, следующее обсуждение в основном посвящено системным вызовам, связанным с вводом-выводом, и файловым системам.Page Cache
некоторые механизмы.
Стек ввода-вывода в ядре Linux
В этом разделе рассматривается структура стека ввода-вывода ядра Linux. Во-первых, полная картина [4]:
Как видно из рисунка, от интерфейса системного вызова вниз стек ввода-вывода под Linux имеет примерно три уровня:
-
уровень файловой системы с
write(2)
Например, ядро копируетwrite(2)
Данные пользовательского режима, указанные параметром, хранятся в кэше файловой системы и вовремя синхронизируются с нижним уровнем. -
Блочный уровень, управляет очередью ввода-вывода блочных устройств, объединяет и сортирует запросы ввода-вывода (помните алгоритм планирования ввода-вывода, изученный в курсе по операционной системе?)
-
Уровень устройств напрямую взаимодействует с памятью через DMA для завершения взаимодействия между данными и конкретными устройствами.
В сочетании с этой картинкой подумайте о буферизованном вводе-выводе, используемом в системном программировании Linux.mmap(2)
, Direct IO, как эти механизмы связаны со стеком ввода-вывода в Linux? Приведенная выше схема немного усложнена, я рисую простую схему и добавляю расположение этих механизмов:
Теперь это очевидно? Использование традиционного буферизованного ввода-выводаread(2)
Как выглядит процесс чтения файла? Предположим, вы хотите прочитать холодный файл (которого нет в кэше),open(2)
После открытия файлового ядра устанавливается ряд структур данных, а затем вызываетсяread(2)
, добраться до уровня файловой системы и найтиPage Cache
Отображение диска для этого местоположения не существует вPage Cache
и связаны с соответствующим сектором. Затем запрос переходит на уровень блочного устройства, где
Очередь в очереди ввода-вывода, принять серию планирования, а затем перейти на уровень драйвера устройства.В это время DMA обычно используется для чтения соответствующих секторов диска в кэш, а затемread(2)
Скопируйте данные в буфер пользовательского режима, предоставленный пользователем (read(2)
указанные параметры)
Сколько копий всего процесса? с диска наPage Cache
Впервые изPage Cache
В буфер пользовательского режима идет второй раз. иmmap(2)
Что вы наделали?mmap(2)
напрямуюPage Cache
Он сопоставляется с адресным пространством пользовательского режима, поэтомуmmap(2)
Нет второго процесса копирования для чтения файла. Так что же делает Direct IO? Этот механизм более безжалостен, напрямую разрешая пользовательский режим и блочный ввод-вывод.
Стыковка слоев, откажитесь напрямуюPage Cache
, копировать данные с диска напрямую и в пользовательском режиме. Каковы преимущества? Операция записи напрямую сопоставляет буфер процесса с сектором диска и передает данные с помощью прямого доступа к памяти, уменьшая первоначальную потребность вPage Cache
Одна копия слоя повышает эффективность записи. Для чтения первый раз определенно быстрее, чем традиционный метод, но последующее чтение не так хорошо, как традиционный метод (конечно, вы также можете сделать Cache самостоятельно в пользовательском режиме, что и делают некоторые коммерческие базы данных).
В дополнение к традиционному буферизованному вводу-выводу, который может свободно читать и записывать файлы по схеме «смещение + длина»,mmap(2)
И Direct IO, и Direct IO требуют выравнивания данных по страницам.Direct IO также ограничивает, что операции чтения и записи должны быть целым числом, кратным размеру блока базового устройства хранения (даже Linux 2.4 требует целого числа, кратного логическому блоку файловой системы). Таким образом, интерфейс становится все ниже и ниже, в обмен на очевидное повышение эффективности необходимо делать больше вещей на прикладном уровне. Поэтому, если вы хотите эффективно использовать эти расширенные функции, в дополнение к глубокому пониманию механизма, лежащего в их основе, вы также должны усердно работать над проектированием системы.
Page Cache
синхронизация
В широком смысле существует два метода синхронизации Cache, а именно:Write Through
(переписать) иWrite back
(Ответная запись) Из названия видно, что эти два метода являются понятиями, производными от разных методов обработки операций записи (в чистом чтении нет согласованности Cache, не так ли). соответствующий линуксPage Cache
так называемыйWrite Through
означаетwrite(2)
операция копирует данные вPage Cache
Сразу после этого выполнить операцию синхронной записи с нижним уровнем, и вернуться после завершения обновления нижнего уровня. иWrite back
Наоборот, это означает, что после написанияPage Cache
Вы можете вернуться.Page Cache
Операции обновления нижних уровней выполняются асинхронно.
Буферизованный ввод-вывод под Linux использует значение по умолчанию.Write back
механизм, то есть запись файловых операций записывается только вPage Cache
вернуться, послеPage Cache
Операции обновления на диск выполняются асинхронно.Page Cache
Измененная страница памяти называется грязной страницей (Dirty Page), а грязная страница записывается на диск потоком ядра с именем pdflush (Page Dirty Flush) в определенное время. Время и условия записи следующие:
-
Когда свободная память падает ниже определенного порога, ядро должно записать грязные страницы обратно на диск, чтобы освободить память.
-
Когда грязные страницы остаются в памяти дольше определенного порога, ядро должно записать истекшие грязные страницы обратно на диск.
-
Вызов пользовательского процесса
sync(2)
,fsync(2)
,fdatasync(2)
Когда происходит системный вызов, ядро выполняет соответствующую операцию обратной записи.
Стратегия обновления определяется следующими параметрами (единица измерения — 1/100 секунды):
# flush每隔5秒执行一次
root@082caa3dfb1d / $ sysctl vm.dirty_writeback_centisecs
vm.dirty_writeback_centisecs = 500
# 内存中驻留30秒以上的脏数据将由flush在下一次执行时写入磁盘
root@082caa3dfb1d / $ sysctl vm.dirty_expire_centisecs
vm.dirty_expire_centisecs = 3000
# 若脏页占总物理内存10%以上,则触发flush把脏数据写回磁盘
root@082caa3dfb1d / $ sysctl vm.dirty_background_ratio
vm.dirty_background_ratio = 10
скопировать код
По умолчанию используется режим обратной записи.Что делать, если вы хотите указать, что файл является режимом сквозной записи? То есть, когда надежность операции записи превышает эффективность, можно ли это сделать? Конечно, помимо уже упомянутогоfsync(2)
вне системных вызовов, таких какopen(2)
При открытии файла передатьO_SYNC
Этот флаг можно реализовать. Вот справочная статья [5], не повторяя ее (лучше прочитать главы, связанные с TLPI).
Сохраняются ли данные, когда чтение и запись файла отключены? Я полагаю, у вас есть свой собственный ответ. использоватьO_SYNC
илиfsync(2)
Безопасно ли обновлять файл? Современные диски обычно имеют встроенные кэши, и на уровне кода данные могут быть сброшены только в кэш диска. Что происходит, когда отключается питание, а данные уже находятся в кэше диска? Это, наверное, нельзя обобщать. Но вы можете использоватьhdparm -W0
Команда на отключение этого кеша, соответственно производительность диска неизбежно снизится.
напиши в конце
Я тратил менее получаса каждый день на то, чтобы писать по частям в течение недели, что немного неправильно, чтобы сказать, что это новичок, но это всего лишь небольшое углубленное введение в механизм ввода-вывода в Linux. В любом случае, я надеюсь, что студенты, закончившие изучение системного программирования Linux, смогут продолжить изучение и попытаться понять основные механизмы и принципы, лежащие в основе системных вызовов. Результат исследования не имеет значения, важен сам процесс исследования и связанный с ним опыт и методы обучения. Я не пытался ответить на все вопросы, поднятые выше, но после прочтения не знаю, на сколько вы сами сможете ответить?
-Подписывайтесь на меня
Рекомендуемое чтение
Низкоуровневые очереди для TCP/IP
Распределенное ограничение тока на основе Redis и Lua
Сверхдетальный принципиальный анализ AbstractQueuedSynchronizer
Введение в алгоритмы управления перегрузкой TCP
использованная литература
[1] Изображение взято из главы 6 «Компьютерные системы: взгляд программиста». Е5% 99%А8%Е5%В1%В1
[2] Местоположение ссылки, https://en.wikipedia.org/wiki/Locality_of_reference
[3] Изображение взято из «Интерфейса программирования Linux», глава 13. БУФЕРНАЯ ФАЙЛОВАЯ ВВОД/ВЫВОД
[4] Linux Storage Stack Diagram, https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram
[5] Подробное объяснение O_DIRECT и O_SYNC, http://www.cnblogs.com/suzhou/p/5381738.html.
[6] http://librelist.com/browser/usp.ruby/2013/6/5/o-append-atomicity/
[7] Coding for SSD, https://dirtysalt.github.io/coding-for-ssd.html
[8] Дженс Эксбо, автор fio, сопровождающий часть ввода/вывода ядра Linux, домашняя страница инструмента http://freecode.com/projects/fio/
[9] How to benchmark disk, https://www.binarylane.com.au/support/solutions/articles/1000055889-how-to-benchmark-disk-i-o
[10] Подробная архитектура ядра Linux, (De) Morle, издательство People's Posts and Telecommunications Publishing House.
Постоянная расшифровка Redis http://oldblog.antirez.com/post/redis-persistence-demystified.html