Какова истинная логика реализации write()

Я знаю, что в 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, будет ли такая копия трудоемкой?

Нет. Это быстрее, чем запись напрямую на диск, поскольку кэширование позволяет выполнять меньше операций записи большего объема, чем без него.

stark 28.06.2023 14:20

В этом суть буферизованного ввода-вывода: нет необходимости обращаться к устройству для каждой операции.

Weather Vane 28.06.2023 14:33

@WeatherVane Итак, включает ли базовая реализация записи memcpy? в пространстве пользователя или пространстве ядра? Я хочу знать его реализацию, но я могу найти исходный код, кроме этого github.com/lattera/glibc/blob/master/io/write.c

Yongqi Z 28.06.2023 17:35

@YongqiZ есть функция write() glibc, которая выполняет системный вызов write(), который вызывает операцию записи в драйвере файловой системы, который планирует операцию записи для драйвера блочного устройства. На всех слоях может быть буферизация/кеширование. Какую реализацию вы хотите знать?

dimich 28.06.2023 17:39

@dimich Первый шаг, после звонка write(fd, msg, size), куда система ставит msg? память пользовательского пространства или память ядра? А как система ставит, использует какую-то функцию типа memcpy?

Yongqi Z 28.06.2023 17:44

Вам может быть интересна эта запись в блоге: Путь ввода-вывода из пользовательского пространства в устройство.

Ian Abbott 28.06.2023 17:55

Для фактического системного вызова в ядре используется этот исходный код: elixir.bootlin.com/linux/latest/source/fs/read_write.c#L646

12431234123412341234123 28.06.2023 18:08

@YongqiZ Это зависит (с). В некоторых случаях драйвер блочного устройства может запрограммировать DMA для передачи данных непосредственно из буфера пользовательского пространства на устройство.

dimich 28.06.2023 18:16

@lan Abbott Это действительно хороший блог.

Yongqi Z 29.06.2023 03:13
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
9
98
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Какова истинная логика реализации 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 28.06.2023 15:13

@interjay Если быть точным, приведенный выше фрагмент кода не может быть выполнен сам по себе. В нем отсутствуют несколько функций, а 1024 является фиксированным значением, вызывающим опасения по поводу переполнения буфера. Однако то, что я подразумеваю под функцией «запись», похоже на это.

Whozcry 28.06.2023 15:57

Копирование в память выполняется быстрее, чем ввод-вывод на диск. Дисковый ввод-вывод очень медленный, намного медленнее, чем копирование в память.
(В особых случаях мы можем обойти буфер, чтобы выполнить дисковый ввод-вывод напрямую. Например, есть буфер пользовательского пространства, и вы хотите избежать его двойного копирования.)
С буфером ядра ядро ​​может группировать и планировать записи, чтобы выдавать меньше дисковых операций ввода-вывода, выполнять большие последовательные операции ввода-вывода или даже отменять некоторые операции ввода-вывода, если файл удален. Если вы используете fwrite в libc, есть также буфер пользовательского пространства, чтобы избежать слишком частого захвата ядра.

Это не всегда так, поэтому есть O_DIRECT. Вы можете быть быстрее в специальных приложениях, где копирование данных займет много времени (но в большинстве случаев вы этого не делаете).

12431234123412341234123 28.06.2023 18:01

@ 12431234123412341234123 Я отредактирую ответ, чтобы упомянуть об этом. Спасибо.

XbzOverflow 28.06.2023 18:03
Ответ принят как подходящий

Да; ваш буфер копируется в память ядра перед возвратом write; после этого вы можете перезаписать буфер, не затрагивая какой-либо фоновый ввод-вывод, связанный с исходными данными.

Копирование данных требует времени; Это не бесплатно.

write — это универсальная функция, которая работает с любым файловым дескриптором: вы можете писать в последовательную консоль, межпроцессный канал, сетевой сокет, файл, блочное устройство и т. д.

write должен просмотреть дескриптор файла и вызвать правильную реализацию для устройства.

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

write запросы не совпадают с блоками. Они часто больше или меньше блоков и не начинаются и не заканчиваются на границах блоков; написание на самом деле является деятельностью по редактированию, которая должна сочетать существующие данные с новыми данными. Когда вы записываете несколько байтов в файл, если вы не записываете точный блок, некоторые из байтов, записываемых в хранилище, будут байтами, которые не пришли из вашей программы: например, нулевые байты заполнения в блоке, если нет других данных, или существующие байты в блоке, которые ваш write не трогал.

Измененные буферы должны быть записаны в хранилище, хотя это действие может быть отложено. Отсрочка действия позволяет оптимизировать: например, если эта программа (или любая другая) выполняет больше операций write над одними и теми же блоками, тогда эти изменения могут быть включены до того, как что-либо будет фактически написано. Не говоря уже о том, что файловая система может каким-то образом упорядочивать записи, что делает их быстрее, например, сводя к минимуму движения головки жесткого диска с вращающимся диском.

Ключевая идея заключается в том, что сброс данных из буферов операционной системы в базовый файл обычно происходит не из-за заполнения буферов, а из-за активности на основе таймера, которая отслеживает грязные буферы.

Функцию write можно использовать для задержки выполнения программы до тех пор, пока предыдущие записи в дескриптор файла не будут зафиксированы в хранилище (с некоторыми задокументированными предостережениями). Функция, возможно, также способствует очистке (заставляет это происходить раньше, чем в противном случае).

Другие вопросы по теме