Мы часто используем конвейерные команды для импорта вывода одной инструкции во ввод другой инструкции, то есть прикладом к устам, этот принцип известен даже учащимся начальной школы. Но если вы хотите подробно спросить, как вывод одной инструкции импортируется во ввод другой инструкции и какую роль играет конвейер, по оценкам, менее 1% людей могут ответить на этот вопрос. Теперь давайте подробно проанализируем принцип реализации инструкции конвейера.Что делает оболочка для следующей инструкции?
$ cmd1 | cmd2
Сначала я использую следующую картинку, чтобы описать конечную форму, а затем шаг за шагом разложить процесс формирования конечной формы
На рисунке выше мы видим отношения родитель-потомок таблицы дескрипторов процессов, каналов и процессов.
вилка и выполнение
Каждый раз, когда оболочка выполняет инструкцию, ей необходимо разветвить дочерний процесс для выполнения, а затем заменить образ дочернего процесса целевой инструкцией, которая снова будет использовать функцию exec. Например, следующая простая команда
$ cmd
Функция Exec не изменяет идентификатор процесса текущего процесса, а также не изменяет отношения родительской дочерней связи между процессами. Вы можете подумать о процессе в качестве сферы с оболочкой. После exec внешняя оболочка не изменится, а содержимое сферы полностью заменяется. Дескрипторы входных и выходных файлов находятся на оболочке по умолчанию, что означает, что вход и вывод команд CMD наследует вход и вывод процесса оболочки.
$ cmd1 | cmd2
Когда инструкция содержит символ вертикальной черты, это означает, что две инструкции должны выполняться параллельно.В это время оболочке необходимо дважды разветвиться, чтобы сгенерировать два подпроцесса, а затем заменить exec на целевую инструкцию соответственно.
Мы заметили, что на рисунке также есть канал, который отвечает за связь между родительским и дочерним процессами.
pipe
Пайпы используются для связи между родительским и дочерним процессами.Пайпы создаются до форка, а пайп станет связующим звеном между родительским и дочерним процессами после форка. Функция pipe возвращает два дескриптора (pipe_in, pipe_out), один для чтения и один для записи.
dup2
Далее нам нужно настроить верхушку дескриптора на рисунке, указать дескриптор stdout процесса cmd1 на канал для записи, а дескриптор stdin процесса cmd2 указать на канал для чтения, что требует волшебного Функция dup2(fd1, fd2), ее функция состоит в том, чтобы связать дескриптор fd1 с объектом ядра, на который указывает fd2.Счетчик ссылок объекта ядра, на который указывает fd1, уменьшается на единицу, и если он уменьшается до нуля, он будет уничтожен. Обратите внимание, что обычно мы вызываем метод close, существенно уменьшая счетчик ссылок, и один и тот же объект ядра может совместно использоваться несколькими процессами. Он официально закрывается, когда счетчик ссылок падает до нуля.
Далее мы применим правила функции dup2 и вызовем метод dup2 для двух процессов соответственно, чтобы получить
Затем закройте ненужные дескрипторы и получите финальную картинку ниже, идеально!
Если это два символа канала и три команды выглядят следующим образом, будут сгенерированы два канала.
$ cmd1 | cmd2 | cmd3
Что произойдет, если процесс на любом конце внезапно зависнет?
Предполагая, что cmd1 зависает первым, конвейер пассивно закрывается, а cmd2 встречает EOF при чтении содержимого конвейера, а затем завершается нормально. Предполагая, что cmd2 зависает первым, канал чтения пассивно закрывается, а cmd1 продолжает запись в канал.В это время процесс получает сигнал SIGPIPE, и по умолчанию процесс завершается напрямую.
В следующей статье мы будем использовать классный код для реализации всего описанного выше процесса.Нам нужно не только знать принцип, но и знать больше деталей посредством практических экспериментов.
Прочтите более подробные технические статьи, отсканируйте приведенный выше QR-код и подпишитесь на общедоступную учетную запись WeChat «Code Cave».