A1. Власний shell
Кожна дія оболонки є системним викликом: запуск програми, перенаправлення,
конвеєр, фонова задача. Написавши її самостійно, ви побачите ці виклики
у власному коді й зрозумієте, як три з них (fork, exec, wait)
складаються в усе, що робить bash.
Після цієї роботи ви зможете:
- запускати програми з C через
forkіexecvpі коректно чекати на їхнє завершення; - пояснити, чому
cdне може бути зовнішньою програмою, а> fileне потребує від програми жодної підтримки; - з’єднувати процеси каналами і знати, чому незакритий дескриптор вішає конвеєр;
- прибирати завершені фонові процеси, щоб не накопичувати зомбі.
Ті самі механізми стоять за CI-скриптами, systemd, Docker і за кожним
| у вашому терміналі. Після цієї роботи вивід strace будь-якої
програми читатиметься як знайомий текст.
Завдання
Section titled “Завдання”Написати на C програму myshell, яка читає команди зі стандартного
входу рядок за рядком і виконує їх так, як це робить sh, у межах,
описаних нижче.
Що має вміти:
- запускати зовнішні програми з аргументами, шукаючи їх у
PATH; - вбудовані команди
cd,pwd,exit; - перенаправлення
<,>,>>; - конвеєри з довільної кількості команд;
- фонові задачі через
&без накопичення зомбі; Ctrl+Cзавершує передній процес, а не саму оболонку;- ігнорувати порожні рядки; невідома команда не має валити оболонку;
- завершуватися на
Ctrl+D(кінець вводу).
Обмеження. Лише C і системні виклики: без system(), popen()
і без виклику готової оболонки. Сенс роботи в тому, щоб зробити ці
виклики самому.
Готово, коли:
./check.sh ./myshellпроходить усі шістнадцять перевірок;strace -f ./myshellпоказуєclone,execve,wait4,pipe2іdup2там, де ви їх очікуєте;- у звіті є відповідь на два запитання: чому
cdмусить бути вбудованою командою і що станеться, якщо забути закрити кінці каналу в батькові.
Чого робити не треба. Лапки, екранування, підстановка змінних і символи-шаблони до цієї роботи не входять: слова розділяються пробілами, і цього досить. Автоперевірка нічого з переліченого не використовує.
Перед початком
Section titled “Перед початком”- Прочитайте в модулі 6 розділи «Створення процесу», «Зомбі та сироти» і «Сигнали», а в модулі 3 розділ «Що відбувається при системному виклику».
- Розпакуйте архів курсу: перевірка лежить
у
labs/a1-shell/check.sh. - Знадобляться
gcc,straceі сторінкиman 2 fork,man 2 pipe,man 2 dup2. Підійде будь-який Linux, зокрема контейнер і WSL2.
-
Запуск однієї команди.
Прочитати рядок, розбити на слова,
fork, у дитиніexecvp, у батькаwaitpid. Порожній рядок ігнорувати,Ctrl+D(кінець вводу) завершує оболонку.Перевірте
strace -fна власній програмі: ви маєте побачитиclone,execveіwait4, як описано в модулі 6. -
Вбудовані команди:
cd,pwd,exit.cdмусить виконуватися в самій оболонці, а не в породженому процесі. Спробуйте зробити навпаки й подивіться, що станеться: робочий каталог зміниться в дитині, яка одразу помре.Через це
cdі немає в/usr/bin. -
Перенаправлення
>,>>,<.Уся робота відбувається між
forkіexec: у дитині відкрити файл, підмінити дескриптор0або1черезdup2, закрити зайве, і аж тодіexecvp.Програма, яку ви запускаєте, про перенаправлення нічого не знає.
-
Конвеєри
a | b | c.На кожен стик потрібен
pipe(). Дитина зліва пише вpipefd[1], дитина справа читає зpipefd[0]. Батько зобов’язаний закрити обидва кінці в себе, інакшеreadправоруч ніколи не побачить кінця потоку і конвеєр зависне.Кількість команд у конвеєрі — довільна.
-
Фонові задачі
&і прибирання зомбі.Команда з
&не чекається: оболонка одразу повертає запрошення. Але завершені діти лишаються зомбі, поки хтось не викличеwait, тому потрібен обробникSIGCHLDабоwaitpid(-1, ..., WNOHANG)у циклі перед кожним запрошенням.Перевірка: запустіть
sleep 0.2 &десять разів і подивітьсяps -eo stat | grep -c Z. -
Сигнали.
Ctrl+Cмає завершувати передній процес, а не саму оболонку. Оболонка ігноруєSIGINTдля себе й повертає типову дію дитині післяfork, доexec.
Автоперевірка
Section titled “Автоперевірка”Перевірка лежить в архіві з файлами курсу, і команди нижче виконуються з розпакованого каталогу.
cd labs/a1-shell./check.sh ./myshellСкрипт робить шістнадцять перевірок у п’яти групах (запуск команд, вбудовані команди, перенаправлення, конвеєри, фонові задачі) і показує, що не збіглося з очікуваним.
Часті помилки
Section titled “Часті помилки”Конвеєр зависає. Батько не закрив свої копії кінців каналу. Правило
таке: кожен pipe() дає два дескриптори, після fork вони існують уже
в трьох процесах, і закрити треба всі, крім тих двох, що справді потрібні.
cd не працює. Ви виконали його в дитині. Робочий каталог є атрибутом
процесу, тож змінювати треба свій власний.
Зомбі накопичуються. Немає прибирання після фонових задач. Один зомбі ніхто й не помітить, а сотня вичерпає таблицю процесів (модуль 6).
Перенаправлення застосувалося до самої оболонки. dup2 виконано
до fork замість після. Наслідок помітний одразу: оболонка перестає
щось друкувати.
execvp повернувся, і програма продовжила виконуватись. Успішний
exec не повертається ніколи, тож його повернення означає помилку.
Дитина зобов’язана завершитися через _exit, інакше у вас буде дві
оболонки замість однієї.
Далі, якщо цікаво
Section titled “Далі, якщо цікаво”Керування задачами (jobs, fg, bg) через групи процесів
і tcsetpgrp; підстановка змінних; && і ||.