Перейти до вмісту

A1. Власний shell

базовийспирається на модуль 3, модуль 6

Оболонка — найкоротший шлях побачити всі механізми модуля 6 одразу. Кожна її дія є системним викликом, і жодного з них не заміниш бібліотечною функцією.

Наприкінці ви матимете програму, яка запускає команди, будує конвеєри й перенаправляє потоки. А заразом зрозумієте, чому cd не може бути зовнішньою програмою й чому > file не потребує від програми жодної підтримки.

Виконуваний файл myshell, який читає команди зі стандартного входу й виконує їх.

Чого робити не треба. Лапки, екранування, підстановка змінних і символи-шаблони до цієї роботи не входять: слова розділяються пробілами, і цього досить. Автоперевірка нічого з переліченого не використовує.

  1. Запуск однієї команди.

    Прочитати рядок, розбити на слова, fork, у дитині execvp, у батька waitpid. Порожній рядок ігнорувати, Ctrl+D (кінець вводу) завершує оболонку.

    Перевірте strace -f на власній програмі: ви маєте побачити clone, execve і wait4 — рівно те, що описано в модулі 6.

  2. Вбудовані команди: cd, pwd, exit.

    cd мусить виконуватися в самій оболонці, а не в породженому процесі. Спробуйте зробити навпаки й подивіться, що станеться: робочий каталог зміниться в дитині, яка одразу помре.

    Це і є відповідь на питання, чому cd немає в /usr/bin.

  3. Перенаправлення >, >>, <.

    Уся робота відбувається між fork і exec: у дитині відкрити файл, підмінити дескриптор 0 або 1 через dup2, закрити зайве — і аж тоді execvp.

    Програма, яку ви запускаєте, про перенаправлення нічого не знає.

  4. Конвеєри a | b | c.

    На кожен стик — pipe(). Дитина зліва пише в pipefd[1], дитина справа читає з pipefd[0]. Батько зобов’язаний закрити обидва кінці в себе, інакше read праворуч ніколи не побачить кінця потоку і конвеєр зависне.

    Кількість команд у конвеєрі — довільна.

  5. Фонові задачі & і прибирання зомбі.

    Команда з & не чекається: оболонка одразу повертає запрошення. Але завершені діти лишаються зомбі, поки хтось не викличе wait — тому потрібен обробник SIGCHLD або waitpid(-1, ..., WNOHANG) у циклі перед кожним запрошенням.

    Перевірка: запустіть sleep 0.2 & десять разів і подивіться ps -eo stat | grep -c Z.

  6. Сигнали.

    Ctrl+C має завершувати передній процес, а не саму оболонку. Оболонка ігнорує SIGINT для себе й повертає типову дію дитині після fork, до exec.

Перевірка лежить в архіві з файлами курсу, і команди нижче виконуються з розпакованого каталогу.

Terminal window
cd labs/a1-shell
./check.sh ./myshell

Скрипт проганяє дванадцять сценаріїв — від одиночної команди до тримісного конвеєра з перенаправленням — і показує, що саме не збіглося з очікуваним.

Конвеєр зависає. Батько не закрив свої копії кінців каналу. Правило таке: кожен pipe() дає два дескриптори, після fork вони існують уже в трьох процесах, і закрити треба всі, крім тих двох, що справді потрібні.

cd не працює. Ви виконали його в дитині. Робочий каталог є атрибутом процесу, тож змінювати треба свій власний.

Зомбі накопичуються. Немає прибирання після фонових задач. Один зомбі ніхто й не помітить, а сотня вичерпає таблицю процесів (модуль 6).

Перенаправлення застосувалося до самої оболонки. dup2 виконано до fork замість після. Наслідок помітний одразу: оболонка перестає щось друкувати.

execvp повернувся, і програма продовжила виконуватись. Успішний exec не повертається ніколи, тож його повернення означає помилку. Дитина зобов’язана завершитися через _exit, інакше у вас буде дві оболонки замість однієї.

Керування задачами (jobs, fg, bg) через групи процесів і tcsetpgrp; підстановка змінних; && і ||.