Как работают каналы в Linux

25

Я читал о том, как каналы реализованы в ядре Linux, и хотел проверить мое понимание. Если я ошибаюсь, будет выбран ответ с правильным объяснением.

  • В Linux есть VFS с именем pipefs, которая монтируется в ядре (а не в пространстве пользователя)
  • pipefs имеет один суперблок и монтируется в своем корневом ( pipe:)/
  • pipefs не могут быть просмотрены напрямую в отличие от большинства файловых систем
  • Вход в pipefs через pipe(2)системный вызов
  • pipe(2)Системный вызов , используемый оболочками для трубопроводов с |оператором (вручную или с любого другого процесса) создает новый файл в pipefs , который ведет себя очень похоже на обычный файл
  • Файл с левой стороны оператора канала stdoutперенаправляется во временный файл, созданный в pipefs.
  • Файл в правой части оператора pipe имеет свой stdinнабор для файла в pipefs
  • pipefs хранится в памяти, и благодаря некоторой магии ядра не должен быть выгружен

Является ли это объяснение того, как трубы (например ls -la | less) функционируют в значительной степени правильно?

Одна вещь, которую я не понимаю, это то, как что-то вроде bash будет задавать процесс stdinили stdoutвозвращать дескриптор файла pipe(2). Я пока не смог ничего найти по этому поводу.

Брэндон Вамбольдт
источник
Обратите внимание, что вы говорите о двух разных слоях вещей с одинаковыми именами. pipe()Вызов ядра наряду с техникой , которая поддерживает его ( pipefsи т.д.) значительно ниже уровень , чем |оператор , предоставляемый в оболочке. Последнее обычно реализуется с использованием первого, но это не обязательно.
Грег Хьюгилл
Да, я имею в виду операции более низкого уровня, предполагая, что |оператор просто вызывает pipe(2)как процесс, как это делает bash.
Брэндон Вамбольдт
Смотрите также В чем разница между «Перенаправление» и «Труба»?
Сергей Колодяжный

Ответы:

19

Ваш анализ до сих пор в целом правильно. Способ, которым оболочка может установить stdin процесса в дескриптор канала, может быть (псевдокод):

pipe(p) // create a new pipe with two handles p[0] and p[1]
fork() // spawn a child process
    close(p[0]) // close the write end of the pipe in the child
    dup2(p[1], 0) // duplicate the pipe descriptor on top of fd 0 (stdin)
    close(p[1]) // close the other pipe descriptor
    exec() // run a new process with the new descriptors in place
Грег Хьюгилл
источник
Благодарность! Просто любопытно, зачем dup2нужен вызов, а вы не можете просто назначить дескриптор канала для stdin?
Брэндон Вамбольдт
3
Вызывающая сторона не может выбрать, какое числовое значение дескриптора файла при его создании pipe(). dup2()Вызова позволяет абоненту скопировать дескриптор файла определенного числового значения (необходимо потому , что 0, 1, 2 STDIN, стандартный вывод, STDERR). Это ядро ​​эквивалентно «назначению непосредственно в stdin». Обратите внимание, что глобальная переменная библиотеки времени выполнения C stdin- это a FILE *, которая не связана с ядром (хотя она инициализирована для подключения к дескриптору 0).
Грег Хьюгилл
Отличный ответ! Я немного потерян в деталях. Просто интересно, почему вы закрываете (p [1]) перед запуском exec ()? Когда dup2 вернется, p [1] не укажет на fd 0? Затем close (p [1]) закрывает дескриптор файла 0. Тогда как мы можем читать со стандартного ввода дочернего процесса?
user1559897
@ user1559897: dup2звонок не меняется p[1]. Вместо этого он делает две ручки p[1]и 0указывает на один и тот же объект ядра (канал). Поскольку дочерний процесс не нуждается в двух дескрипторах stdin (и в p[1]любом случае не знает, что это за номерный дескриптор ), p[1]он закрывается раньше exec.
Грег Хьюгилл,
@GregHewgill Gotchu. Спасибо!
user1559897