С использованием O_SYNC
Затрачиваемое
Общее ЦП
Затрачиваемое
Общее ЦП
1
16
256
4096
0,73
0,05
0,02
0,01
0,73
0,05
0,02
0,01
1030
65,0
4,07
0,34
98,8
0,40
0,03
0,03
Как видно, указание флага O_SYNC приводит к чудовищному увеличению затрачиваемого времени при использовании буфера размером 1 байт более чем в 1000 раз. Обратите также внимание на большую разницу, возникающую при выполнении записей с флагом O_SYNC, между затраченным временем и временем задействования ЦП. Она является последствием блокирования выполнения программы при фактическом сбросе содержимого каждого буфера на диск.
В результатах, показанных в табл. 13.3, не учтен еще один фактор, влияющий на производительность при использовании O_SYNC. Современные дисковые накопители обладают внутренней кэш-памятью большого объема, и по умолчанию установка флага O_SYNC просто приводит к переносу данных в эту кэш-память. Если отключить кэширование на диске (воспользовавшись командой hdparm — W0), влияние O_SYNC на производительность станет еще более существенным. При размере буфера 1 байт затраченное время возрастет с 1030 секунд до приблизительно 16 000 секунд. При размере буфера 4096 байт затраченное время возрастет с 0,34 секунды до 4 секунд. В итоге, если нужно выполнить принудительный сброс на диск буферов ядра, следует рассмотреть, можно ли спроектировать приложение с использованием бóльших по объему буферов для write() или же подумать об использовании вместо флага O_SYNC периодических вызовов fsync() или fdatasync().
В SUSv3 определены два дополнительных флага состояния открытого файла, имеющих отношение к синхронизированному вводу-выводу: O_DSYNC и O_RSYNC.
Флаг O_DSYNC приводит к выполнению в последующем синхронизированных операций записи с целостностью данных завершаемого ввода-вывода (подобно использованию fdatasync()). Эффект от его работы отличается от эффекта, вызываемого флагом O_SYNC, использование которого приводит к выполнению в последующем синхронизированных операций записи с целостностью файла (подобно fsync()).
Флаг O_RSYNC указывается совместно с O_SYNC либо с O_DSYNC и приводит к расширению поведения, связанного с этими флагами при выполнении операций чтения. Указание при открытии файла флагов O_RSYNC и O_DSYNC приводит к выполнению в последующем синхронизированных операций чтения с целостностью данных (то есть прежде чем будет выполнено чтение, из-за наличия O_DSYNC завершаются все ожидающие файловые записи). Указание при открытии файла флагов O_RSYNC и O_SYNC приводит к выполнению в последующем синхронизированных операций чтения с целостностью файла (то есть прежде, чем будет выполнено чтение, из-за наличия O_SYNC завершаются все ожидающие файловые записи).
До выхода версии ядра 2.6.33 флаги O_DSYNC и O_RSYNC в Linux не были реализованы и в заголовочных файлах glibc эти константы определялись как выставление флага O_SYNC. (В случае с O_RSYNC это было неверно, поскольку O_SYNC не влияет на какие-либо функциональные особенности операций чтения.)
Начиная с ядра версии 2.6.33, в Linux реализуется флаг O_DSYNC, а реализация флага O_RSYNC, скорее всего, будет добавлена в будущие выпуски ядра.
До выхода ядра 2.6.33 в Linux отсутствовала полная реализация семантики O_SYNC. Вместо этого флаг O_SYNC был реализован как O_DSYNC. В приложениях, скомпонованных со старыми версиями GNU библиотеки C для старых ядер, в версиях Linux 2.6.33 и выше флаг O_SYNC по прежнему ведет себя как O_DSYNC. Это сделано для сохранения привычного поведения таких программ. (Для сохранения обратной бинарной совместимости в ядре 2.6.33 флагу O_DSYNC было присвоено старое значение флага O_SYNC, а новое значение O_SYNC включает в себя флаг O_DSYNC (на одной из машин это 04010000 и 010000 соответственно). Это позволяет приложениям, скомпилированным с новыми заголовочными файлами, получать в ядрах, вышедших до версии 2.6.33, по меньшей мере семантику O_DSYNC.)
На рис. 13.1 приведена схема буферизации, используемой (для файлов вывода) библиотекой stdio и ядром, а также показаны механизмы для управления каждым типом буферизации. Если пройтись по схеме вниз до ее середины, станет виден перенос пользовательских данных функциями библиотеки stdio в буфер stdio, который работает в пользовательском пространстве памяти. Когда этот буфер заполнен, библиотека stdio прибегает к системному вызову write(), переносящему данные в буферную кэш-память ядра (находящуюся в памяти ядра). В результате ядро инициирует дисковую операцию для переноса данных на диск.