Я знаю, что в Linux ядро часто кэширует доступ для чтения и записи на диск, как в приведенных ниже кодах, оно сохраняет msg в буфер после вызова записи и возврата, а затем выполняет фактическую запись, когда буфер заполнен.
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int
main()
{
int fd = open("./test.txt", O_RDWR, 0766);
if (fd < 0) {
printf("open file failed %d\n", errno);
return -1;
}
char msg[100] = "write this msg to disk";
write(fd, msg, strlen(msg));
return 0;
}
Я не знаю реализацию write(), мой вопрос: если буфер находится в памяти, ядро скопирует msg в буфер, например memcpy, будет ли такая копия трудоемкой?
В этом суть буферизованного ввода-вывода: нет необходимости обращаться к устройству для каждой операции.
@WeatherVane Итак, включает ли базовая реализация записи memcpy? в пространстве пользователя или пространстве ядра? Я хочу знать его реализацию, но я могу найти исходный код, кроме этого github.com/lattera/glibc/blob/master/io/write.c
@YongqiZ есть функция write() glibc, которая выполняет системный вызов write(), который вызывает операцию записи в драйвере файловой системы, который планирует операцию записи для драйвера блочного устройства. На всех слоях может быть буферизация/кеширование. Какую реализацию вы хотите знать?
@dimich Первый шаг, после звонка write(fd, msg, size), куда система ставит msg? память пользовательского пространства или память ядра? А как система ставит, использует какую-то функцию типа memcpy?
Вам может быть интересна эта запись в блоге: Путь ввода-вывода из пользовательского пространства в устройство.
Для фактического системного вызова в ядре используется этот исходный код: elixir.bootlin.com/linux/latest/source/fs/read_write.c#L646
@YongqiZ Это зависит (с). В некоторых случаях драйвер блочного устройства может запрограммировать DMA для передачи данных непосредственно из буфера пользовательского пространства на устройство.
@lan Abbott Это действительно хороший блог.





Какова истинная логика реализации write()?
Функция write() вызовет другую функцию, например sys_write().
sys_write() как показано ниже:
int32_t sys_write(int32_t fd, const void* buf, uint32_t count){
if (fd < 0){
printk("sys_write: fd error\n");
}
if (fd == stdout_no){
char tmp_buf[1024] = {0};
memcpy(tmp_buf, buf, count);
console_put_str(tmp_buf);
return count;
}
uint32_t _fd = fd_local2global(fd);
struct file* wr_file = &file_table[_fd];
if (wr_file->fd_flag & O_WRONLY || wr_file->fd_flag & O_RDWR){
uint32_t byte_written = file_write(wr_file, buf, count);
return bytes_written;
}else{
console_put_str("sys_write: not allowed to write file without flag O_RDWR or O_WRONLY\n");
return -1;
}
}
На самом деле это не отвечает на вопрос (все, что он показывает, это то, что будет вызвана некоторая функция с именем file_write). И я не знаю, откуда взялся код, но возможно переполнение буфера. И вызов memcpy кажется бессмысленным.
@interjay Если быть точным, приведенный выше фрагмент кода не может быть выполнен сам по себе. В нем отсутствуют несколько функций, а 1024 является фиксированным значением, вызывающим опасения по поводу переполнения буфера. Однако то, что я подразумеваю под функцией «запись», похоже на это.
Копирование в память выполняется быстрее, чем ввод-вывод на диск. Дисковый ввод-вывод очень медленный, намного медленнее, чем копирование в память.
(В особых случаях мы можем обойти буфер, чтобы выполнить дисковый ввод-вывод напрямую. Например, есть буфер пользовательского пространства, и вы хотите избежать его двойного копирования.)
С буфером ядра ядро может группировать и планировать записи, чтобы выдавать меньше дисковых операций ввода-вывода, выполнять большие последовательные операции ввода-вывода или даже отменять некоторые операции ввода-вывода, если файл удален.
Если вы используете fwrite в libc, есть также буфер пользовательского пространства, чтобы избежать слишком частого захвата ядра.
Это не всегда так, поэтому есть O_DIRECT. Вы можете быть быстрее в специальных приложениях, где копирование данных займет много времени (но в большинстве случаев вы этого не делаете).
@ 12431234123412341234123 Я отредактирую ответ, чтобы упомянуть об этом. Спасибо.
По словам @Ian Abbott, в этом блоге есть очень подробный анализ
Да; ваш буфер копируется в память ядра перед возвратом write; после этого вы можете перезаписать буфер, не затрагивая какой-либо фоновый ввод-вывод, связанный с исходными данными.
Копирование данных требует времени; Это не бесплатно.
write — это универсальная функция, которая работает с любым файловым дескриптором: вы можете писать в последовательную консоль, межпроцессный канал, сетевой сокет, файл, блочное устройство и т. д.
write должен просмотреть дескриптор файла и вызвать правильную реализацию для устройства.
Когда вы добавляете несколько байтов в файл, эти байты перекрывают один или несколько блоков (единиц одинакового размера) файла. Если эти блоки уже существуют, их нужно перенести в память и поместить в буферы. Затем буферы редактируются с новыми байтами и помечаются как «грязные» (измененные).
write запросы не совпадают с блоками. Они часто больше или меньше блоков и не начинаются и не заканчиваются на границах блоков; написание на самом деле является деятельностью по редактированию, которая должна сочетать существующие данные с новыми данными. Когда вы записываете несколько байтов в файл, если вы не записываете точный блок, некоторые из байтов, записываемых в хранилище, будут байтами, которые не пришли из вашей программы: например, нулевые байты заполнения в блоке, если нет других данных, или существующие байты в блоке, которые ваш write не трогал.
Измененные буферы должны быть записаны в хранилище, хотя это действие может быть отложено. Отсрочка действия позволяет оптимизировать: например, если эта программа (или любая другая) выполняет больше операций write над одними и теми же блоками, тогда эти изменения могут быть включены до того, как что-либо будет фактически написано. Не говоря уже о том, что файловая система может каким-то образом упорядочивать записи, что делает их быстрее, например, сводя к минимуму движения головки жесткого диска с вращающимся диском.
Ключевая идея заключается в том, что сброс данных из буферов операционной системы в базовый файл обычно происходит не из-за заполнения буферов, а из-за активности на основе таймера, которая отслеживает грязные буферы.
Функцию write можно использовать для задержки выполнения программы до тех пор, пока предыдущие записи в дескриптор файла не будут зафиксированы в хранилище (с некоторыми задокументированными предостережениями). Функция, возможно, также способствует очистке (заставляет это происходить раньше, чем в противном случае).
Нет. Это быстрее, чем запись напрямую на диск, поскольку кэширование позволяет выполнять меньше операций записи большего объема, чем без него.