Страницы

Поиск по вопросам

Показаны сообщения с ярлыком syscall. Показать все сообщения
Показаны сообщения с ярлыком syscall. Показать все сообщения

среда, 22 января 2020 г.

Буферизация stdout, работа fflush

#cpp #linux #c #syscall #буферизация


Что делает fflush? Многие пишут,что эта функция дает команду ОС сбросить содержимое
буфера на диск. Но разве этим занимается ОС? Насколько я понял при работе с файлами
буфер создает сама программа, например, функция fopen. В таблице syscall-ов linux я
не нашел чего-то похожего на flush. При этом в пример приводят вот такой код:

#include 
int main() {
  printf("Hello");
  while(true);
  return 0;
}


При запуске такой программы, hello напечатано не будет, если не добавить после printf
вызов fflush(stdout). Однако вот такой код вполне нормально работает:

#include 
#include 
int main() {
  char b[] = "Hello";
  write(1,(void*)b,sizeof(b)-1);
  while(true);
  return 0;
}


Тут мы не используем высокоуровневые методы работы с stdout, а работаем на уровне
syscall-ов. Я правильно понимаю что буферизация работы с файлами реализована средствами
самой программы, а не ОС? И при вызове write, данные сразу будут записаны на диск?
    


Ответы

Ответ 1



Что касается fflush, в сущности всё так и есть. Семейство функций для работы с потока в Си (fopen/fread/fprintf итп) fflush сбрасывает только оные буферы. Они же могут быть сброшены и раньше по исчерпанию (размер стандартного буфера BUFSIZ [8к для glibc/linux/x86]) или в зависимости от настроек буферизации (см. man 3 setbuf), в частности по-умолчанию для обычных файлов используется блочная буферизация (сбрасывается по 4к для glibc/linux), для терминалов (втч для stdout — строчная, сбрасывается по символу '\n'), для stderr буферизации нет. Для Linux собственно во время сброса буфера происходит системный вызов write(), а что происходит с данными после сброса этого стандарт Си не гарантирует. Из man 3 fflush: ЗАМЕЧАНИЯ Заметим, что fflush() сбрасывает буферы только пользовательского пространства, заданные библиотекой Си. Чтобы гарантировать, что данные действительно физически сохранены на диске, буферы ядра также должны быть сохранены, например, с помощью вызова sync(2) или fsync(2). Что происходит с данными после write() зависит от того с каким файлом связан дескриптор и как он был открыт. Практически в любом случае ОС практически всегда проводит дополнительную буферизацию/кеширование. Очевидное исключение, например, запись в /dev/null; в принципе возможно существование устройств не буферизирующих ничего, а работающих напрямую с буфером из юзерспейса, но на вскидку я таких не назову Не вдаваясь в детали в Linux'е для обычного файла на обычной дисковой ФС при не-синхронном вводе-выводе происходит следующее: ядро вносит изменения в кеше страниц и помечает их как грязные, затем возвращает управление процессу. Далее специальный демон (раньше был один pdflush (с несколькими потоками), ныне для каждого диска/блочного устройства/некоторых ФС свой) следит за грязными страницами и отдаёт команды драйверам ФС сохранить данные на диск при одном из условий: Когда не хватает свободной памяти Когда грязных страниц слишком много (см. /proc/sys/vm/dirty_{ratio,background_ratio}), По прошествии определённого времени (указанного в /proc/sys/vm/dirty_{writeback_centisecs,expire_seconds}) По вызову [f]sync() Когда ещё какая-то умная эвристика сработала. В этот момент ФС может преобразовать данные (например сжать или зашифровать). Далее создаётся запрос на вывод на блочное устройство. Когда драйвер жёсткого диска обработает этот запрос т.е. отправит данные непосредственно на устройство и получил подтверждение, что всё в порядке, страница снова отмечается, как чистая. При синхронном вводе-выводе (когда файл открыт с O_SYNC или ФС смонтирована с -o sync) всё точно также, но управление процессу из write() не будет возвращено пока страница не отмечена как чистая.

вторник, 26 ноября 2019 г.

Зачем в POSIX-системах вызов fork() создаёт полную копию?


Изучая операционные системы задал себе вопрос: зачем в POSIX-подобных системах пр
создании процесса избран путь полного копирования родительского процесса в дочерни
и только после этого с помощью сис.вызова execve новый процесс перезаписывается полезны
содержимым? В чем смысл передавать дочернему весь образ памяти, строки конфигурации и т.д., когда, к примеру, в Windows создаётся, ну очень грубо говоря, "пустое ничего" и уже ему задаются аргументы, в поле памяти записывается выполняющаяся программа и ещё 7-8 стандартных параметров?

Вопрос иерархий тоже не затрагиваю, тут с этим все однозначно и польза от подход
ясна. У меня нет цели захейтить один из путей или развести холивар, мне хочется разобраться
какие плюсы и минусы у обоих способов. Ведь не просто так 'fork'у не нашли замены в течении нескольких десятилетий, так ведь? Да, я знаю, что большинство современных POSIX систем при вызове fork() используют COW принцип, но раньше ведь было не так.

Спасибо тем, кто постарается простым (а можно и не простым) языком описать суть на столько разных подходов. Принимаются любые ссылки с описанием.
    


Ответы

Ответ 1



Благодаря наводке кемментатора был найден англоязычный ресурс с идентичным вопросом В комментариях найден ответ, который по моему мнению, более всего соответствует действительности. Итак: Существует две философии создания процесса: fork с наследованием содержимого родител и создание пустого процесса с заданием аргументов (create). Очевидно, Unix используе fork. (Например, OSE и VMS используют метод create.) Unix имеет много наследуемых характеристик процесса. Через наследование эти характеристики могут быть добавлены новому процессу без измененя существующих программ. Используя модель create-with-arguments, добавление новых характеристик будет означать добавление новых аргументов в вызов create. Модель Unix проще. Имея вышеописанное существует возможность иметь также очень полезную модель fork-without-exec где процесс может разбиться на несколько частей. Это было особо важно, когда не был никакой формы асинхронного ввода-вывода, и полезно, когда вы используете несколько процессоров в системе. По сути, это позволяет контейнеризировать несколько «программ» в одну программу, поэтому нет места для рассогласования или несоответствий версий и т.д. Модель fork / exec также дает возможность определенному дочернему процессу наследоват среду, настроенную между разветвлённым процессом и exec. Особенно, такие вещи, как унаследованные файловые дескрипторы. Модель создания (create model) не дает возможности наследовать все, что не было предусмотрено создателями вызова create. Некоторые системы также могут поддерживать динамическую компиляцию собственного кода где процесс фактически создает собственную программу с собственным кодом. Другими словами он хочет, чтобы новая программа записывала себя «на лету», без необходимости проходить цикл исходного кода / компилятора / компоновщика и занимать дисковое пространство. Модель fork поддерживает это, модель создания обычно нет.

Ответ 2



Я думаю, что суть fork в минимизации усилий программиста (обратите внимание, программиста а не железа) при создании нового процесса, окружение которого уже подготовлено всей историей жизни родителя (и остальных предков). Вам остается только добавить что-то свое (впрочем, ничто не мешает и отбросить это груз). Причем, делаете это безопасно для родителя, не оказывая воздействия на других его потомков. В простейшем случае вы можете даже не менять исполняемую программу, но в большинств случаев все же почти сразу делаете это (вызываете exec). Но и в этом случае fork очевидно полезен, позволяя изолировать какую-то подготовку к такой перемене уже в новом процессе. Да что тут много говорить, лучше почитайте The Evolution of the Unix Time-sharing System. В общем, fork это просто очень красивая концепция.

вторник, 12 марта 2019 г.

Буферизация stdout, работа fflush

Что делает fflush? Многие пишут,что эта функция дает команду ОС сбросить содержимое буфера на диск. Но разве этим занимается ОС? Насколько я понял при работе с файлами буфер создает сама программа, например, функция fopen. В таблице syscall-ов linux я не нашел чего-то похожего на flush. При этом в пример приводят вот такой код:
#include int main() { printf("Hello"); while(true); return 0; }
При запуске такой программы, hello напечатано не будет, если не добавить после printf вызов fflush(stdout). Однако вот такой код вполне нормально работает:
#include #include int main() { char b[] = "Hello"; write(1,(void*)b,sizeof(b)-1); while(true); return 0; }
Тут мы не используем высокоуровневые методы работы с stdout, а работаем на уровне syscall-ов. Я правильно понимаю что буферизация работы с файлами реализована средствами самой программы, а не ОС? И при вызове write, данные сразу будут записаны на диск?


Ответ

Что касается fflush, в сущности всё так и есть. Семейство функций для работы с потока в Си (fopen/fread/fprintf итп) fflush сбрасывает только оные буферы. Они же могут быть сброшены и раньше по исчерпанию (размер стандартного буфера BUFSIZ [8к для glibc/linux/x86]) или в зависимости от настроек буферизации (см. man 3 setbuf), в частности по-умолчанию для обычных файлов используется блочная буферизация (сбрасывается по 4к для glibc/linux), для терминалов (втч для stdout — строчная, сбрасывается по символу '
'), для stderr буферизации нет.
Для Linux собственно во время сброса буфера происходит системный вызов write(), а что происходит с данными после сброса этого стандарт Си не гарантирует.
Из man 3 fflush
ЗАМЕЧАНИЯ Заметим, что fflush() сбрасывает буферы только пользовательского пространства, заданные библиотекой Си. Чтобы гарантировать, что данные действительно физически сохранены на диске, буферы ядра также должны быть сохранены, например, с помощью вызова sync(2) или fsync(2).
Что происходит с данными после write() зависит от того с каким файлом связан дескриптор и как он был открыт. Практически в любом случае ОС практически всегда проводит дополнительную буферизацию/кеширование. Очевидное исключение, например, запись в /dev/null; в принципе возможно существование устройств не буферизирующих ничего, а работающих напрямую с буфером из юзерспейса, но на вскидку я таких не назову
Не вдаваясь в детали в Linux'е для обычного файла на обычной дисковой ФС при не-синхронном вводе-выводе происходит следующее: ядро вносит изменения в кеше страниц и помечает их как грязные, затем возвращает управление процессу. Далее специальный демон (раньше был один pdflush (с несколькими потоками), ныне для каждого диска/блочного устройства/некоторых ФС свой) следит за грязными страницами и отдаёт команды драйверам ФС сохранить данные на диск при одном из условий:
Когда не хватает свободной памяти Когда грязных страниц слишком много (см. /proc/sys/vm/dirty_{ratio,background_ratio}), По прошествии определённого времени (указанного в /proc/sys/vm/dirty_{writeback_centisecs,expire_seconds}) По вызову [f]sync() Когда ещё какая-то умная эвристика сработала.
В этот момент ФС может преобразовать данные (например сжать или зашифровать). Далее создаётся запрос на вывод на блочное устройство. Когда драйвер жёсткого диска обработает этот запрос т.е. отправит данные непосредственно на устройство и получил подтверждение, что всё в порядке, страница снова отмечается, как чистая.
При синхронном вводе-выводе (когда файл открыт с O_SYNC или ФС смонтирована с -o sync) всё точно также, но управление процессу из write() не будет возвращено пока страница не отмечена как чистая.