Недавно я завершил проект по созданию реализации оболочки с полным пониманием процесса, который легко перенести на любого, кто хочет узнать, как работает реальная оболочка. Вы должны иметь возможность получать ввод от пользователя в каком-то цикле, который постоянно ожидает пользователя, анализировать текст на наличие токенов и выполнять команды, ища их в пути. Фундаментальная идея оболочки заключается в получении текста от пользователя и объединении его с системными вызовами для вывода вывода на терминал.

Получение ввода от пользователя и сохранение его в буфере

Библиотека C Standard IO содержит функцию getline, невероятно полезную для этой задачи. Он ожидает, пока пользователь введет текст, и сохраняет текст в буфере, который он размещает в куче. Эта функция продолжает считывать введенные пользователем данные до тех пор, пока пользователь не нажмет кнопку enter и не добавит в буфер новую строку. Этот буфер завершается нулевым байтом.

Расчет количества токенов в буфере

Ваш буфер может содержать любое количество табуляции, пробелов и других разделителей по мере того, как пользователь вводит текст в терминал. В нашей реализации оболочки мы определили наши разделители, включающие пробелы, табуляции и символы новой строки. Вы должны принять наихудший сценарий, когда в вашем буфере может существовать любое количество разделителей. Разделители могут стоять до и после любого слова в буфере, что усложняет задачу. ls -lбез пробелов эквивалентно ls [tab] [tab] [space] -l [табуляция] [пробел].

strspn и strpbrk решают проблему подсчета токенов по двум причинам. Во-первых, strspn вернет длину исходной строки, содержащей любые разделители. Если в вашем буфере есть начальные разделители, strspn — отличный способ подсчитать количество байтов до первого не разделителя. Это приведет вас к первому разделителю без разделителя, если у вас есть ведущие разделители. После вызова strspn вызовите strpbrk, чтобы найти следующий разделитель и увеличить количество токенов. Эта часть процесса включает в себя вызовы strspn и strpbrk до тех пор, пока строка не укажет на null. Каждый вызов strspn возвращает количество байтов до первого не разделителя, что позволяет вам сместить указатель буфера в эту точку. Это начало маркера, и каждый вызов strpbrk возвращает указатель на первое вхождение разделителя. Этот процесс включает в себя повторные вызовы strspn и strpbrk для подсчета количества токенов в буфере. Вы в основном прыгаете от токена к токену, используя эти две функции.

Извлечение токенов из буфера

Теперь, когда весь текст введен в ваш буфер и подсчитано количество токенов, мы собираемся извлечь эти токены и сохранить их в линейной структуре данных. Строки — это просто указатели char *, поэтому все эти токены будут храниться в массиве, содержащем указатели char *. strtok проанализирует буфер, заменит разделители нулевыми байтами и вернет токен с нулевым завершением. Мы храним эти токены в массиве, содержащем каждый токен и указатель NULL для завершения массива.

Поиск исполняемых файлов в системном пути

Предполагается, что первым токеном каждого входа в систему является команда. Записи в оболочке начинаются с имени исполняемой команды, за которой следуют любые флаги, доступные для команды, и заканчиваются возможными файлами или каталогами, с которыми будет действовать команда. Чтобы запустить ls без указания /bin/ls, мы должны извлечь каталоги из PATH.

Каждая программа в Linux наследует среду. Но что такое окружающая среда? Среда содержит массив строк, каждая из которых содержит имя переменной и связанное с ней значение. Каждая запись в среде имеет следующий базовый формат: «ПЕРЕМЕННАЯ=ЗНАЧЕНИЕ». Путь — это одна из таких переменных среды, которая содержит список каталогов, разделенных двоеточиями.

Если вы указываете абсолютный путь к исполняемому файлу, вам не нужно беспокоиться о поиске пути, потому что вы уже сообщаете оболочке его местоположение. Но что, если просто ввести ls? Обзор процесса описывается следующим образом:

  1. Создайте массив, где каждый член массива представляет собой один каталог, и завершите этот массив нулем.
  2. Объедините команду с каталогом
  3. Используйте системный вызов access, чтобы узнать, существует ли файл и есть ли у него разрешения на ЧТЕНИЕ/ЗАПИСЬ.
  4. Если вышеуказанное проходит успешно, продолжайте читать раздел выполнения этой статьи.

Выполнение команд

Возвращаясь к нашему массиву токенов, этот массив токенов будет передан в execve для выполнения команды. Маркер имени файла и массив маркеров передаются в execve для выполнения команды. Каждому вызову execve должен предшествовать fork для запуска исполняемого файла в отдельном процессе. Это важно, потому что вызывающая программа перезаписывается исполняемой программой. Разветвление процесса и указание вызывающему процессу ожидать дочерний процесс идеально подходят для запуска исполняемых файлов. Обзор процесса:

  1. Создайте дочерний процесс с помощью fork()
  2. Вызов execve внутри дочернего процесса
  3. Возврат к вызывающему процессу
  4. Продолжайте ждать ввода в оболочку, чтобы продолжить цикл

На этом обзор пути текстовой команды от ввода терминала до потока stdout завершен.

Для реальной реализации оболочки sh просмотрите исходный код:



Вы получите более глубокое понимание материала, комбинируя его с исходным кодом из репозитория git.

Этот проект был нашим совместным предприятием с Полом Лейвом.

АВТОРЫ:

Пол Лейв ([email protected])

Хемант Хир ([email protected])