From e373514f5f893295fea9c4698a33ccfc47608e5f Mon Sep 17 00:00:00 2001 From: e1turin Date: Mon, 28 Sep 2026 01:42:42 +0300 Subject: [PATCH 01/21] aigen-iter 1: simplified task guide --- lab/vtsh/GUIDE.md | 484 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 484 insertions(+) create mode 100644 lab/vtsh/GUIDE.md diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md new file mode 100644 index 0000000..8e17052 --- /dev/null +++ b/lab/vtsh/GUIDE.md @@ -0,0 +1,484 @@ +# Пошаговое руководство: простой Shell на C + +## 1. Цель работы + +В этой лабораторной работе необходимо разработать **простую командную оболочку** +на языке C. Оболочка должна показать, как пользовательская программа запускает +другие программы с помощью системных вызовов ОС, передаёт им аргументы и +окружение, ожидает их завершения и управляет фоновыми процессами. + +Задача не состоит в повторении Bash. Не требуется поддерживать весь синтаксис +POSIX Shell. Главное — понять: + +- зачем операционной системе нужна командная оболочка; +- как создаются и завершаются процессы; +- чем отличаются родительский и дочерний процессы; +- как передаются аргументы и переменные окружения; +- как оболочка получает код завершения программы; +- как реализуются последовательное, условное и фоновое выполнение; +- как устроены сигналы, группы процессов и каналы (`pipe`). + +В руководстве намеренно нет готовой реализации. Названия системных вызовов, +этапы алгоритмов, примеры ввода и вопросы для проверки должны помочь спроектировать +решение самостоятельно. + +## 2. Состав задания + +### 2.1. Обязательная часть + +Оболочка должна: + +1. быть написана на C; +2. показывать приглашение, читать строку и разбирать её на команду и аргументы; +3. запускать внешнюю программу через механизм создания процессов, назначенный + вариантом: `fork`, `vfork` или `clone`; +4. передавать новой программе аргументы и текущее окружение оболочки; +5. находить программу по абсолютному/относительному пути и через `PATH`; +6. корректно ожидать процессы переднего плана и получать их статус; +7. поддерживать операторы `;`, `&&` и `||`; +8. запускать простую команду в фоне с помощью `&`; +9. не оставлять завершившиеся дочерние процессы в состоянии zombie; +10. обрабатывать пустой ввод, ошибочную команду и завершение стандартного ввода. + +Все операторы реализуются каждым студентом. Вариант определяет только способ +создания дочернего процесса. + +### 2.2. Расширения + +Набор обязательных расширений определяет преподаватель. Возможные расширения: + +- измерение времени работы программы; +- ограничение ресурсов и защита от fork bomb; +- более полная обработка `Ctrl-C` и других сигналов; +- встроенная команда `jobs`; +- конвейеры через `|`; +- перенаправление стандартных потоков; +- кавычки, экранирование и подстановка переменных окружения; +- встроенные команды `cd`, `export`, `unset` и `exit`. + +Независимо от выбранных расширений студент должен подготовить собственные +автоматические тесты на Python. + +## 3. Минимальный синтаксис + +Для базовой версии достаточно следующей модели: + +- команда — имя программы и ноль или больше аргументов; +- слова разделяются одним или несколькими пробельными символами; +- операторы `;`, `&&`, `||` и `&` отделяются пробелами; +- кавычки, обратный слеш, glob-шаблоны (`*`, `?`) и подстановка `$NAME` не + обязательны; +- `&` в обязательной части применяется только к одной простой команде; +- некорректная или неполная конструкция должна выдавать понятную ошибку, а не + приводить к аварийному завершению оболочки. + +Примеры допустимого ввода: + +```text +/bin/echo hello world +printf one ; printf two +false && echo not-run +false || echo fallback +sleep 2 & +``` + +Можно реализовать разбор операторов без окружающих пробелов как расширение. +Ограничения синтаксиса должны быть описаны в отчёте и проверены тестами. + +### Семантика операторов + +| Оператор | Поведение | +|---|---| +| `cmd1 ; cmd2` | выполнить `cmd1`, затем выполнить `cmd2` независимо от статуса `cmd1` | +| `cmd1 && cmd2` | выполнить `cmd2`, только если `cmd1` завершилась успешно | +| `cmd1 \|\| cmd2` | выполнить `cmd2`, только если `cmd1` завершилась с ошибкой | +| `cmd &` | запустить `cmd` и сразу вернуть управление пользователю | + +Успехом считается код завершения `0`. Любой ненулевой код или завершение по +сигналу считается ошибкой. Операторы `&&` и `||` имеют одинаковый приоритет и +вычисляются слева направо; `;` разделяет такие логические цепочки. + +## 4. Рекомендуемая архитектура + +Разделите программу хотя бы на четыре логические части: + +1. **REPL** — приглашение, чтение строк и завершение оболочки; +2. **parser** — преобразование строки в команды, аргументы и операторы; +3. **executor** — запуск команд и применение правил `;`, `&&`, `||`, `&`; +4. **process manager** — ожидание, хранение фоновых задач и сбор завершившихся + процессов. + +Необязательно помещать каждую часть в отдельный файл, но их обязанности не +следует смешивать. Например, parser не должен создавать процессы, а обработчик +сигнала не должен разбирать новую командную строку. + +Полезно заранее определить две внутренние сущности: + +- **простая команда**: массив аргументов, где первый элемент — имя программы; +- **план выполнения**: список команд и операторов между ними. + +Для фоновых задач дополнительно понадобятся идентификатор задачи, PID или PGID, +исходный текст команды и текущее состояние. + +## 5. Пошаговая разработка + +### Шаг 1. Реализуйте цикл оболочки + +На каждой итерации: + +1. при интерактивном запуске покажите приглашение; +2. прочитайте одну строку целиком; +3. удалите завершающий перевод строки; +4. пропустите пустую строку; +5. передайте строку parser; +6. выполните полученный план; +7. перед следующим приглашением проверьте завершившиеся фоновые процессы. + +Приглашение лучше выводить только тогда, когда стандартный ввод связан с +терминалом. Это упрощает автоматическое тестирование через перенаправление +ввода. + +`Ctrl-D` не посылает сигнал. Терминал сообщает программе конец файла (EOF), если +во входном буфере нет данных. В этом случае оболочка должна аккуратно завершить +цикл, освободить память и решить, что делать с ещё работающими фоновыми задачами. +Выбранная политика должна быть описана в отчёте. + +### Шаг 2. Разберите простую команду + +Сначала поддержите только имя программы и аргументы. Проверьте случаи: + +- пустая строка; +- пробелы в начале и конце; +- несколько пробелов между словами; +- команда без аргументов; +- большое число аргументов; +- слишком длинная строка. + +Массив аргументов для запускаемой программы должен завершаться специальным +нулевым элементом. Решите, кто владеет выделенной памятью и в какой момент она +освобождается. + +Не начинайте с кавычек и подстановок. Сначала добейтесь надёжного запуска простой +команды. + +### Шаг 3. Запустите одну программу + +Общий сценарий запуска выглядит так: + +1. родитель создаёт дочерний процесс выбранным по варианту способом; +2. дочерний процесс подготавливает своё состояние и заменяет свой образ внешней + программой через семейство `exec`; +3. при ошибке `exec` дочерний процесс печатает диагностическое сообщение и + немедленно завершается через `_exit`; +4. родитель сохраняет PID и ожидает дочерний процесс, если команда не фоновая; +5. родитель преобразует результат ожидания в код выполнения команды. + +Не вызывайте внешнюю программу через `system`, `popen` или запуск `/bin/sh -c`: +эти функции скрывают именно те действия, которые требуется изучить. + +Рекомендуемые соглашения об ошибках: + +- программа не найдена — статус `127`; +- программа найдена, но не может быть запущена — статус `126`; +- ошибка создания процесса остаётся ошибкой текущей команды, но не завершает + сам shell. + +#### Вариант `fork` + +После `fork` родитель и ребёнок продолжают выполнение с одной точки, но имеют +разные адресные пространства. Оба процесса должны сразу выбрать свою ветвь по +возвращённому значению. Нужно обработать и отрицательный результат. + +#### Вариант `vfork` + +До успешного `exec` или `_exit` ребёнок временно использует адресное пространство +родителя, а родитель приостановлен. В дочерней ветви нельзя возвращаться из +текущей функции, изменять данные родителя, освобождать память и выполнять +небезопасную подготовительную работу. Все данные для запуска подготовьте до +`vfork`. + +На защите необходимо объяснить, почему обычный путь обработки после `fork` +нельзя механически перенести в ветвь `vfork`. + +#### Вариант `clone` + +Необходимо самостоятельно выбрать и обосновать флаги, предоставить стек для +дочернего контекста и обеспечить получение родителем уведомления о завершении. +Для поведения, похожего на отдельный процесс, не следует без необходимости +разделять память через `CLONE_VM`. Интерфейс и допустимые флаги зависят от Linux, +поэтому сверяйтесь с документацией именно той системы, где будет проверяться +работа. + +### Шаг 4. Передайте окружение и найдите программу + +Новая программа должна получить переменные окружения shell. Проверьте это с +помощью утилит `env` или `printenv`. + +Если имя содержит `/`, рассматривайте его как явный путь. В остальных случаях +последовательно проверьте каталоги из `PATH`. Следует различать ситуации: + +- подходящий файл не найден; +- файл существует, но не является исполняемым; +- запуск не удался по другой причине. + +Передача окружения не означает обязательную поддержку `$NAME` в командной +строке. Подстановка переменных и встроенная команда `export` являются отдельными +расширениями. + +### Шаг 5. Корректно обработайте завершение процесса + +Ожидание может завершиться не только обычным кодом возврата. Разберите статус с +помощью предназначенных для этого макросов и различайте: + +- обычное завершение; +- завершение по сигналу; +- остановку или продолжение процесса, если эти состояния отслеживаются. + +Ожидание может быть прервано сигналом. В этом случае обычно требуется повторить +его. Нельзя считать PID кодом завершения или сравнивать необработанный статус +напрямую с нулём. + +Для процесса, завершённого сигналом, удобно сформировать внутренний ненулевой +статус по правилу `128 + номер сигнала`. Главное — использовать одно соглашение +во всём executor. + +### Шаг 6. Добавьте `;`, `&&` и `||` + +Сначала parser должен отделить команды от операторов и обнаружить синтаксические +ошибки: оператор в начале, два несовместимых оператора подряд, отсутствующая +команда в конце. + +Executor хранит статус последней действительно выполненной команды и решает, +нужно ли запускать следующую: + +- после `;` — всегда; +- после `&&` — только после успеха; +- после `||` — только после ошибки. + +Команда, пропущенная из-за короткого замыкания, не создаёт процесс и не меняет +статус последней выполненной команды. + +Проверьте не только пары, но и цепочки: + +```text +true && false || echo recovered +false && echo no ; echo yes +``` + +### Шаг 7. Добавьте фоновое выполнение + +Для команды с `&` родитель не должен выполнять блокирующее ожидание. Он сохраняет +информацию о задаче и сразу показывает следующее приглашение. + +Однако завершившийся ребёнок всё равно должен быть собран. Периодически вызывайте +неблокирующее ожидание: + +- перед показом приглашения; +- после обработки строки; +- при получении уведомления `SIGCHLD`, если реализовано расширенное управление + сигналами. + +Обработчик `SIGCHLD` должен делать минимум работы. Сложное обновление структур и +печать лучше выполнять в основном цикле. Помните, что один сигнал может +соответствовать завершению нескольких процессов, поэтому проверка должна +продолжаться, пока доступен хотя бы один изменивший состояние ребёнок. + +## 6. Расширения + +### 6.1. Измерение времени + +Используйте монотонные часы, не зависящие от изменения системного календаря. +Зафиксируйте время непосредственно перед созданием процесса и после получения +его окончательного статуса. + +Для фоновой команды результат можно вывести в момент её сбора. Заранее определите: + +- измеряется каждая программа или вся логическая цепочка; +- куда печатается результат — обычно служебные сообщения отправляют в `stderr`; +- в каких единицах и с какой точностью показывается время. + +Автоматический тест не должен сравнивать время на точное равенство. + +### 6.2. Защита от fork bomb + +Один только счётчик фоновых задач не защищает от программы, которая сама создаёт +тысячи потомков. Базовая защита должна сочетать: + +1. лимит одновременно отслеживаемых фоновых задач в shell; +2. ограничение числа процессов через `RLIMIT_NPROC`, наследуемое запускаемой + программой; +3. проверку ошибок создания процесса (`EAGAIN`, `ENOMEM`) без повторения в + бесконечном цикле; +4. ограничение длины строки и числа команд в одном плане; +5. безопасную среду испытаний: контейнер, отдельного пользователя или VM. + +`RLIMIT_NPROC` обычно действует на реального пользователя, а не только на один +shell. Значение лимита должно быть настраиваемым и не должно случайно мешать +остальным процессам пользователя. + +Не запускайте настоящую fork bomb на основной системе. Для теста используйте +контролируемую вспомогательную программу, малый лимит и обязательный timeout. + +### 6.3. `Ctrl-C` и управление терминалом + +При нажатии `Ctrl-C` терминал обычно отправляет `SIGINT` всей foreground-группе +процессов. Желаемое поведение: + +- выполняемая команда завершается; +- сам shell остаётся жив; +- появляется новое приглашение; +- фоновые задачи не получают этот `SIGINT`. + +Для устойчивого решения изучите группы процессов, `setpgid` и передачу терминала +foreground-группе через `tcsetpgrp`. В дочернем процессе перед `exec` должны быть +восстановлены стандартные реакции на сигналы, которые shell обрабатывает или +игнорирует сам. + +Обработчик сигнала не должен вызывать функции, небезопасные в асинхронном +контексте. Обычно он только меняет флаг типа `sig_atomic_t`, а основная логика +выполняется в REPL. + +Ещё раз: `Ctrl-D` — это EOF, а не сигнал. + +### 6.4. Встроенная команда `jobs` + +`jobs` выполняется самим shell и не создаёт внешнюю программу. Для каждой фоновой +задачи храните: + +- локальный номер задачи; +- PID или PGID; +- исходную командную строку; +- состояние `Running`, `Stopped` или `Done`; +- итоговый статус, если задача завершена. + +Перед выводом списка обновите состояния неблокирующим ожиданием. Определите, +когда завершённые записи удаляются: сразу после уведомления, после одного показа +в `jobs` или только по явной очистке. + +### 6.5. Конвейеры + +Конвейер связывает стандартный вывод одной программы со стандартным вводом +следующей: + +```text +printf hello | wc -c +``` + +Для каждого соседнего звена нужен канал, созданный через `pipe`. После создания +ребёнка нужные концы подключаются к `STDIN_FILENO` и `STDOUT_FILENO` через +`dup2`. Все неиспользуемые файловые дескрипторы закрываются и в детях, и в +родителе. + +Типичная причина зависания — открытый в родителе дескриптор записи: последний +читатель не получает EOF. Сначала реализуйте конвейер из двух программ, затем +обобщите его на произвольное число звеньев. Родитель должен дождаться всех +процессов foreground-конвейера; статусом конвейера обычно считают статус его +последнего звена. + +## 7. Собственные тесты на Python + +Тесты являются частью работы, а не только способом выставления оценки. Создайте +отдельный набор тестов, который запускает собранный shell как дочерний процесс, +передаёт команды через `stdin` и читает `stdout` и `stderr`. + +Рекомендуется использовать стандартные модули `subprocess`, `unittest` или +`pytest`. Для каждого запуска задавайте timeout, чтобы ошибка ожидания не +заблокировала весь набор тестов. Не запускайте проверяемые команды через +`shell=True`, иначе часть задания незаметно выполнит системная оболочка. + +Минимальный набор сценариев: + +1. пустая строка и EOF; +2. программа без аргументов и с несколькими аргументами; +3. лишние пробелы; +4. абсолютный путь и поиск через `PATH`; +5. передача специально заданной переменной окружения; +6. несуществующая и неисполняемая команда; +7. коды завершения `0` и не `0`; +8. каждая ветвь операторов `;`, `&&`, `||`; +9. цепочка из трёх и более команд; +10. фоновая команда не блокирует следующую; +11. после фоновой команды не остаётся zombie; +12. `Ctrl-C` завершает foreground-команду, но не shell; +13. `jobs` показывает и затем обновляет задачу, если расширение реализовано; +14. конвейер передаёт данные и получает EOF, если расширение реализовано; +15. ошибочный синтаксис не завершает shell. + +Для проверки поведения терминала может потребоваться псевдотерминал (PTY). +Закрытие входного канала моделирует `Ctrl-D`; отправка `SIGINT` группе процессов +моделирует `Ctrl-C` надёжнее, чем передача символа `\x03` в обычный pipe. + +Каждый тест должен создавать собственное окружение и удалять временные файлы и +процессы после завершения, в том числе после ошибки или timeout. + +## 8. Рекомендуемый порядок работы + +Не пытайтесь реализовать всё сразу. После каждого этапа сохраните работающую +версию и добавьте тесты: + +1. REPL и EOF; +2. parser одной команды; +3. запуск по явному пути; +4. поиск через `PATH` и окружение; +5. ожидание и статусы; +6. `;`; +7. `&&` и `||`; +8. `&` и сбор фоновых процессов; +9. выбранные расширения; +10. негативные тесты и проверка утечек ресурсов. + +Для диагностики полезны `strace`, просмотр дерева процессов и инструменты поиска +ошибок памяти. На защите будьте готовы по трассировке объяснить, какой процесс +вызвал каждый системный вызов. + +## 9. Критерии готовности + +Работа готова к защите, если: + +- shell стабильно принимает несколько строк подряд; +- обычные команды получают правильные `argv` и окружение; +- используется назначенный вариантом механизм создания процесса; +- `;`, `&&`, `||` и `&` имеют описанное поведение; +- ошибки запуска не завершают shell; +- foreground-процессы ожидаются, background-процессы собираются; +- после тестов не остаются дочерние процессы и zombie; +- EOF корректно завершает оболочку; +- реализованные расширения документированы; +- Python-тесты воспроизводимо проходят с timeout; +- студент может объяснить различия между `fork`, `vfork`, `clone`, `exec` и + `wait`, даже если реализовывал только свой вариант. + +## 10. Вопросы для самопроверки + +1. Почему после создания процесса одна ветвь вызывает `exec`, а другая — `wait`? +2. Что произойдёт, если `exec` завершится ошибкой и ребёнок продолжит цикл shell? +3. Почему после `vfork` набор допустимых действий ребёнка сильно ограничен? +4. Чем PID отличается от кода завершения? +5. Когда появляется zombie и кто обязан его собрать? +6. Почему фоновый процесс нельзя ожидать обычным блокирующим вызовом сразу после + запуска? +7. Как `&&` и `||` используют статус предыдущей команды? +8. Что именно наследует дочерний процесс от shell? +9. Почему `Ctrl-C` и `Ctrl-D` требуют разной обработки? +10. Почему незакрытый конец `pipe` может навсегда задержать EOF? +11. Почему счётчик задач внутри shell не останавливает fork bomb внутри дочерней + программы? +12. Какие функции допустимо вызывать из обработчика сигнала? + +## 11. Полезная документация + +Перед реализацией прочитайте соответствующие страницы руководства: + +- `fork(2)`, `vfork(2)` или `clone(2)` — по варианту; +- `execve(2)`, `exec(3)`; +- `wait(2)`, `waitpid(2)`; +- `environ(7)`; +- `signal(7)`, `sigaction(2)`; +- `setpgid(2)`, `tcsetpgrp(3)`; +- `pipe(2)`, `dup2(2)` — для конвейеров; +- `clock_gettime(2)` — для измерения времени; +- `getrlimit(2)`, `setrlimit(2)` — для ограничения ресурсов. + +Суффикс `(2)` обозначает системные вызовы, `(3)` — функции библиотечного API, +`(7)` — обзорные разделы документации. Это различие также следует уметь +объяснить на защите. From 441f0b94a13979c00cd5b55f9d46c0d901e7ccc2 Mon Sep 17 00:00:00 2001 From: e1turin Date: Mon, 28 Sep 2026 02:35:01 +0300 Subject: [PATCH 02/21] enhance, add comments --- lab/vtsh/GUIDE.md | 113 +++++++++++++++++++++------------------------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 8e17052..f247254 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -1,4 +1,4 @@ -# Пошаговое руководство: простой Shell на C +# VTSH: простой Shell на C ## 1. Цель работы @@ -68,7 +68,7 @@ POSIX Shell. Главное — понять: - операторы `;`, `&&`, `||` и `&` отделяются пробелами; - кавычки, обратный слеш, glob-шаблоны (`*`, `?`) и подстановка `$NAME` не обязательны; -- `&` в обязательной части применяется только к одной простой команде; +- `&` применяется только к одинарной простой команде (без операторов); - некорректная или неполная конструкция должна выдавать понятную ошибку, а не приводить к аварийному завершению оболочки. @@ -82,8 +82,8 @@ false || echo fallback sleep 2 & ``` -Можно реализовать разбор операторов без окружающих пробелов как расширение. -Ограничения синтаксиса должны быть описаны в отчёте и проверены тестами. +Можно дополнительно расширить возможности синтаксиса. Ограничения синтаксиса +должны быть описаны в отчёте и проверены тестами. ### Семантика операторов @@ -118,7 +118,7 @@ sleep 2 & - **план выполнения**: список команд и операторов между ними. Для фоновых задач дополнительно понадобятся идентификатор задачи, PID или PGID, -исходный текст команды и текущее состояние. +исходный текст команды и атрибут текущего состояния. ## 5. Пошаговая разработка @@ -138,10 +138,10 @@ sleep 2 & терминалом. Это упрощает автоматическое тестирование через перенаправление ввода. -`Ctrl-D` не посылает сигнал. Терминал сообщает программе конец файла (EOF), если -во входном буфере нет данных. В этом случае оболочка должна аккуратно завершить -цикл, освободить память и решить, что делать с ещё работающими фоновыми задачами. -Выбранная политика должна быть описана в отчёте. +Сочетание клавиш `Ctrl-D` не посылает сигнал. Терминал сообщает программе конец +файла (EOF), если во входном буфере нет данных. В этом случае оболочка должна +аккуратно завершить цикл, освободить память и решить, что делать с ещё +работающими фоновыми задачами. Выбранная политика должна быть описана в отчёте. ### Шаг 2. Разберите простую команду @@ -165,29 +165,30 @@ sleep 2 & Общий сценарий запуска выглядит так: -1. родитель создаёт дочерний процесс выбранным по варианту способом; +1. родитель создаёт дочерний процесс способом, соответствующим варианту; 2. дочерний процесс подготавливает своё состояние и заменяет свой образ внешней - программой через семейство `exec`; + программой через вызов системной функции из семейства `exec`; 3. при ошибке `exec` дочерний процесс печатает диагностическое сообщение и - немедленно завершается через `_exit`; -4. родитель сохраняет PID и ожидает дочерний процесс, если команда не фоновая; -5. родитель преобразует результат ожидания в код выполнения команды. + немедленно завершается; +4. родитель сохраняет PID и в стаёт в ожидаии дочернего процесса с помощью + функции `wait`, если команда не фоновая; +5. родитель преобразует результат функции ожидания в код выполнения команды. -Не вызывайте внешнюю программу через `system`, `popen` или запуск `/bin/sh -c`: -эти функции скрывают именно те действия, которые требуется изучить. +Запрещено вызывать внешнюю программу через `system`, `popen` или запуск `/bin/sh -c` +— задача разрабатать свой shell самостоятельно, а не взять готовый. Рекомендуемые соглашения об ошибках: - программа не найдена — статус `127`; - программа найдена, но не может быть запущена — статус `126`; - ошибка создания процесса остаётся ошибкой текущей команды, но не завершает - сам shell. + сам shell. #### Вариант `fork` -После `fork` родитель и ребёнок продолжают выполнение с одной точки, но имеют +После `fork` родитель и ребёнок shell'ы продолжают выполнение с одной точки, но имеют разные адресные пространства. Оба процесса должны сразу выбрать свою ветвь по -возвращённому значению. Нужно обработать и отрицательный результат. +возвращённому значению. Нужно обработать случай ошибки с отрицательным результатом. #### Вариант `vfork` @@ -198,16 +199,17 @@ sleep 2 & `vfork`. На защите необходимо объяснить, почему обычный путь обработки после `fork` -нельзя механически перенести в ветвь `vfork`. +нельзя механически перенести в ветвь `vfork`. #### Вариант `clone` Необходимо самостоятельно выбрать и обосновать флаги, предоставить стек для дочернего контекста и обеспечить получение родителем уведомления о завершении. Для поведения, похожего на отдельный процесс, не следует без необходимости -разделять память через `CLONE_VM`. Интерфейс и допустимые флаги зависят от Linux, -поэтому сверяйтесь с документацией именно той системы, где будет проверяться -работа. +разделять память через `CLONE_VM`, как и аллоцировать память стека с помощью +`malloc` — посмотрите самостоятельно как это следует делать. Интерфейс и +допустимые флаги зависят от Linux, поэтому сверяйтесь с документацией именно той +системы, где будет проверяться работа. ### Шаг 4. Передайте окружение и найдите программу @@ -222,8 +224,8 @@ sleep 2 & - запуск не удался по другой причине. Передача окружения не означает обязательную поддержку `$NAME` в командной -строке. Подстановка переменных и встроенная команда `export` являются отдельными -расширениями. +строке. Подстановка переменных и встроенная команда `export` являются +необязательными расширениями. ### Шаг 5. Корректно обработайте завершение процесса @@ -236,7 +238,7 @@ sleep 2 & Ожидание может быть прервано сигналом. В этом случае обычно требуется повторить его. Нельзя считать PID кодом завершения или сравнивать необработанный статус -напрямую с нулём. +напрямую с нулём. Для процесса, завершённого сигналом, удобно сформировать внутренний ненулевой статус по правилу `128 + номер сигнала`. Главное — использовать одно соглашение @@ -255,10 +257,12 @@ Executor хранит статус последней действительно - после `&&` — только после успеха; - после `||` — только после ошибки. -Команда, пропущенная из-за короткого замыкания, не создаёт процесс и не меняет -статус последней выполненной команды. +Порядок выполнения команд ленивый: если первый операнд даёт понять, какой будет +итоговы результат, то второй операнд вычислять не трубется, т.е. вторая команда +не выполняется и не создаёт процесс, не меняет статус последней выполненной +команды. -Проверьте не только пары, но и цепочки: +Проверьте не только пары, но и цепочки, например: ```text true && false || echo recovered @@ -270,12 +274,12 @@ false && echo no ; echo yes Для команды с `&` родитель не должен выполнять блокирующее ожидание. Он сохраняет информацию о задаче и сразу показывает следующее приглашение. -Однако завершившийся ребёнок всё равно должен быть собран. Периодически вызывайте -неблокирующее ожидание: +Однако завершившийся в фоне подпроцесс всё равно должен быть обработан. +Периодически вызывайте **неблокирующее** ожидание: -- перед показом приглашения; +- перед показом приглашения к вводу; - после обработки строки; -- при получении уведомления `SIGCHLD`, если реализовано расширенное управление +- при получении сигнала `SIGCHLD`, если реализовано расширенное управление сигналами. Обработчик `SIGCHLD` должен делать минимум работы. Сложное обновление структур и @@ -289,15 +293,13 @@ false && echo no ; echo yes Используйте монотонные часы, не зависящие от изменения системного календаря. Зафиксируйте время непосредственно перед созданием процесса и после получения -его окончательного статуса. - -Для фоновой команды результат можно вывести в момент её сбора. Заранее определите: +его окончательного статуса. Измеряется вся логическая цепочка. Для фоновой +команды результат можно вывести в сразу же после её запуска. -- измеряется каждая программа или вся логическая цепочка; -- куда печатается результат — обычно служебные сообщения отправляют в `stderr`; -- в каких единицах и с какой точностью показывается время. +Результат, как обычное служебное сообщениеб печатается в `stderr`, время +выводите в мс, с точностью до сотых. -Автоматический тест не должен сравнивать время на точное равенство. +Автоматический тест **не** должен сравнивать время на точное равенство. ### 6.2. Защита от fork bomb @@ -402,7 +404,9 @@ printf hello | wc -c 12. `Ctrl-C` завершает foreground-команду, но не shell; 13. `jobs` показывает и затем обновляет задачу, если расширение реализовано; 14. конвейер передаёт данные и получает EOF, если расширение реализовано; -15. ошибочный синтаксис не завершает shell. +15. ошибочный синтаксис не завершает shell; +16. после выполнения `cd` новые команды выполняются в контексте указанной + директории. Для проверки поведения терминала может потребоваться псевдотерминал (PTY). Закрытие входного канала моделирует `Ctrl-D`; отправка `SIGINT` группе процессов @@ -428,27 +432,14 @@ printf hello | wc -c 10. негативные тесты и проверка утечек ресурсов. Для диагностики полезны `strace`, просмотр дерева процессов и инструменты поиска -ошибок памяти. На защите будьте готовы по трассировке объяснить, какой процесс -вызвал каждый системный вызов. - -## 9. Критерии готовности - -Работа готова к защите, если: - -- shell стабильно принимает несколько строк подряд; -- обычные команды получают правильные `argv` и окружение; -- используется назначенный вариантом механизм создания процесса; -- `;`, `&&`, `||` и `&` имеют описанное поведение; -- ошибки запуска не завершают shell; -- foreground-процессы ожидаются, background-процессы собираются; -- после тестов не остаются дочерние процессы и zombie; -- EOF корректно завершает оболочку; -- реализованные расширения документированы; -- Python-тесты воспроизводимо проходят с timeout; -- студент может объяснить различия между `fork`, `vfork`, `clone`, `exec` и - `wait`, даже если реализовывал только свой вариант. +ошибок памяти ("санитайзеры"). На защите будьте готовы по трассировке объяснить, +какой процесс вызвал каждый системный вызов. + +Будьте готовы объяснить различия между `fork`, `vfork`, `clone`, `exec`, +`wait` и `pipe`, даже если реализовывал только свой вариант. ## 10. Вопросы для самопроверки + 1. Почему после создания процесса одна ветвь вызывает `exec`, а другая — `wait`? 2. Что произойдёт, если `exec` завершится ошибкой и ребёнок продолжит цикл shell? @@ -467,7 +458,7 @@ printf hello | wc -c ## 11. Полезная документация -Перед реализацией прочитайте соответствующие страницы руководства: +Перед реализацией прочитайте соответствующие страницы руководства `man`: - `fork(2)`, `vfork(2)` или `clone(2)` — по варианту; - `execve(2)`, `exec(3)`; From d87359781b98662fcdd918c0075eeae487dd55b7 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 00:47:17 +0300 Subject: [PATCH 03/21] simplify part of logic operators --- lab/vtsh/GUIDE.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index f247254..37e9f56 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -244,11 +244,10 @@ sleep 2 & статус по правилу `128 + номер сигнала`. Главное — использовать одно соглашение во всём executor. -### Шаг 6. Добавьте `;`, `&&` и `||` +### Шаг 6. Добавьте логические операторы Сначала parser должен отделить команды от операторов и обнаружить синтаксические -ошибки: оператор в начале, два несовместимых оператора подряд, отсутствующая -команда в конце. +ошибки. Executor хранит статус последней действительно выполненной команды и решает, нужно ли запускать следующую: @@ -265,8 +264,9 @@ Executor хранит статус последней действительно Проверьте не только пары, но и цепочки, например: ```text -true && false || echo recovered -false && echo no ; echo yes +true && echo recovered && false +false ; echo no ; echo yes +false || false || echo recovered ``` ### Шаг 7. Добавьте фоновое выполнение From 9456e119ccc7716471fec2aabd67d0ba1ed72998 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 00:48:39 +0300 Subject: [PATCH 04/21] improve part of new process creation --- lab/vtsh/GUIDE.md | 36 ++++++------------------------------ 1 file changed, 6 insertions(+), 30 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 37e9f56..dacb3ea 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -165,7 +165,7 @@ sleep 2 & Общий сценарий запуска выглядит так: -1. родитель создаёт дочерний процесс способом, соответствующим варианту; +1. родитель создаёт дочерний процесс способом, соответствующим варианту (`fork`/...); 2. дочерний процесс подготавливает своё состояние и заменяет свой образ внешней программой через вызов системной функции из семейства `exec`; 3. при ошибке `exec` дочерний процесс печатает диагностическое сообщение и @@ -181,37 +181,13 @@ sleep 2 & - программа не найдена — статус `127`; - программа найдена, но не может быть запущена — статус `126`; -- ошибка создания процесса остаётся ошибкой текущей команды, но не завершает - сам shell. +- ошибка создания процесса остаётся ошибкой текущей команды, но не превращается + в ошибку shell, как приложения, т.е. его работа не завершается, shell + продолжает принимать новые комады. -#### Вариант `fork` + -После `fork` родитель и ребёнок shell'ы продолжают выполнение с одной точки, но имеют -разные адресные пространства. Оба процесса должны сразу выбрать свою ветвь по -возвращённому значению. Нужно обработать случай ошибки с отрицательным результатом. - -#### Вариант `vfork` - -До успешного `exec` или `_exit` ребёнок временно использует адресное пространство -родителя, а родитель приостановлен. В дочерней ветви нельзя возвращаться из -текущей функции, изменять данные родителя, освобождать память и выполнять -небезопасную подготовительную работу. Все данные для запуска подготовьте до -`vfork`. - -На защите необходимо объяснить, почему обычный путь обработки после `fork` -нельзя механически перенести в ветвь `vfork`. - -#### Вариант `clone` - -Необходимо самостоятельно выбрать и обосновать флаги, предоставить стек для -дочернего контекста и обеспечить получение родителем уведомления о завершении. -Для поведения, похожего на отдельный процесс, не следует без необходимости -разделять память через `CLONE_VM`, как и аллоцировать память стека с помощью -`malloc` — посмотрите самостоятельно как это следует делать. Интерфейс и -допустимые флаги зависят от Linux, поэтому сверяйтесь с документацией именно той -системы, где будет проверяться работа. - -### Шаг 4. Передайте окружение и найдите программу +### Шаг 4. Пробросьте окружение Новая программа должна получить переменные окружения shell. Проверьте это с помощью утилит `env` или `printenv`. From 167bc6572154f346309b3305f7943e30e003f91f Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 00:49:06 +0300 Subject: [PATCH 05/21] remove part of PATH traverse --- lab/vtsh/GUIDE.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index dacb3ea..9d8e9a1 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -192,17 +192,6 @@ sleep 2 & Новая программа должна получить переменные окружения shell. Проверьте это с помощью утилит `env` или `printenv`. -Если имя содержит `/`, рассматривайте его как явный путь. В остальных случаях -последовательно проверьте каталоги из `PATH`. Следует различать ситуации: - -- подходящий файл не найден; -- файл существует, но не является исполняемым; -- запуск не удался по другой причине. - -Передача окружения не означает обязательную поддержку `$NAME` в командной -строке. Подстановка переменных и встроенная команда `export` являются -необязательными расширениями. - ### Шаг 5. Корректно обработайте завершение процесса Ожидание может завершиться не только обычным кодом возврата. Разберите статус с From 3b55f19ba75d71e828f93addabb0fc679efb887e Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 00:49:59 +0300 Subject: [PATCH 06/21] improve desc of handling of process ending --- lab/vtsh/GUIDE.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 9d8e9a1..acd119e 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -194,16 +194,16 @@ sleep 2 & ### Шаг 5. Корректно обработайте завершение процесса -Ожидание может завершиться не только обычным кодом возврата. Разберите статус с +Ожидание подпроцесса может завершиться по разным причинам. Обработайте статус с помощью предназначенных для этого макросов и различайте: - обычное завершение; - завершение по сигналу; - остановку или продолжение процесса, если эти состояния отслеживаются. -Ожидание может быть прервано сигналом. В этом случае обычно требуется повторить -его. Нельзя считать PID кодом завершения или сравнивать необработанный статус -напрямую с нулём. +Ожидание может быть прервано сигналом, например `SIGINT`. В этом случае +системный вызов может вернуть ошибку `EINTR`; если сигнал не должен завершать +операцию ожидания, его нужно обработать и повторить ожидание. Для процесса, завершённого сигналом, удобно сформировать внутренний ненулевой статус по правилу `128 + номер сигнала`. Главное — использовать одно соглашение From 8e410624de0578fa9a3f2f6a85c83a060b27df11 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 00:50:27 +0300 Subject: [PATCH 07/21] improve bg execution --- lab/vtsh/GUIDE.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index acd119e..cfe8589 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -236,8 +236,8 @@ false || false || echo recovered ### Шаг 7. Добавьте фоновое выполнение -Для команды с `&` родитель не должен выполнять блокирующее ожидание. Он сохраняет -информацию о задаче и сразу показывает следующее приглашение. +Для команды с `&` родитель не должен выполнять блокирующее ожидание. Он +сохраняет информацию о задаче и сразу показывает следующее приглашение к вводу. Однако завершившийся в фоне подпроцесс всё равно должен быть обработан. Периодически вызывайте **неблокирующее** ожидание: @@ -247,10 +247,13 @@ false || false || echo recovered - при получении сигнала `SIGCHLD`, если реализовано расширенное управление сигналами. -Обработчик `SIGCHLD` должен делать минимум работы. Сложное обновление структур и -печать лучше выполнять в основном цикле. Помните, что один сигнал может -соответствовать завершению нескольких процессов, поэтому проверка должна -продолжаться, пока доступен хотя бы один изменивший состояние ребёнок. +> [!NOTE] +> Обработчик `SIGCHLD` должен делать минимум работы. Сложное обновление структур +> и печать лучше выполнять в основном цикле. + +Помните, что один сигнал может соответствовать завершению нескольких процессов, +поэтому проверка должна продолжаться, пока доступен хотя бы один изменивший +состояние дочерний процесс. ## 6. Расширения From d900ff3fb483a23a7eac01acb386118097fe80e9 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 00:50:49 +0300 Subject: [PATCH 08/21] comment TODO for resource limiting --- lab/vtsh/GUIDE.md | 95 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index cfe8589..813f90e 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -269,6 +269,11 @@ false || false || echo recovered Автоматический тест **не** должен сравнивать время на точное равенство. +### 6.2. Вспомогательные ограничения ресурсов + + + + ### 6.3. `Ctrl-C` и управление терминалом From e85848f5e8fedc68fccf23fb67139333fe4d9cff Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 00:58:17 +0300 Subject: [PATCH 09/21] remove part of signal handling and jobs built in command --- lab/vtsh/GUIDE.md | 38 +------------------------------------- 1 file changed, 1 insertion(+), 37 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 813f90e..0729a40 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -384,43 +384,7 @@ TODO: make it shorter (just names of restriction and general idea, doc reference Для углублённого изучения ограничения числа процессов полезно также посмотреть документацию Linux по **cgroup v2 PIDs controller (`pids.max`)**. В отличие от `RLIMIT_NPROC`, он позволяет ограничивать число задач именно внутри конкретной группы процессов и её потомков.--> -### 6.3. `Ctrl-C` и управление терминалом - -При нажатии `Ctrl-C` терминал обычно отправляет `SIGINT` всей foreground-группе -процессов. Желаемое поведение: - -- выполняемая команда завершается; -- сам shell остаётся жив; -- появляется новое приглашение; -- фоновые задачи не получают этот `SIGINT`. - -Для устойчивого решения изучите группы процессов, `setpgid` и передачу терминала -foreground-группе через `tcsetpgrp`. В дочернем процессе перед `exec` должны быть -восстановлены стандартные реакции на сигналы, которые shell обрабатывает или -игнорирует сам. - -Обработчик сигнала не должен вызывать функции, небезопасные в асинхронном -контексте. Обычно он только меняет флаг типа `sig_atomic_t`, а основная логика -выполняется в REPL. - -Ещё раз: `Ctrl-D` — это EOF, а не сигнал. - -### 6.4. Встроенная команда `jobs` - -`jobs` выполняется самим shell и не создаёт внешнюю программу. Для каждой фоновой -задачи храните: - -- локальный номер задачи; -- PID или PGID; -- исходную командную строку; -- состояние `Running`, `Stopped` или `Done`; -- итоговый статус, если задача завершена. - -Перед выводом списка обновите состояния неблокирующим ожиданием. Определите, -когда завершённые записи удаляются: сразу после уведомления, после одного показа -в `jobs` или только по явной очистке. - -### 6.5. Конвейеры +### 6.3. Конвейеры Конвейер связывает стандартный вывод одной программы со стандартным вводом следующей: From 88451f3cb3bf31b88226c56470d3a3f235a3d209 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 01:09:49 +0300 Subject: [PATCH 10/21] enhance final requirements --- lab/vtsh/GUIDE.md | 55 ++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 0729a40..f6d6e33 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -411,33 +411,30 @@ printf hello | wc -c передаёт команды через `stdin` и читает `stdout` и `stderr`. Рекомендуется использовать стандартные модули `subprocess`, `unittest` или -`pytest`. Для каждого запуска задавайте timeout, чтобы ошибка ожидания не -заблокировала весь набор тестов. Не запускайте проверяемые команды через -`shell=True`, иначе часть задания незаметно выполнит системная оболочка. +`pytest`. Не запускайте проверяемые команды через `shell=True`, т.к. вам нужно +протестировать свой vtsh, а не системную оболочку. Минимальный набор сценариев: 1. пустая строка и EOF; -2. программа без аргументов и с несколькими аргументами; -3. лишние пробелы; -4. абсолютный путь и поиск через `PATH`; +2. вызов программы по абсолютному пути; +3. программа без аргументов и с несколькими аргументами; +4. лишние пробелы; 5. передача специально заданной переменной окружения; 6. несуществующая и неисполняемая команда; -7. коды завершения `0` и не `0`; -8. каждая ветвь операторов `;`, `&&`, `||`; -9. цепочка из трёх и более команд; -10. фоновая команда не блокирует следующую; -11. после фоновой команды не остаётся zombie; -12. `Ctrl-C` завершает foreground-команду, но не shell; -13. `jobs` показывает и затем обновляет задачу, если расширение реализовано; -14. конвейер передаёт данные и получает EOF, если расширение реализовано; -15. ошибочный синтаксис не завершает shell; -16. после выполнения `cd` новые команды выполняются в контексте указанной - директории. +7. ошибочный синтаксис не завершает shell; +8. коды завершения `0` и не `0`; +9. каждая ветвь логических операторов (`;`, `&&`, `||`); +10. цепочка из трёх и более команд; +11. фоновая команда не блокирует следующую; +12. после фоновой команды не остаётся zombie; +13. конвейер передаёт данные и получает EOF; +14. после выполнения `cd` новые команды выполняются в контексте указанной + директории; +15. ограничения на ресурсы работают. Для проверки поведения терминала может потребоваться псевдотерминал (PTY). -Закрытие входного канала моделирует `Ctrl-D`; отправка `SIGINT` группе процессов -моделирует `Ctrl-C` надёжнее, чем передача символа `\x03` в обычный pipe. +Закрытие входного канала моделирует `Ctrl-D`. Каждый тест должен создавать собственное окружение и удалять временные файлы и процессы после завершения, в том числе после ошибки или timeout. @@ -452,21 +449,25 @@ printf hello | wc -c 3. запуск по явному пути; 4. поиск через `PATH` и окружение; 5. ожидание и статусы; -6. `;`; -7. `&&` и `||`; +6. `;` / `&&` / `||`; +9. конвейер передачи данных `|` 8. `&` и сбор фоновых процессов; -9. выбранные расширения; -10. негативные тесты и проверка утечек ресурсов. +9. ограничение ресурсов; +10. тесты и проверка утечек ресурсов. Для диагностики полезны `strace`, просмотр дерева процессов и инструменты поиска -ошибок памяти ("санитайзеры"). На защите будьте готовы по трассировке объяснить, -какой процесс вызвал каждый системный вызов. +ошибок памяти ("санитайзеры"), отладчик GDB, `atop` в режиме записи. На защите +будьте готовы по трассировке объяснить, какой процесс вызвал каждый системный +вызов. Будьте готовы объяснить различия между `fork`, `vfork`, `clone`, `exec`, -`wait` и `pipe`, даже если реализовывал только свой вариант. +`wait` и `pipe`, rlimit, даже если реализовывал только свой вариант. ## 10. Вопросы для самопроверки - + +Ответьте на вопросы ниже после реализации обязательной части и выбранных +расширений. Они охватывают отдельные темы руководства и могут использоваться +для подготовки к защите. 1. Почему после создания процесса одна ветвь вызывает `exec`, а другая — `wait`? 2. Что произойдёт, если `exec` завершится ошибкой и ребёнок продолжит цикл shell? From a335d8b1548900fcff46ebe1cba0661d20303e58 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 01:51:44 +0300 Subject: [PATCH 11/21] update reference to man & rlimit part --- lab/vtsh/GUIDE.md | 188 ++++++++++++++++++---------------------------- 1 file changed, 75 insertions(+), 113 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index f6d6e33..5de4fca 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -185,7 +185,21 @@ sleep 2 & в ошибку shell, как приложения, т.е. его работа не завершается, shell продолжает принимать новые комады. - +Перед реализацией изучите страницы `man`: + +- [`fork(2)`](https://man7.org/linux/man-pages/man2/fork.2.html), + [`vfork(2)`](https://man7.org/linux/man-pages/man2/vfork.2.html) или + [`clone(2)`](https://man7.org/linux/man-pages/man2/clone.2.html) — создание + процесса по варианту и ограничения выбранного механизма; +- [`exec(3)`](https://man7.org/linux/man-pages/man3/exec.3.html) и + [`execve(2)`](https://man7.org/linux/man-pages/man2/execve.2.html) — замена образа + процесса, поиск через `PATH`, передача аргументов и окружения; +- [`wait(2)` / `waitpid(2)`](https://man7.org/linux/man-pages/man2/wait.2.html) — + ожидание конкретного ребёнка, неблокирующее ожидание и макросы разбора статуса; +- [`_exit(2)`](https://man7.org/linux/man-pages/man2/_exit.2.html) — завершение + дочернего процесса при ошибке запуска без повторного сброса буферов stdio. + +Например, локально страницу можно открыть командой `man 2 fork`. ### Шаг 4. Пробросьте окружение @@ -271,118 +285,29 @@ false || false || echo recovered ### 6.2. Вспомогательные ограничения ресурсов - - - +Особенности Linux, безопасная установка лимитов и подробности проверок приведены +в [приложении A](#приложение-a-ограничения-ресурсов-в-linux). ### 6.3. Конвейеры @@ -496,8 +421,45 @@ printf hello | wc -c - `setpgid(2)`, `tcsetpgrp(3)`; - `pipe(2)`, `dup2(2)` — для конвейеров; - `clock_gettime(2)` — для измерения времени; -- `getrlimit(2)`, `setrlimit(2)` — для ограничения ресурсов. +- `getrlimit(2)`, `setrlimit(2)` — для ограничения ресурсов; +- `prlimit(1)`, `proc_pid_limits(5)` — для проверки лимитов в Linux. + +Суффикс `(1)` обозначает пользовательские команды, `(2)` — системные вызовы, +`(3)` — функции библиотечного API, `(5)` — форматы файлов, `(7)` — обзорные +разделы документации. Это различие также следует уметь объяснить на защите. + +## Приложение A. Ограничения ресурсов в Linux + +### Установка ограничений и их наследование + +Ограничение (лимит) обычно устанавливают после `fork` в дочернем процессе до +`exec`. Лимиты наследуются при создании потомков и сохраняются после `exec`, но +обычно учитывают ресурсы каждого процесса отдельно, а не сумму по дереву +потомков. У `RLIMIT_NPROC` особая область учёта — реальный пользователь. + +Есть два параметра ограничений: **soft** и **hard limit** — уточните, в чем +разница и в каком случае следует какое устанавливать. При установке нового +значения учитывайте исходные значения из `getrlimit()`. + +Проверяйте ошибки установки лимита, сам shell должен продолжить принимать +команды и не запускать команду без заданного ограничения. + +> [!WARNING] +> После `vfork()` нельзя произвольно вызывать функции, в том числе `setrlimit()`, +> до `exec()` или `_exit()`. Для такого варианта потребуется отдельная программа- +> обёртка, запускаемая через `exec`: она установит лимит и выполнит целевую +> программу. Для `clone()` учитывайте выбранные флаги совместного использования +> памяти и ресурсов; не переносите схему для `fork()` без проверки документации. + +Текущие лимиты можно проверить через `/proc//limits` (`man 5 proc_pid_limits`), +а для экспериментов вне shell использовать `prlimit` (`man 1 +prlimit`). + +Дополнительные материалы: -Суффикс `(2)` обозначает системные вызовы, `(3)` — функции библиотечного API, -`(7)` — обзорные разделы документации. Это различие также следует уметь -объяснить на защите. +- [`getrlimit(2)` / `setrlimit(2)`](https://man7.org/linux/man-pages/man2/getrlimit.2.html) + — семантика лимитов, наследование и ошибки; +- Michael Kerrisk, *The Linux Programming Interface*, глава **Process Resources**; +- [cgroup v2 PIDs controller](https://docs.kernel.org/admin-guide/cgroup-v2.html#pid) + — `pids.max` ограничивает число задач в cgroup и её дочерних cgroup, в отличие + от учёта по real UID в `RLIMIT_NPROC`. From 6e25b25d9ff8495957cc67c6df01ad9aed8fe74e Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 01:51:52 +0300 Subject: [PATCH 12/21] update intro --- lab/vtsh/GUIDE.md | 46 +++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 5de4fca..5f5a31b 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -24,40 +24,22 @@ POSIX Shell. Главное — понять: ## 2. Состав задания -### 2.1. Обязательная часть +### 2. Функциональность VTSH Оболочка должна: -1. быть написана на C; -2. показывать приглашение, читать строку и разбирать её на команду и аргументы; -3. запускать внешнюю программу через механизм создания процессов, назначенный - вариантом: `fork`, `vfork` или `clone`; +1. быть написана на C (или другой низкоуровневый язык с возможностью выполнения + системных вызовов напрямую через C-API); +2. показывать приглашение к вводу; +3. запускать внешнюю программу по абсолютному пути; +4. поддерживать команды `cd` и `exit`; 4. передавать новой программе аргументы и текущее окружение оболочки; -5. находить программу по абсолютному/относительному пути и через `PATH`; -6. корректно ожидать процессы переднего плана и получать их статус; -7. поддерживать операторы `;`, `&&` и `||`; -8. запускать простую команду в фоне с помощью `&`; -9. не оставлять завершившиеся дочерние процессы в состоянии zombie; -10. обрабатывать пустой ввод, ошибочную команду и завершение стандартного ввода. - -Все операторы реализуются каждым студентом. Вариант определяет только способ -создания дочернего процесса. - -### 2.2. Расширения - -Набор обязательных расширений определяет преподаватель. Возможные расширения: - -- измерение времени работы программы; -- ограничение ресурсов и защита от fork bomb; -- более полная обработка `Ctrl-C` и других сигналов; -- встроенная команда `jobs`; -- конвейеры через `|`; -- перенаправление стандартных потоков; -- кавычки, экранирование и подстановка переменных окружения; -- встроенные команды `cd`, `export`, `unset` и `exit`. - -Независимо от выбранных расширений студент должен подготовить собственные -автоматические тесты на Python. +5. корректно ожидать запускаемые подпроцессы и получать их статус; +6. поддерживать конвейеризацию через `|`; +7. запускать простую команду в фоне с помощью `&`; +8. не оставлять завершившиеся дочерние процессы в состоянии zombie; +9. поддерживать логические операторы по вариантам; +10. ограничение ресурсов запускаемых программ. ## 3. Минимальный синтаксис @@ -65,9 +47,7 @@ POSIX Shell. Главное — понять: - команда — имя программы и ноль или больше аргументов; - слова разделяются одним или несколькими пробельными символами; -- операторы `;`, `&&`, `||` и `&` отделяются пробелами; -- кавычки, обратный слеш, glob-шаблоны (`*`, `?`) и подстановка `$NAME` не - обязательны; +- операторы `;`, `&&`, `||`, `&`, `|` отделяются пробелами; - `&` применяется только к одинарной простой команде (без операторов); - некорректная или неполная конструкция должна выдавать понятную ошибку, а не приводить к аварийному завершению оболочки. From d3e5ed02e237b8fb5dbd284878a86502a7a8a836 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:19:38 +0300 Subject: [PATCH 13/21] extract extensions by variant --- lab/vtsh/GUIDE.md | 162 +++++++++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 75 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 5f5a31b..21b363a 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -1,11 +1,23 @@ # VTSH: простой Shell на C +| Вариант | Описание | +| ------- | -------- | +| fork/vfork/clone | Способ создания подпроцесса | +| timer | * Расширение для замера времени выполнения команды | +| pipe | * Расширение для перенаправления ввода-вывода | +| bg | * Расширение для запуска фоновых процессов | +| `&&` / `\|\|` / `;` | * Расширение с логическими операторами для цепочки вызовов | +| NPROC / NOFILE / FSIZE / STACK / AS | * Расширение с возможностью установки ограничений на ресурсы в подпроцессе | + +\* — Расширения опциональные, задаются преподавателем. + ## 1. Цель работы -В этой лабораторной работе необходимо разработать **простую командную оболочку** -на языке C. Оболочка должна показать, как пользовательская программа запускает -другие программы с помощью системных вызовов ОС, передаёт им аргументы и -окружение, ожидает их завершения и управляет фоновыми процессами. +В этой лабораторной работе необходимо разработать **простую командную оболочку**. + +Оболочка должна показать, как пользовательская программа запускает другие +программы с помощью системных вызовов ОС, передаёт им аргументы и окружение, +ожидает их завершения и управляет фоновыми процессами. Задача не состоит в повторении Bash. Не требуется поддерживать весь синтаксис POSIX Shell. Главное — понять: @@ -33,13 +45,16 @@ POSIX Shell. Главное — понять: 2. показывать приглашение к вводу; 3. запускать внешнюю программу по абсолютному пути; 4. поддерживать команды `cd` и `exit`; -4. передавать новой программе аргументы и текущее окружение оболочки; -5. корректно ожидать запускаемые подпроцессы и получать их статус; -6. поддерживать конвейеризацию через `|`; -7. запускать простую команду в фоне с помощью `&`; -8. не оставлять завершившиеся дочерние процессы в состоянии zombie; -9. поддерживать логические операторы по вариантам; -10. ограничение ресурсов запускаемых программ. +5. передавать новой программе аргументы и текущее окружение оболочки; +6. корректно ожидать запускаемые подпроцессы и получать их статус; + +Дополнительные функции (по варианту): + +1. замерять время работы выполнения комманд; +2. поддерживать конвейеризацию через `|`; +3. запускать простую команду в фоне с помощью `&`; +4. поддерживать логические операторы по вариантам; +5. ограничение ресурсов запускаемых программ по вариантам. ## 3. Минимальный синтаксис @@ -65,26 +80,14 @@ sleep 2 & Можно дополнительно расширить возможности синтаксиса. Ограничения синтаксиса должны быть описаны в отчёте и проверены тестами. -### Семантика операторов - -| Оператор | Поведение | -|---|---| -| `cmd1 ; cmd2` | выполнить `cmd1`, затем выполнить `cmd2` независимо от статуса `cmd1` | -| `cmd1 && cmd2` | выполнить `cmd2`, только если `cmd1` завершилась успешно | -| `cmd1 \|\| cmd2` | выполнить `cmd2`, только если `cmd1` завершилась с ошибкой | -| `cmd &` | запустить `cmd` и сразу вернуть управление пользователю | - -Успехом считается код завершения `0`. Любой ненулевой код или завершение по -сигналу считается ошибкой. Операторы `&&` и `||` имеют одинаковый приоритет и -вычисляются слева направо; `;` разделяет такие логические цепочки. ## 4. Рекомендуемая архитектура -Разделите программу хотя бы на четыре логические части: +Программу можно разделить на четыре логические части: 1. **REPL** — приглашение, чтение строк и завершение оболочки; 2. **parser** — преобразование строки в команды, аргументы и операторы; -3. **executor** — запуск команд и применение правил `;`, `&&`, `||`, `&`; +3. **executor** — запуск команд и применение правил логических операторов; 4. **process manager** — ожидание, хранение фоновых задач и сбор завершившихся процессов. @@ -100,7 +103,7 @@ sleep 2 & Для фоновых задач дополнительно понадобятся идентификатор задачи, PID или PGID, исходный текст команды и атрибут текущего состояния. -## 5. Пошаговая разработка +## 5. Пошаговая инструкция к разработке ### Шаг 1. Реализуйте цикл оболочки @@ -203,32 +206,41 @@ sleep 2 & статус по правилу `128 + номер сигнала`. Главное — использовать одно соглашение во всём executor. -### Шаг 6. Добавьте логические операторы +## 6. Расширения -Сначала parser должен отделить команды от операторов и обнаружить синтаксические -ошибки. +### 6.1. Измерение времени -Executor хранит статус последней действительно выполненной команды и решает, -нужно ли запускать следующую: +Используйте монотонные часы, не зависящие от изменения системного календаря. +Зафиксируйте время непосредственно перед созданием процесса и после получения +его окончательного статуса. Измеряется вся логическая цепочка. Для фоновой +команды результат можно вывести в сразу же после её запуска. -- после `;` — всегда; -- после `&&` — только после успеха; -- после `||` — только после ошибки. +Результат, как обычное служебное сообщениеб печатается в `stderr`, время +выводите в мс, с точностью до сотых. -Порядок выполнения команд ленивый: если первый операнд даёт понять, какой будет -итоговы результат, то второй операнд вычислять не трубется, т.е. вторая команда -не выполняется и не создаёт процесс, не меняет статус последней выполненной -команды. +Автоматический тест **не** должен сравнивать время на точное равенство. -Проверьте не только пары, но и цепочки, например: +### 6.2. Конвейеры + +Конвейер связывает стандартный вывод одной программы со стандартным вводом +следующей: ```text -true && echo recovered && false -false ; echo no ; echo yes -false || false || echo recovered +printf hello | wc -c ``` -### Шаг 7. Добавьте фоновое выполнение +Для каждого соседнего звена нужен канал, созданный через `pipe`. После создания +ребёнка нужные концы подключаются к `STDIN_FILENO` и `STDOUT_FILENO` через +`dup2`. Все неиспользуемые файловые дескрипторы закрываются и в детях, и в +родителе. + +Типичная причина зависания — открытый в родителе дескриптор записи: последний +читатель не получает EOF. Сначала реализуйте конвейер из двух программ, затем +обобщите его на произвольное число звеньев. Родитель должен дождаться всех +процессов foreground-конвейера; статусом конвейера обычно считают статус его +последнего звена. + +### 6.3. Фоновое выполнение Для команды с `&` родитель не должен выполнять блокирующее ожидание. Он сохраняет информацию о задаче и сразу показывает следующее приглашение к вводу. @@ -244,26 +256,45 @@ false || false || echo recovered > [!NOTE] > Обработчик `SIGCHLD` должен делать минимум работы. Сложное обновление структур > и печать лучше выполнять в основном цикле. +> +> Помните, что один сигнал может соответствовать завершению нескольких +> процессов, поэтому проверка должна продолжаться, пока доступен хотя бы один +> изменивший состояние дочерний процесс. -Помните, что один сигнал может соответствовать завершению нескольких процессов, -поэтому проверка должна продолжаться, пока доступен хотя бы один изменивший -состояние дочерний процесс. +### 6.4. Логические операторы -## 6. Расширения +Сначала parser должен отделить команды от операторов и обнаружить синтаксические +ошибки. -### 6.1. Измерение времени +Executor хранит статус последней действительно выполненной команды и решает, +нужно ли запускать следующую: -Используйте монотонные часы, не зависящие от изменения системного календаря. -Зафиксируйте время непосредственно перед созданием процесса и после получения -его окончательного статуса. Измеряется вся логическая цепочка. Для фоновой -команды результат можно вывести в сразу же после её запуска. +- после `;` — всегда; +- после `&&` — только после успеха; +- после `||` — только после ошибки. -Результат, как обычное служебное сообщениеб печатается в `stderr`, время -выводите в мс, с точностью до сотых. +Порядок выполнения команд ленивый: если первый операнд даёт понять, какой будет +итоговы результат, то второй операнд вычислять не трубется, т.е. вторая команда +не выполняется и не создаёт процесс, не меняет статус последней выполненной +команды. -Автоматический тест **не** должен сравнивать время на точное равенство. +| Оператор | Поведение | +|---|---| +| `cmd1 ; cmd2` | выполнить `cmd1`, затем выполнить `cmd2` независимо от статуса `cmd1` | +| `cmd1 && cmd2` | выполнить `cmd2`, только если `cmd1` завершилась успешно | +| `cmd1 \|\| cmd2` | выполнить `cmd2`, только если `cmd1` завершилась с ошибкой | + +Успехом считается код завершения `0`. Любой ненулевой код или завершение по сигналу считается ошибкой. -### 6.2. Вспомогательные ограничения ресурсов +Проверьте не только пары, но и цепочки, например: + +```text +true && echo recovered && false +false ; echo no ; echo yes +false || false || echo recovered +``` + +### 6.5. Вспомогательные ограничения ресурсов Реализуйте **ограничение ресурсов запускаемых программ** по варианту расширения, назначенному преподавателем: @@ -289,25 +320,6 @@ shell продолжает работу. Настоящую fork bomb запус Особенности Linux, безопасная установка лимитов и подробности проверок приведены в [приложении A](#приложение-a-ограничения-ресурсов-в-linux). -### 6.3. Конвейеры - -Конвейер связывает стандартный вывод одной программы со стандартным вводом -следующей: - -```text -printf hello | wc -c -``` - -Для каждого соседнего звена нужен канал, созданный через `pipe`. После создания -ребёнка нужные концы подключаются к `STDIN_FILENO` и `STDOUT_FILENO` через -`dup2`. Все неиспользуемые файловые дескрипторы закрываются и в детях, и в -родителе. - -Типичная причина зависания — открытый в родителе дескриптор записи: последний -читатель не получает EOF. Сначала реализуйте конвейер из двух программ, затем -обобщите его на произвольное число звеньев. Родитель должен дождаться всех -процессов foreground-конвейера; статусом конвейера обычно считают статус его -последнего звена. ## 7. Собственные тесты на Python From 9088c98e7f0013c68d9e1b50676350dad02843fe Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:25:12 +0300 Subject: [PATCH 14/21] extract old task to another lab --- lab/vt25hell/.clang-format | 16 +++++++ lab/vt25hell/.clang-tidy | 17 +++++++ lab/vt25hell/.gitignore | 8 ++++ lab/vt25hell/.vscode/extensions.json | 25 +++++++++++ lab/vt25hell/.vscode/settings.json | 7 +++ lab/vt25hell/CMakeLists.txt | 13 ++++++ lab/{vtsh => vt25hell}/README.md | 0 lab/vt25hell/bin/CMakeLists.txt | 16 +++++++ lab/vt25hell/bin/main.c | 7 +++ lab/vt25hell/lib/CMakeLists.txt | 11 +++++ lab/vt25hell/lib/vtsh.c | 5 +++ lab/vt25hell/lib/vtsh.h | 3 ++ lab/vt25hell/test/base_test.py | 26 +++++++++++ lab/vt25hell/test/shell.py | 18 ++++++++ lab/vt25hell/test/test_basics.py | 40 +++++++++++++++++ lab/vt25hell/test/test_redirection.py | 65 +++++++++++++++++++++++++++ 16 files changed, 277 insertions(+) create mode 100644 lab/vt25hell/.clang-format create mode 100644 lab/vt25hell/.clang-tidy create mode 100644 lab/vt25hell/.gitignore create mode 100644 lab/vt25hell/.vscode/extensions.json create mode 100644 lab/vt25hell/.vscode/settings.json create mode 100644 lab/vt25hell/CMakeLists.txt rename lab/{vtsh => vt25hell}/README.md (100%) create mode 100644 lab/vt25hell/bin/CMakeLists.txt create mode 100644 lab/vt25hell/bin/main.c create mode 100644 lab/vt25hell/lib/CMakeLists.txt create mode 100644 lab/vt25hell/lib/vtsh.c create mode 100644 lab/vt25hell/lib/vtsh.h create mode 100644 lab/vt25hell/test/base_test.py create mode 100644 lab/vt25hell/test/shell.py create mode 100644 lab/vt25hell/test/test_basics.py create mode 100644 lab/vt25hell/test/test_redirection.py diff --git a/lab/vt25hell/.clang-format b/lab/vt25hell/.clang-format new file mode 100644 index 0000000..dd99110 --- /dev/null +++ b/lab/vt25hell/.clang-format @@ -0,0 +1,16 @@ +BasedOnStyle: Google + +AccessModifierOffset: -2 + +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false + +DerivePointerAlignment: false +BreakConstructorInitializers: BeforeComma +AlignAfterOpenBracket: BlockIndent + +BinPackArguments: false +BinPackParameters: false + +AlignArrayOfStructures: Right diff --git a/lab/vt25hell/.clang-tidy b/lab/vt25hell/.clang-tidy new file mode 100644 index 0000000..388d247 --- /dev/null +++ b/lab/vt25hell/.clang-tidy @@ -0,0 +1,17 @@ +Checks: | + -*, + bugprone-*, + cert-*, + clang-analyzer-*, + concurrency-*, + cppcoreguidelines-*, + google-*, + hicpp-*, + misc-*, + modernize-*, + performance-*, + portability-*, + -portability-avoid-pragma-once, + readability-* +WarningsAsErrors: '*' +FormatStyle: file diff --git a/lab/vt25hell/.gitignore b/lab/vt25hell/.gitignore new file mode 100644 index 0000000..fb90d94 --- /dev/null +++ b/lab/vt25hell/.gitignore @@ -0,0 +1,8 @@ +# CMake +build/ + +# Clangd +.cache/ + +# Python +__pycache__/ diff --git a/lab/vt25hell/.vscode/extensions.json b/lab/vt25hell/.vscode/extensions.json new file mode 100644 index 0000000..d6f2099 --- /dev/null +++ b/lab/vt25hell/.vscode/extensions.json @@ -0,0 +1,25 @@ + { + "recommendations": [ + "ms-vscode.cmake-tools", // CMake support (docs, runners) + + /** + * C/C++ extensions group alternative 1: + */ + "llvm-vs-code-extensions.vscode-clangd", // LSP + "vadimcn.vscode-lldb", // debugger extension for clang + /* or official lldb extension */ + // "llvm-vs-code-extensions.lldb-dap", + + /** + * C/C++ extensions group alternative 2: + */ + // "ms-vscode.cpptools", // C/C++ extension pack with debugger & LSP + + /** + * Optional extensions: + */ + // "usernamehw.errorlens", // For inlineerror displaying + // "ms-vscode-remote.remote-wsl", // For WSL support + // "ms-vscode-remote.remote-containers", // For devcontainer support + ] + } \ No newline at end of file diff --git a/lab/vt25hell/.vscode/settings.json b/lab/vt25hell/.vscode/settings.json new file mode 100644 index 0000000..db30e90 --- /dev/null +++ b/lab/vt25hell/.vscode/settings.json @@ -0,0 +1,7 @@ +{ + "clangd.arguments": [ + "-compile-commands-dir=./build", + "-header-insertion=never", + ], + "files.eol": "\n", +} diff --git a/lab/vt25hell/CMakeLists.txt b/lab/vt25hell/CMakeLists.txt new file mode 100644 index 0000000..1553d59 --- /dev/null +++ b/lab/vt25hell/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 3.12) + +project( + vtsh + VERSION 0.1 + DESCRIPTION "VT Shell" + LANGUAGES C +) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_subdirectory(bin) +add_subdirectory(lib) diff --git a/lab/vtsh/README.md b/lab/vt25hell/README.md similarity index 100% rename from lab/vtsh/README.md rename to lab/vt25hell/README.md diff --git a/lab/vt25hell/bin/CMakeLists.txt b/lab/vt25hell/bin/CMakeLists.txt new file mode 100644 index 0000000..7ef16bd --- /dev/null +++ b/lab/vt25hell/bin/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable( + vtsh + main.c +) + +target_include_directories( + vtsh + PUBLIC + . +) + +target_link_libraries( + vtsh + PRIVATE + libvtsh +) diff --git a/lab/vt25hell/bin/main.c b/lab/vt25hell/bin/main.c new file mode 100644 index 0000000..fd9b122 --- /dev/null +++ b/lab/vt25hell/bin/main.c @@ -0,0 +1,7 @@ +#include +#include + +int main() { + printf("%s", vtsh_prompt()); + printf("Hello, world!\n"); +} diff --git a/lab/vt25hell/lib/CMakeLists.txt b/lab/vt25hell/lib/CMakeLists.txt new file mode 100644 index 0000000..962968c --- /dev/null +++ b/lab/vt25hell/lib/CMakeLists.txt @@ -0,0 +1,11 @@ +add_library( + libvtsh + STATIC + vtsh.c +) + +target_include_directories( + libvtsh + PUBLIC + . +) diff --git a/lab/vt25hell/lib/vtsh.c b/lab/vt25hell/lib/vtsh.c new file mode 100644 index 0000000..8c0cb60 --- /dev/null +++ b/lab/vt25hell/lib/vtsh.c @@ -0,0 +1,5 @@ +#include "vtsh.h" + +const char* vtsh_prompt() { + return "vtsh> "; +} diff --git a/lab/vt25hell/lib/vtsh.h b/lab/vt25hell/lib/vtsh.h new file mode 100644 index 0000000..919515a --- /dev/null +++ b/lab/vt25hell/lib/vtsh.h @@ -0,0 +1,3 @@ +#pragma once + +const char* vtsh_prompt(); diff --git a/lab/vt25hell/test/base_test.py b/lab/vt25hell/test/base_test.py new file mode 100644 index 0000000..343c879 --- /dev/null +++ b/lab/vt25hell/test/base_test.py @@ -0,0 +1,26 @@ +import os +from typing import Optional +from unittest import TestCase + +from shell import Shell + + +class BaseShellTest(TestCase): + def setUp(self): + self.shell = Shell("../build/bin/vtsh") + self.test_files = set() + + def tearDown(self): + for file in self.test_files: + if os.path.exists(file): + os.remove(file) + + def add_test_file(self, filename: str): + self.test_files.add(filename) + + def execute(self, cmd: str, expected: Optional[str] = None): + status, stdout = self.shell.execute(cmd) + + self.assertEqual(status, 0) + if expected is not None: + self.assertEqual(stdout, expected) diff --git a/lab/vt25hell/test/shell.py b/lab/vt25hell/test/shell.py new file mode 100644 index 0000000..d5b40e5 --- /dev/null +++ b/lab/vt25hell/test/shell.py @@ -0,0 +1,18 @@ +import subprocess + + +class Shell: + def __init__(self, path: str): + self._path = path + + def execute(self, cmd: str): + shell = subprocess.Popen( + self._path, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + encoding="utf8", + ) + + stdout, _ = shell.communicate(cmd + "\n", timeout=2) + return shell.returncode, stdout.replace("vtsh> ", "").strip() diff --git a/lab/vt25hell/test/test_basics.py b/lab/vt25hell/test/test_basics.py new file mode 100644 index 0000000..06d79aa --- /dev/null +++ b/lab/vt25hell/test/test_basics.py @@ -0,0 +1,40 @@ +import os + +from base_test import BaseShellTest + + +class TestShellBasics(BaseShellTest): + def test_empty_command(self): + self.execute("", "") + + def test_simple_echo(self): + self.execute("echo hello", "hello") + self.execute(" echo hello world", "hello world") + + def test_multiple_commands(self): + self.execute("echo hello\necho world", "hello\nworld") + + def test_file_operations(self): + self.add_test_file("./foo") + + self.execute("touch ./foo", "") + self.assertTrue(os.path.exists("./foo")) + self.execute("rm ./foo", "") + self.assertFalse(os.path.exists("./foo")) + + def test_multiple_file_operations(self): + self.add_test_file("./bar") + + self.execute("touch ./bar\ncat ./bar\nwc -c bar\nrm ./bar", "0 bar") + self.assertFalse(os.path.exists("./bar")) + + def test_stdin_interaction(self): + self.execute("cat\nhello\nworld", "hello\nworld") + + def test_nested_shells(self): + self.execute("./shell\necho hi\n./shell\necho hello", "hi\nhello") + + def test_invalid_paths(self): + self.execute("cat /sys/proc/foo/bar", "") + self.execute("foobar", "Command not found") + diff --git a/lab/vt25hell/test/test_redirection.py b/lab/vt25hell/test/test_redirection.py new file mode 100644 index 0000000..cb14495 --- /dev/null +++ b/lab/vt25hell/test/test_redirection.py @@ -0,0 +1,65 @@ +import unittest + +from base_test import BaseShellTest + +REQUIRED_REDIRECTION_FUNCTIONALITY = False + +@unittest.skipIf(not REQUIRED_REDIRECTION_FUNCTIONALITY, + ("Redirection functionality is not required in the task. " + "This functionality is for an additional task.")) +class TestShellRedirection(BaseShellTest): + def test_simple_redirection(self): + self.add_test_file("aaa") + + self.execute("echo vt flood > aaa", "") + self.execute("cat /dev/null < aaa", "") + + def test_redirection_overwrite(self): + self.add_test_file("aaa") + + self.execute("echo c forever >aaa", "") + self.execute("cat < aaa", "c forever") + + def test_redirection_with_spaces(self): + self.add_test_file("aaa") + + self.execute("echo >aaa andrew tanenbaum", "") + self.execute(" aaa cat\nhello\nworld", "") + self.execute("< aaa cat", "hello\nworld") + + def test_complex_redirection(self): + self.add_test_file("aaa") + self.add_test_file("bbb") + + self.execute(">aaa head -c 250 < /dev/zero", "") + self.execute("wc > bbb -c aaa", "") + self.execute("< bbb cat", "250 aaa") + + def test_invalid_redirection_syntax(self): + self.execute("echo test foo bar>bbb", "test foo bar>bbb") + self.execute("echo testbbb", "testbbb") + + def test_combined_redirection(self): + self.add_test_file("lol") + self.add_test_file("wut") + + self.execute("echo >lolfoo > bar", "Syntax error") + self.execute("cat two hello", "Syntax error") + self.execute("cat >>hello", "Syntax error") + + def test_io_errors(self): + self.execute(" /sys/proc/foo/bar", "I/O error") + self.execute("cat < /foo/bar/baz", "I/O error") + self.execute("cat < baz > foo", "I/O error") From 21fd6728ab516f4d83f0dead06bf4e1490df6226 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:25:33 +0300 Subject: [PATCH 15/21] remove redundant questions --- lab/vtsh/GUIDE.md | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/GUIDE.md index 21b363a..3b568b6 100644 --- a/lab/vtsh/GUIDE.md +++ b/lab/vtsh/GUIDE.md @@ -380,28 +380,7 @@ shell продолжает работу. Настоящую fork bomb запус Будьте готовы объяснить различия между `fork`, `vfork`, `clone`, `exec`, `wait` и `pipe`, rlimit, даже если реализовывал только свой вариант. -## 10. Вопросы для самопроверки - -Ответьте на вопросы ниже после реализации обязательной части и выбранных -расширений. Они охватывают отдельные темы руководства и могут использоваться -для подготовки к защите. - -1. Почему после создания процесса одна ветвь вызывает `exec`, а другая — `wait`? -2. Что произойдёт, если `exec` завершится ошибкой и ребёнок продолжит цикл shell? -3. Почему после `vfork` набор допустимых действий ребёнка сильно ограничен? -4. Чем PID отличается от кода завершения? -5. Когда появляется zombie и кто обязан его собрать? -6. Почему фоновый процесс нельзя ожидать обычным блокирующим вызовом сразу после - запуска? -7. Как `&&` и `||` используют статус предыдущей команды? -8. Что именно наследует дочерний процесс от shell? -9. Почему `Ctrl-C` и `Ctrl-D` требуют разной обработки? -10. Почему незакрытый конец `pipe` может навсегда задержать EOF? -11. Почему счётчик задач внутри shell не останавливает fork bomb внутри дочерней - программы? -12. Какие функции допустимо вызывать из обработчика сигнала? - -## 11. Полезная документация +## 10. Полезная документация Перед реализацией прочитайте соответствующие страницы руководства `man`: @@ -430,7 +409,7 @@ shell продолжает работу. Настоящую fork bomb запус потомков. У `RLIMIT_NPROC` особая область учёта — реальный пользователь. Есть два параметра ограничений: **soft** и **hard limit** — уточните, в чем -разница и в каком случае следует какое устанавливать. При установке нового +разница и в каком случае следует какие устанавливать. При установке нового значения учитывайте исходные значения из `getrlimit()`. Проверяйте ошибки установки лимита, сам shell должен продолжить принимать From 4949d52311d8233cf6a4d00a8a5b0bf002972d7a Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:25:53 +0300 Subject: [PATCH 16/21] rename file with task --- lab/vtsh/{GUIDE.md => README.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lab/vtsh/{GUIDE.md => README.md} (100%) diff --git a/lab/vtsh/GUIDE.md b/lab/vtsh/README.md similarity index 100% rename from lab/vtsh/GUIDE.md rename to lab/vtsh/README.md From fd61bd4d4230a00f094dc64aff3ad87e37ae4aad Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:42:50 +0300 Subject: [PATCH 17/21] enhance description, add notes --- lab/vtsh/README.md | 52 ++++++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/lab/vtsh/README.md b/lab/vtsh/README.md index 3b568b6..8cd9254 100644 --- a/lab/vtsh/README.md +++ b/lab/vtsh/README.md @@ -9,7 +9,10 @@ | `&&` / `\|\|` / `;` | * Расширение с логическими операторами для цепочки вызовов | | NPROC / NOFILE / FSIZE / STACK / AS | * Расширение с возможностью установки ограничений на ресурсы в подпроцессе | -\* — Расширения опциональные, задаются преподавателем. +\* — Расширения опциональные, задаются преподавателем в составе полного +варианта. + +Для каждого расширения в 6 этапе задания присутствует описание. ## 1. Цель работы @@ -218,7 +221,8 @@ sleep 2 & Результат, как обычное служебное сообщениеб печатается в `stderr`, время выводите в мс, с точностью до сотых. -Автоматический тест **не** должен сравнивать время на точное равенство. +Сравните время отображаемое вашим shell и утилитой `time` запущенной внутри +него. ### 6.2. Конвейеры @@ -240,6 +244,9 @@ printf hello | wc -c процессов foreground-конвейера; статусом конвейера обычно считают статус его последнего звена. +Подумайте, как с помощью конвейеров теперь можно сделать перенаправление потока +в файл (`cat kek.txt lol.txt > idk.txt`). Проверьте своё решение. + ### 6.3. Фоновое выполнение Для команды с `&` родитель не должен выполнять блокирующее ожидание. Он @@ -331,7 +338,7 @@ shell продолжает работу. Настоящую fork bomb запус `pytest`. Не запускайте проверяемые команды через `shell=True`, т.к. вам нужно протестировать свой vtsh, а не системную оболочку. -Минимальный набор сценариев: +Минимальный набор сценариев (с учетом вариантов): 1. пустая строка и EOF; 2. вызов программы по абсолютному пути; @@ -350,12 +357,6 @@ shell продолжает работу. Настоящую fork bomb запус директории; 15. ограничения на ресурсы работают. -Для проверки поведения терминала может потребоваться псевдотерминал (PTY). -Закрытие входного канала моделирует `Ctrl-D`. - -Каждый тест должен создавать собственное окружение и удалять временные файлы и -процессы после завершения, в том числе после ошибки или timeout. - ## 8. Рекомендуемый порядок работы Не пытайтесь реализовать всё сразу. После каждого этапа сохраните работающую @@ -363,22 +364,27 @@ shell продолжает работу. Настоящую fork bomb запус 1. REPL и EOF; 2. parser одной команды; -3. запуск по явному пути; -4. поиск через `PATH` и окружение; -5. ожидание и статусы; -6. `;` / `&&` / `||`; -9. конвейер передачи данных `|` -8. `&` и сбор фоновых процессов; -9. ограничение ресурсов; -10. тесты и проверка утечек ресурсов. - -Для диагностики полезны `strace`, просмотр дерева процессов и инструменты поиска -ошибок памяти ("санитайзеры"), отладчик GDB, `atop` в режиме записи. На защите -будьте готовы по трассировке объяснить, какой процесс вызвал каждый системный -вызов. +3. запуск по абсолютному пути; +4. ожидание и статусы; +5. `;` / `&&` / `||`; +6. конвейер передачи данных `|` +7. `&` и сбор фоновых процессов; +8. ограничение ресурсов; +9. дополнительные тесты. +10. Отчет с выводами по работе. + +Для диагностики полезны +* `strace`, +* просмотр дерева процессов, `atop` в режиме записи, +* отладчик GDB, +* инструменты поиска ошибок памяти ("санитайзеры"). Будьте готовы объяснить различия между `fork`, `vfork`, `clone`, `exec`, -`wait` и `pipe`, rlimit, даже если реализовывал только свой вариант. +`wait` и `pipe`, rlimit, даже если реализовывали только свой вариант. + +> [!TIP] +> Подумайте, почему shell такой какой есть и как можно иначе. + ## 10. Полезная документация From 69fe95bac51c2bb33746369dadd8f411542c8723 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:47:41 +0300 Subject: [PATCH 18/21] upd description of vt25hell --- lab/vt25hell/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lab/vt25hell/CMakeLists.txt b/lab/vt25hell/CMakeLists.txt index 1553d59..dc4ebca 100644 --- a/lab/vt25hell/CMakeLists.txt +++ b/lab/vt25hell/CMakeLists.txt @@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.12) project( vtsh VERSION 0.1 - DESCRIPTION "VT Shell" + DESCRIPTION "Good Old VT Shell" LANGUAGES C ) From 5a1c45690285ec6cb3baafdaf497f372c7ced3d9 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:54:41 +0300 Subject: [PATCH 19/21] update project layout --- lab/vtsh/CMakeLists.txt | 3 +-- lab/vtsh/README.md | 16 ++++++++++++++++ lab/vtsh/bin/CMakeLists.txt | 16 ---------------- lab/vtsh/lib/CMakeLists.txt | 11 ----------- lab/vtsh/lib/vtsh.h | 3 --- lab/vtsh/src/CMakeLists.txt | 27 +++++++++++++++++++++++++++ lab/vtsh/{bin => src}/main.c | 5 +++-- lab/vtsh/{lib => src}/vtsh.c | 2 +- lab/vtsh/src/vtsh.h | 3 +++ lab/vtsh/test/base_test.py | 6 +++++- 10 files changed, 56 insertions(+), 36 deletions(-) delete mode 100644 lab/vtsh/bin/CMakeLists.txt delete mode 100644 lab/vtsh/lib/CMakeLists.txt delete mode 100644 lab/vtsh/lib/vtsh.h create mode 100644 lab/vtsh/src/CMakeLists.txt rename lab/vtsh/{bin => src}/main.c (69%) rename lab/vtsh/{lib => src}/vtsh.c (55%) create mode 100644 lab/vtsh/src/vtsh.h diff --git a/lab/vtsh/CMakeLists.txt b/lab/vtsh/CMakeLists.txt index 1553d59..522d358 100644 --- a/lab/vtsh/CMakeLists.txt +++ b/lab/vtsh/CMakeLists.txt @@ -9,5 +9,4 @@ project( set(CMAKE_EXPORT_COMPILE_COMMANDS ON) -add_subdirectory(bin) -add_subdirectory(lib) +add_subdirectory(src) diff --git a/lab/vtsh/README.md b/lab/vtsh/README.md index 8cd9254..aec4076 100644 --- a/lab/vtsh/README.md +++ b/lab/vtsh/README.md @@ -14,6 +14,22 @@ Для каждого расширения в 6 этапе задания присутствует описание. +## Сборка и запуск + +Из каталога `lab/vtsh`: + +```sh +cmake -S . -B build +cmake --build build +./build/bin/vtsh +``` + +Собственный набор Python-тестов использует этот же путь к исполняемому файлу: + +```sh +python3 -m unittest discover -s test +``` + ## 1. Цель работы В этой лабораторной работе необходимо разработать **простую командную оболочку**. diff --git a/lab/vtsh/bin/CMakeLists.txt b/lab/vtsh/bin/CMakeLists.txt deleted file mode 100644 index 7ef16bd..0000000 --- a/lab/vtsh/bin/CMakeLists.txt +++ /dev/null @@ -1,16 +0,0 @@ -add_executable( - vtsh - main.c -) - -target_include_directories( - vtsh - PUBLIC - . -) - -target_link_libraries( - vtsh - PRIVATE - libvtsh -) diff --git a/lab/vtsh/lib/CMakeLists.txt b/lab/vtsh/lib/CMakeLists.txt deleted file mode 100644 index 962968c..0000000 --- a/lab/vtsh/lib/CMakeLists.txt +++ /dev/null @@ -1,11 +0,0 @@ -add_library( - libvtsh - STATIC - vtsh.c -) - -target_include_directories( - libvtsh - PUBLIC - . -) diff --git a/lab/vtsh/lib/vtsh.h b/lab/vtsh/lib/vtsh.h deleted file mode 100644 index 919515a..0000000 --- a/lab/vtsh/lib/vtsh.h +++ /dev/null @@ -1,3 +0,0 @@ -#pragma once - -const char* vtsh_prompt(); diff --git a/lab/vtsh/src/CMakeLists.txt b/lab/vtsh/src/CMakeLists.txt new file mode 100644 index 0000000..7b4c71e --- /dev/null +++ b/lab/vtsh/src/CMakeLists.txt @@ -0,0 +1,27 @@ +add_executable( + vtsh + main.c + vtsh.c +) + +set_target_properties(vtsh PROPERTIES + C_STANDARD 11 + C_STANDARD_REQUIRED YES + C_EXTENSIONS NO + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) + +target_include_directories( + vtsh + PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}" +) + +if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") + target_compile_options(vtsh PRIVATE + -Wall + -Wextra + -Wpedantic + -Wstrict-prototypes + ) +endif() diff --git a/lab/vtsh/bin/main.c b/lab/vtsh/src/main.c similarity index 69% rename from lab/vtsh/bin/main.c rename to lab/vtsh/src/main.c index fd9b122..a33633c 100644 --- a/lab/vtsh/bin/main.c +++ b/lab/vtsh/src/main.c @@ -1,7 +1,8 @@ #include -#include -int main() { +#include "vtsh.h" + +int main(void) { printf("%s", vtsh_prompt()); printf("Hello, world!\n"); } diff --git a/lab/vtsh/lib/vtsh.c b/lab/vtsh/src/vtsh.c similarity index 55% rename from lab/vtsh/lib/vtsh.c rename to lab/vtsh/src/vtsh.c index 8c0cb60..1d75ad9 100644 --- a/lab/vtsh/lib/vtsh.c +++ b/lab/vtsh/src/vtsh.c @@ -1,5 +1,5 @@ #include "vtsh.h" -const char* vtsh_prompt() { +const char* vtsh_prompt(void) { return "vtsh> "; } diff --git a/lab/vtsh/src/vtsh.h b/lab/vtsh/src/vtsh.h new file mode 100644 index 0000000..cf6592b --- /dev/null +++ b/lab/vtsh/src/vtsh.h @@ -0,0 +1,3 @@ +#pragma once + +const char* vtsh_prompt(void); diff --git a/lab/vtsh/test/base_test.py b/lab/vtsh/test/base_test.py index 343c879..6f63b8f 100644 --- a/lab/vtsh/test/base_test.py +++ b/lab/vtsh/test/base_test.py @@ -1,13 +1,17 @@ import os +from pathlib import Path from typing import Optional from unittest import TestCase from shell import Shell +PROJECT_ROOT = Path(__file__).resolve().parent.parent +VTSH_EXECUTABLE = PROJECT_ROOT / "build" / "bin" / "vtsh" + class BaseShellTest(TestCase): def setUp(self): - self.shell = Shell("../build/bin/vtsh") + self.shell = Shell(str(VTSH_EXECUTABLE)) self.test_files = set() def tearDown(self): From 9d8b49efd5774b4cb0be28f2f38fb36cfb488380 Mon Sep 17 00:00:00 2001 From: e1turin Date: Thu, 1 Oct 2026 02:57:21 +0300 Subject: [PATCH 20/21] update github workflow for legacy vtsh --- .github/workflows/{vtsh.yml => vt25hell.yml} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename .github/workflows/{vtsh.yml => vt25hell.yml} (88%) diff --git a/.github/workflows/vtsh.yml b/.github/workflows/vt25hell.yml similarity index 88% rename from .github/workflows/vtsh.yml rename to .github/workflows/vt25hell.yml index 2fbfefe..5cd04e0 100644 --- a/.github/workflows/vtsh.yml +++ b/.github/workflows/vt25hell.yml @@ -1,4 +1,4 @@ -name: Test VT Shell +name: Test VT25hell on: push: @@ -6,7 +6,7 @@ on: - main pull_request: paths: - - 'lab/vtsh/**' + - 'lab/vt25hell/**' jobs: build: @@ -20,7 +20,7 @@ jobs: image: silkeh/clang:latest defaults: run: - working-directory: ./lab/vtsh + working-directory: ./lab/vt25hell steps: - name: Checkout uses: actions/checkout@v4 @@ -45,5 +45,5 @@ jobs: clang-tidy -p ./build {} \; - name: Test - working-directory: ./lab/vtsh/test + working-directory: ./lab/vt25hell/test run: python3 -m unittest discover From 03a00f9da2050c485a736432d502d3503d4c1476 Mon Sep 17 00:00:00 2001 From: e1turin Date: Fri, 2 Oct 2026 01:35:31 +0300 Subject: [PATCH 21/21] remove vfork from variants, remove timer extension, fix grammar issues --- lab/vtsh/README.md | 97 ++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 59 deletions(-) diff --git a/lab/vtsh/README.md b/lab/vtsh/README.md index aec4076..e4f2880 100644 --- a/lab/vtsh/README.md +++ b/lab/vtsh/README.md @@ -2,8 +2,7 @@ | Вариант | Описание | | ------- | -------- | -| fork/vfork/clone | Способ создания подпроцесса | -| timer | * Расширение для замера времени выполнения команды | +| fork/clone | Способ создания подпроцесса | | pipe | * Расширение для перенаправления ввода-вывода | | bg | * Расширение для запуска фоновых процессов | | `&&` / `\|\|` / `;` | * Расширение с логическими операторами для цепочки вызовов | @@ -12,7 +11,7 @@ \* — Расширения опциональные, задаются преподавателем в составе полного варианта. -Для каждого расширения в 6 этапе задания присутствует описание. +Для каждого расширения в разделе 6 приведено описание. ## Сборка и запуск @@ -65,15 +64,14 @@ POSIX Shell. Главное — понять: 3. запускать внешнюю программу по абсолютному пути; 4. поддерживать команды `cd` и `exit`; 5. передавать новой программе аргументы и текущее окружение оболочки; -6. корректно ожидать запускаемые подпроцессы и получать их статус; +6. корректно ожидать завершения запущенных подпроцессов и получать их статус; Дополнительные функции (по варианту): -1. замерять время работы выполнения комманд; -2. поддерживать конвейеризацию через `|`; -3. запускать простую команду в фоне с помощью `&`; -4. поддерживать логические операторы по вариантам; -5. ограничение ресурсов запускаемых программ по вариантам. +1. поддерживать конвейеры с помощью `|`; +2. запускать простую команду в фоновом режиме с помощью `&`; +3. поддерживать логические операторы в соответствии с вариантом; +4. ограничивать ресурсы запускаемых программ в соответствии с вариантом. ## 3. Минимальный синтаксис @@ -172,36 +170,30 @@ sleep 2 & программой через вызов системной функции из семейства `exec`; 3. при ошибке `exec` дочерний процесс печатает диагностическое сообщение и немедленно завершается; -4. родитель сохраняет PID и в стаёт в ожидаии дочернего процесса с помощью - функции `wait`, если команда не фоновая; +4. родитель сохраняет PID и ожидает завершения дочернего процесса с помощью + функции `wait`, если команда не выполняется в фоновом режиме; 5. родитель преобразует результат функции ожидания в код выполнения команды. Запрещено вызывать внешнюю программу через `system`, `popen` или запуск `/bin/sh -c` -— задача разрабатать свой shell самостоятельно, а не взять готовый. +— задача состоит в самостоятельной разработке shell, а не в использовании готового. Рекомендуемые соглашения об ошибках: - программа не найдена — статус `127`; - программа найдена, но не может быть запущена — статус `126`; - ошибка создания процесса остаётся ошибкой текущей команды, но не превращается - в ошибку shell, как приложения, т.е. его работа не завершается, shell - продолжает принимать новые комады. + в ошибку shell как приложения, то есть его работа не завершается и shell + продолжает принимать новые команды. -Перед реализацией изучите страницы `man`: +Перед реализацией изучите соответствующие страницы `man`: - [`fork(2)`](https://man7.org/linux/man-pages/man2/fork.2.html), - [`vfork(2)`](https://man7.org/linux/man-pages/man2/vfork.2.html) или - [`clone(2)`](https://man7.org/linux/man-pages/man2/clone.2.html) — создание - процесса по варианту и ограничения выбранного механизма; + [`vfork(2)`](https://man7.org/linux/man-pages/man2/vfork.2.html), + [`clone(2)`](https://man7.org/linux/man-pages/man2/clone.2.html); - [`exec(3)`](https://man7.org/linux/man-pages/man3/exec.3.html) и - [`execve(2)`](https://man7.org/linux/man-pages/man2/execve.2.html) — замена образа - процесса, поиск через `PATH`, передача аргументов и окружения; -- [`wait(2)` / `waitpid(2)`](https://man7.org/linux/man-pages/man2/wait.2.html) — - ожидание конкретного ребёнка, неблокирующее ожидание и макросы разбора статуса; -- [`_exit(2)`](https://man7.org/linux/man-pages/man2/_exit.2.html) — завершение - дочернего процесса при ошибке запуска без повторного сброса буферов stdio. - -Например, локально страницу можно открыть командой `man 2 fork`. + [`execve(2)`](https://man7.org/linux/man-pages/man2/execve.2.html); +- [`wait(2)` / `waitpid(2)`](https://man7.org/linux/man-pages/man2/wait.2.html); +- [`_exit(2)`](https://man7.org/linux/man-pages/man2/_exit.2.html). ### Шаг 4. Пробросьте окружение @@ -227,20 +219,7 @@ sleep 2 & ## 6. Расширения -### 6.1. Измерение времени - -Используйте монотонные часы, не зависящие от изменения системного календаря. -Зафиксируйте время непосредственно перед созданием процесса и после получения -его окончательного статуса. Измеряется вся логическая цепочка. Для фоновой -команды результат можно вывести в сразу же после её запуска. - -Результат, как обычное служебное сообщениеб печатается в `stderr`, время -выводите в мс, с точностью до сотых. - -Сравните время отображаемое вашим shell и утилитой `time` запущенной внутри -него. - -### 6.2. Конвейеры +### 6.1. Конвейеры Конвейер связывает стандартный вывод одной программы со стандартным вводом следующей: @@ -263,7 +242,7 @@ printf hello | wc -c Подумайте, как с помощью конвейеров теперь можно сделать перенаправление потока в файл (`cat kek.txt lol.txt > idk.txt`). Проверьте своё решение. -### 6.3. Фоновое выполнение +### 6.2. Фоновое выполнение Для команды с `&` родитель не должен выполнять блокирующее ожидание. Он сохраняет информацию о задаче и сразу показывает следующее приглашение к вводу. @@ -284,7 +263,7 @@ printf hello | wc -c > процессов, поэтому проверка должна продолжаться, пока доступен хотя бы один > изменивший состояние дочерний процесс. -### 6.4. Логические операторы +### 6.3. Логические операторы Сначала parser должен отделить команды от операторов и обнаружить синтаксические ошибки. @@ -297,7 +276,7 @@ Executor хранит статус последней действительно - после `||` — только после ошибки. Порядок выполнения команд ленивый: если первый операнд даёт понять, какой будет -итоговы результат, то второй операнд вычислять не трубется, т.е. вторая команда +итоговый результат, то второй операнд вычислять не требуется, то есть вторая команда не выполняется и не создаёт процесс, не меняет статус последней выполненной команды. @@ -317,7 +296,7 @@ false ; echo no ; echo yes false || false || echo recovered ``` -### 6.5. Вспомогательные ограничения ресурсов +### 6.4. Вспомогательные ограничения ресурсов Реализуйте **ограничение ресурсов запускаемых программ** по варианту расширения, назначенному преподавателем: @@ -338,7 +317,7 @@ false || false || echo recovered Для тестирования запускайте небольшую вспомогательную программу сначала с достаточным, затем с уменьшенным лимитом. Проверяйте реальный отказ операции или завершение по сигналу, а не только успех `setrlimit()`, и убедитесь, что -shell продолжает работу. Настоящую fork bomb запускать нестоит. +shell продолжает работу. Настоящую fork bomb запускать не стоит. Особенности Linux, безопасная установка лимитов и подробности проверок приведены в [приложении A](#приложение-a-ограничения-ресурсов-в-linux). @@ -383,23 +362,24 @@ shell продолжает работу. Настоящую fork bomb запус 3. запуск по абсолютному пути; 4. ожидание и статусы; 5. `;` / `&&` / `||`; -6. конвейер передачи данных `|` +6. конвейер передачи данных `|`; 7. `&` и сбор фоновых процессов; 8. ограничение ресурсов; -9. дополнительные тесты. -10. Отчет с выводами по работе. +9. дополнительные тесты; +10. отчёт с выводами по работе. + +Для диагностики полезны: -Для диагностики полезны -* `strace`, -* просмотр дерева процессов, `atop` в режиме записи, -* отладчик GDB, -* инструменты поиска ошибок памяти ("санитайзеры"). +- `strace`; +- просмотр дерева процессов и `atop` в режиме записи; +- отладчик GDB; +- инструменты поиска ошибок памяти («санитайзеры»). Будьте готовы объяснить различия между `fork`, `vfork`, `clone`, `exec`, -`wait` и `pipe`, rlimit, даже если реализовывали только свой вариант. +`wait`, `pipe` и `rlimit`, даже если реализовывали только свой вариант. > [!TIP] -> Подумайте, почему shell такой какой есть и как можно иначе. +> Подумайте, почему shell устроен именно так и как его можно было бы устроить иначе. ## 10. Полезная документация @@ -413,7 +393,6 @@ shell продолжает работу. Настоящую fork bomb запус - `signal(7)`, `sigaction(2)`; - `setpgid(2)`, `tcsetpgrp(3)`; - `pipe(2)`, `dup2(2)` — для конвейеров; -- `clock_gettime(2)` — для измерения времени; - `getrlimit(2)`, `setrlimit(2)` — для ограничения ресурсов; - `prlimit(1)`, `proc_pid_limits(5)` — для проверки лимитов в Linux. @@ -432,10 +411,10 @@ shell продолжает работу. Настоящую fork bomb запус Есть два параметра ограничений: **soft** и **hard limit** — уточните, в чем разница и в каком случае следует какие устанавливать. При установке нового -значения учитывайте исходные значения из `getrlimit()`. +значения учитывайте исходные значения, полученные с помощью `getrlimit()`. -Проверяйте ошибки установки лимита, сам shell должен продолжить принимать -команды и не запускать команду без заданного ограничения. +Проверяйте ошибки установки лимита. Shell должен продолжить принимать +команды и не запускать программу без заданного ограничения. > [!WARNING] > После `vfork()` нельзя произвольно вызывать функции, в том числе `setrlimit()`,