Конвеєри (Unix)

Матеріал з Вікіпедії — вільної енциклопедії.
Перейти до навігації Перейти до пошуку
Конвеєр з трьох програмних процесів, виконувана з текстового терміналу

В Unix-подібних операційних системах конвеєр — це механізм міжпроцесної взаємодії, що використовує передачу повідомлень. Є набором поєднаних між собою процесів, які забезпечують передачу даних так, що стандартний вихідний потік кожного процесу (stdout) безпосередньо з'єднується зі стандартним вхідним потоком (stdin) наступного. Наступний процес починається, поки перший ще виконується і вони виконуються одночасно. Концепцію було запропоновано Дугласом Макілроєм в Bell Labs, колисці Unix, в процесі його розробки та формування філософії інструментизації[1][2]. Назву дано за аналогією зі справжніми конвеєрами. Ключовою функцією цих конвеєрів є «приховування нутрощів» (Ritchie & Thompson, 1974). Це забезпечує системі чистоту за простоту.

У цій статті йдеться про неіменовані канали, де дані, повернені одним процесом, буферизуються системою, поки не будуть зчитані наступним процесом і цей односпрямований канал зникає з закінченням процесів. Це відрізняється від іменованих каналів, де повідомлення передаються між процесами файлами, які залишаються по закінченні процесів. Стандартний shell-синтаксис для неіменованих каналів — це список декількох команд, розділених вертикальними рисками («пайпи» в розмовній лексиці користувачів Unix):

command1 | command2 | command3

Наприклад, щоб отримати список файлів в поточній директорії, зберегти лише стрічку ls виводу, що має рядок "key" (grep) і показати результат на сторінці, що прогортується (less), користувач може написати в командний рядок терміналу наступне:

ls -l | grep key | less

Командаls -l виконується як процес, вивід (stdout) якого передається вводу (stdin) процесу команди grep key, так само з командою less. Кожен процес приймає ввід від попереднього і передає свій вивід наступному стандартними потоками. Кожен | вказує командній оболонці під'єднати стандартний вивід команди зліва до стандартного вводу команди справа через механізм взаємодії між процесами, що називають неіменованим каналом, вбудованим в операційну систему. Канали є односторонніми: дані переходять каналом лише зліва направо.

Приклади[ред. | ред. код]

Нижче наведений приклад конвеєра, що реалізовує перевірку орфографії для веб-сторінки, на яку вказує посилання.

curl "https://en.wikipedia.org/wiki/Pipeline_(Unix)" |
sed 's/[^a-zA-Z ]/ /g' |
tr 'A-Z ' 'a-z\n' |
grep '[a-z]' |
sort -u |
comm -23 - <(sort /usr/share/dict/words) |
less
  1. curl отримує html вміст вебсторінки (для деяких систем використовують wget).
  2. sed замінює всі символи (з вмісту вебсторінки), що не є пробілами чи літерами, на пробіли (зі збереженням переходу на новий рядок)
  3. tr змінює всі великі літери на малі і конвертує пробіли у переходи на новий рядок (кожне «слово» тепер в окремому рядку).
  4. grep приймає лише ті рядки, які містять щонайменше одну малу літеру алфавіту, таким чином виключаючи пусті рядки.
  5. sort сортує список «слів» в алфавітному порядку, а -u видаляє дублікати.
  6. comm шукає стрічки, що є однаковими в обидвох файлах, -23виключає вивід стрічок, що є унікальними в другому файлі та однаковими в обох файлах, виводячи лише стрічки, унікальні для першого файлу. - на місці назви файлу вказує процесу comm використовувати стандартний потік вводу (в даному випадку з конвеєра). sort /usr/share/dict/words сортує вміст файлу words за алфавітом (за вимогами процесу comm). <( ... )виводить результат виконання в тимчасовий файл (використовуючи процесне заміщення), який зчитує comm. Результатом є список слів (стрічок), не знайдених в /usr/share/dict/words
  7. less дозволяє користувачеві розділити результат на сторінки і прогортувати їх.

Конвеєри в інтерфейсах командного рядка[ред. | ред. код]

Всі широко використовувані оболонки Unix мають власний синтаксис для створення конвеєрів. Загалом, команди пишуть послідовно, розділюючи їх вертикальною рискою ASCII | (яку з цієї ж причини часто називають «пайпом»). Командна оболонка розпочинає процеси і організовує необхідні зв'язки між їхніми стандартними потоками, використовуючи деякий обсяг буферу.

Потік виводу помилок[ред. | ред. код]

За замовчуванням стандартні потоки виводу помилок («stderr») процесів в конвеєрі не передаються далі, натомість з'єднуються і направляються в консоль. Однак, багато оболонок мають додатковий синтаксис для зміни цих налаштувань. Наприклад, використання |& замість |в оболонці csh означає, що стандартний потік виводу помилок теж треба злити зі стандартним виводом і передати наступному процесу.

Канальні роздрібнювачі[ред. | ред. код]

У найпростіших та найпоширеніших конвеєрах, оболонка з'єднує серію підпроцесів каналами і виконує зовнішні команди безпосередньо в підпроцесі. Таким чином сама оболонка не виконує прямої обробки даних, що передаються каналом.

Однак, оболонка може напряму обробляти дані, використовуючи так звані канальні роздрібнювачі (оскільки команда while «роздібнює» результати з початкової команди і проходиться по кожному з них). Загальний вигляд такого конструкту виглядає наступним чином:

command | while read -r var1 var2 ...; do
    # process each line, using variables as parsed into var1, var2, etc
    # (note that this may be a subshell: var1, var2 etc will not be available
    # after the while loop terminates; some shells, such as zsh and newer
    # versions of Korn shell, process the commands to the left of the pipe
    # operator in a subshell)
    done

Такі конструкти можуть виконуватись некоректно, якщо тіло циклу містить такі команди, як cat i ssh, які потребують зчитування з stdin:[3] при першій його ітерації. Така програма (назвімо її стоком) зчитуватиме залишковий вивід з command і цикл буде припинено (його результат залежатиме від специфіки стоку). Є декілька шляхів уникнення такого сценарію. По-перше, деякі стоки підтримують можливість вимкнення читання з stdin (напр. ssh -n). Як альтернатива, якщо стік не потребує вхідних даних з stdin, щоб зробити щось корисне, вхідними даними може бути  < /dev/null.

Оскільки всі складові каналу виконуються паралельно, оболонка типово розділяє підпроцес (підоболонку), щоб обробити його вміст, що унеможливлює поширення зміни змінної в зовнішню оболонку. Щоб виправити цю проблему, даний конструкт може прийняти значення з here-документу, що містить командне заміщення, яке очікує завершення конвеєру, перш ніж проходитись по даних. В альтернативу цьому, для розпаралелення можна використовувати іменовані канали або заміщення процесів. GNU bash також має варіант lastpipe для вимкнення розділення останнього компоненту каналу.[4]

Програмне створення конвеєрів[ред. | ред. код]

Конвеєри можуть бути створені програмно. Системний виклик в Unix pipe() просить операційну систему побудувати новий об'єкт неіменованого каналу. Результатом є відкриті два нові файлові дескриптори процесу: один лише для читання, другий лише для запису. Вони схожі зі звичайними неіменованими файловими дескрипторами, але в них немає можливості пошуку.

Щоб уникнути взаємного блокування та використати розпаралелення, Unix процес з одним або більше новим каналом викликає fork(), щоб створити нові процеси.

Іменовані канали теж можна створити командою mkfifo() або mknod[en]().

Виконання[ред. | ред. код]

В більшості Unix-подібних системах всі процеси конвеєра починаються одночасно. Їхні потоки повинні бути відповідно правильно під'єднані і організовані планувальником разом з іншими процесами, що виконуються машиною. Важливим аспектом цього є наступне: відділяти Unix канали від інших каналів, що виконуються, є концептом буферингу. Наприклад, програма-відправник може генерувати 5000 байтів на секунду, а програма-отримувач може отримувати лише 100 байтів на секунду, але при цьому дані не втрачаються, а зберігаються в буфері. Коли отримувач готовий приймати дані, наступний процес конвеєру зчитує їх з буферу. Якщо він заповнений, програма-відправник блокується, поки певний об'єм даних не переміститься з буферу до отримувача. В Лінуксі розмір буферу складає 65546 байтів (64КБ). У відкритих джерелах можна знайти сторонній фільтр bfr для забезпечення буферу більшого об'єму при потребі.

Історія[ред. | ред. код]

Дуглас Макілрой[5] винайшов концепт конвеєру і вперше описав його в посібнику для Version 3 Unix[6]. Макілрой помітив, що дуже часто командні оболонки передавали вихідні дані однієї програми на вхід іншій.

Його ідеї були втілені в 1973 році, коли Кен Томпсон додав системний виклик pipe() і канали до оболонки і декількох утиліт Version 3 Unix. Позначення | — також заслуга Томпсона, це значно полегшило опис синтаксису у Version 4.[7][6]

Диф. також[ред. | ред. код]

Посилання[ред. | ред. код]

  1. Mahoney, Michael S. The Unix Oral History Project: Release.0, The Beginning. Архів оригіналу за 21 квітня 2021. Процитовано 14 січня 2022. McIlroy: It was one of the only places where I very nearly exerted managerial control over Unix, was pushing for those things, yes.
  2. Архівована копія. Архів оригіналу за 3 лютого 2015. Процитовано 14 січня 2022.{{cite web}}: Обслуговування CS1: Сторінки з текстом «archived copy» як значення параметру title (посилання)
  3. Shell Loop Interaction with SSH. 6 березня 2012. Архів оригіналу за 6 березня 2012.
  4. John1024. How can I store the "find" command results as an array in Bash. Stack Overflow.
  5. The Creation of the UNIX Operating System. Bell Labs. Архів оригіналу за 14 вересня 2004.
  6. а б McIlroy, M. D. (1987). A Research Unix reader: annotated excerpts from the Programmer's Manual, 1971–1986 (PDF) (Технічний звіт). CSTR. № 139. Bell Labs. Архів оригіналу (PDF) за 11 листопада 2017. Процитовано 14 січня 2022.
  7. http://www.linfo.org/pipe.html [Архівовано 26 червня 2020 у Wayback Machine.] Pipes: A Brief Introduction by The Linux Information Project (LINFO)