Вопрос или проблема
Итак, Unix использует единый подход к вводу-выведению, который позволяет программам соединяться через каналы. Я также знаю, что для этого используются дескрипторы файлов. Но я слышал, как кто-то говорил, что каналы позволяют соединить stdout
одного процесса с stdin
другого процесса с помощью небольшого буфера в памяти и без записи всего промежуточного потока данных на диск, и это меня запутало.
Допустим, у нас есть что-то вроде
cat foo.log | awk 'print $3' | sort | uniq
Предположим, что foo.log
— это огромный файл, намного больше, чем уместится в ОЗУ.
Мой вопрос: когда мы делаем sort | uniq
, я изначально думал, что sort
записывает весь отсортированный результат в файл и передаёт дескриптор этого файла в uniq
, но потом, услышав этот комментарий (а также факт, что такой подход означал бы наличие временного файла на жестком диске), кажется, что результат передается через какой-то промежуточный буфер в памяти? Это означает, что |
каким-то образом вызывает другую утилиту ОС для чтения/передачи данных между процессами?
Как именно это работает в Linux?
Каналы — это специальный вид дескрипторов файлов, которые создаются попарно: один для стороны чтения, другой для стороны записи. Операционная система делает всё, что записано на сторону записи, доступным для чтения на стороне чтения.
В конвейере, например, sort | uniq
, оболочка создает канал и два дескриптора файлов, и устраивает запуск дочерних процессов так, чтобы stdout
левой программы (sort
) был подключен к stdin
правой программы (uniq
).
Самим программам на самом деле безразлично, какого рода дескрипторы файлов у них есть, они просто читают из дескриптора stdin
и записывают в дескриптор stdout
одинаково, независимо от того, указывают ли они на обычный дескриптор или на дескриптор канала. Или один, подключенный к терминалу, который вы получаете по умолчанию для всего, что производит вывод в интерактивной сессии. Дескрипторы файлов идентифицируются по номерам, и stdin
(0), stdout
(1) и stderr
(2) — это просто дескрипторы файлов с известными номерами и установленными нормами использования.
Под капотом каналы, конечно, работают через буферы памяти, но это внутреннее дело операционной системы, не то, что нужно реализовывать в программном обеспечении пользовательского уровня. Хотя sort
в частности должен читать весь свой ввод, прежде чем сможет произвести какой-либо вывод, поэтому ему может понадобиться потенциально большой внутренний буфер, либо в памяти, либо с использованием временных файлов. Но многие другие утилиты могут выполнять свою работу, обрабатывая поток файла, часто рассматривая только одну строку за раз, как ваш awk 'print $3'
там.
Ответ или решение
Вопрос о том, как результаты передаются между процессами через конвейеры (pipes) в Unix и Linux, касается основополагающих принципов работы операционных систем. Давайте подробно разберем, как это происходит, и что именно подразумевается под описанным процессом.
Основы работы конвейеров
Конвейеры в Unix представляют собой механизм, позволяющий соединить несколько процессов таким образом, что вывод одного процесса (stdout) становится входом (stdin) для следующего. Это достигается через использование файловых дескрипторов, которые представляют собой целочисленные идентификаторы открытых файлов в системе.
Когда вы вводите команду, такую как:
cat foo.log | awk 'print $3' | sort | uniq
Операционная система создает пару конвейеров — один для чтения и один для записи. В данном случае, при запуске команды, оболочка (shell) создает два дочерних процесса: awk
и sort
. Важно отметить, что cat
читает файл foo.log
и передает его вывод awk
, а awk
затем передает свои результаты sort
в том порядке, как они были обработаны.
Внутренняя работа
Каждый процесс получает свои файловые дескрипторы автоматически. Например, awk
будет использовать дескриптор для чтения из конвейера, созданного для cat
, а sort
будет получать данные через другой дескриптор, связанный с awk
. Таким образом, данные передаются между процессами не через временные файлы, а непосредственно через память.
В отличие от временных файлов
Как вы правильно заметили, если бы sort
записывал результаты в временный файл на диске, это потребовало бы дополнительных операций ввода-вывода и создало бы временные файлы на диске, что нежелательно, особенно при работе с большими объемами данных. Вместо этого, благодаря использованию памяти, данные передаются более эффективно, избегая необходимости в промежуточных записях на диск.
Использование буферов
Конкретные реализации команд, такие как sort
, могут использовать свои внутренние механизмы для обработки данных. Например, sort
требует, чтобы весь входной поток был доступен перед тем, как он начнет выводить отсортированные данные, поэтому ему требуется значительное количество памяти или временные файлы для обработки больших объемов данных. Однако команды, такие как awk
, могут обрабатывать данные по мере их поступления, так как они могут работать с одним элементом (строкой) за раз.
Конвейеры в контексте системы
Для реализации всей этой логики операционная система использует механизмы управления процессами и памятью, которые обеспечивают изолированную среду выполнения для каждого процесса, где они могут свободно обмениваться данными через установленные конвейеры.
Заключение
Использование конвейеров в Unix подобно проложению надежного и быстрого пути передачи данных между процессами. В то время как управление памятью происходит исключительно в рамках кесарственного процесса, вся эта сложная работа происходит в тени, позволяя пользователям и разработчикам сосредоточиться на более высоком уровне абстракции, не волнуясь о механизмах, стоящих за эффективным обменом данными среди программ.
Таким образом, основываясь на принципах работы с конвейерами в Unix и Linux, можно утверждать, что эта функциональность обеспечивает эффективное и быстрое взаимодействие между процессами без необходимости использования временных файлов, а вся работа осуществляется через внутренние механизмы операционной системы, что делает её незаменимой для обработки потоковых данных.