Брент Янски и Кэ'Незе Кервин

Многое происходит за кулисами такого, казалось бы, простого процесса. Давайте пройдемся по нему шаг за шагом!

Получение ввода

Наша оболочка ничего не делает, пока не получит какие-либо аргументы. Откуда у него эти аргументы? Из стандартного ввода. Для обычного пользователя стандартным вводом традиционно является ваша клавиатура, но технически стандартным вводом может быть что угодно, что передает поток символов для интерпретации оболочкой. Например, если ваш стандартный ввод — это просто файл со списками команд, ваша оболочка будет работать почти так же, как если бы вы просто вводили эти команды одну за другой на клавиатуре (нажимая «ввод» после каждой). Вы можете увидеть это действие, если используете команду pipe в Linux. Если вы передаете поток текста (текстовый файл) в исполняемый файл оболочки, команда pipe отправляет эти данные в качестве стандартного ввода в исполняемый файл оболочки. Вы можете думать о стандартном вводе как о туннеле или канале ввода символов в исполняемый файл.

Большинство из нас понимает нашу оболочку через знакомую подсказку, которая ждет, пока мы что-нибудь напечатаем. Наша оболочка печатает это приглашение, чтобы сообщить нам, что ожидает ввода. Следующее место, следующее сразу за этим печатным приглашением, — это системный вызов ядра Linux, который ожидает ввода со стандартного ввода. Вот почему в нашем примере с каналом вы никогда не видите подсказку… он мгновенно получает символы!

Обработка ввода

Итак, теперь у нас есть системный вызов, ожидающий, пока мы что-нибудь напечатаем. В нашем случае мы, конечно же, наберем ls -l. Теперь нажимаем ENTER. Наша оболочка берет пару наших персонажей и записывает их в буфер, выделенный в куче памяти (непосредственно перед отображением нашего приглашения) размера по умолчанию. Этот буфер представляет собой просто строку символов, точно такую ​​же, как и любая другая, обычно используемая в программировании на C. В нашем случае аргументы нашей оболочки всегда разделяются пробелами, а наши строки (почти) всегда заканчиваются символом новой строки (кнопка ENTER).

Теперь оболочка просматривает строку, чтобы увидеть, сколько передано аргументов, зная, что пробелы всегда будут разделять наши аргументы. Теперь, когда оболочка знает, сколько аргументов было передано, она может построить массив в памяти кучи для хранения строк каждого из этих изолированных аргументов без пробелов.

Следующий процесс заключается в том, что оболочка проходит через нашу строку буфера, пока не найдет пробел (или конец), а затем помещает предыдущие символы в элемент массива. Когда он находит пробел, он передает следующий аргумент в следующий элемент массива и так далее. Наша оболочка в основном будет иметь дело только с нашим первым аргументом, так как это команда или файл для выполнения, остальные аргументы интерпретируются самим исполняемым файлом (ls сообщит нашей оболочке, какой исполняемый файл использовать … тогда -l будет передана или интерпретирована самой ls).

Интерпретация аргументов

Теперь у нас есть аккуратный массив, содержащий каждый аргумент, введенный пользователем в командной строке. Вот тут становится интересно. Первое, что ищет оболочка, это если пользователь ввел встроенную команду. Это действие, которое оболочка фактически выполняет внутри, а не просто выполняет другую программу в файловой системе. Эти команды идентифицируются путем сравнения строки символов в первом аргументе (первом элементе массива) с точной строкой символов, соответствующей каждому встроенному аргументу. Например, он проверит, набрал ли пользователь именно «выход», сравнивая аргумент со строкой, состоящей из… «выход».

Если какое-либо из этих сравнений совпадает, оболочка запустит соответствующий процесс, освободит всю выделенную память и вернется к «верху» программы, чтобы получить аргументы из стандартного ввода.

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

Проверка ПУТИ

Теперь есть еще одна вещь, которую нужно попробовать. В ОС на базе Unix обычно указывается PATH для оболочки для поиска исполняемых файлов. Этот ПУТЬ находится в СРЕДЕ, сохраненном в ядре. Оболочка обращается к этой среде через внешнюю переменную (массив строк). Одна из этих строк — PATH. Как это находит? Просматривая первые 5 символов каждой строки и ища: PATH=. Теперь он нашел правильную строку. Чтобы использовать эту строку, нам нужно разметить ее и поместить в массив, точно так же, как наши аргументы командной строки (к счастью, мы знаем, что наш PATH всегда разделяется двоеточиями).

Итак, теперь, когда у нас есть массив PATH, нам нужно проверить наш первый аргумент в каждом пути. Как мы это делаем? Добавляя аргумент к каждому изолированному PATH, хранящемуся в массиве… например, если наш первый аргумент ls, он добавит ls к пути типа /bin (/bin/ls),/usr/bin (usr/bin/ls) и т. д. Каждый раз, когда он добавляет аргумент, он будет выполнять те же исполняемые тесты, которые были описаны ранее.

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

Разветвление, выполнение и ожидание

Теперь давайте вернемся к успешному первому аргументу, будь то полное имя пути или исполняемый файл, найденный в пути. Как только мы находим хороший исполняемый файл, оболочка «разветвляется» перед выполнением файла. Это означает, что оболочка создает дублирующую копию всех своих текущих процессов для одновременного запуска. Теперь в памяти существует его зеркало. Почему? Потому что мы будем использовать этот форк для запуска исполняемого файла «внутри» него. Это имеет смысл, потому что в случае с нашей оболочкой мы хотим запускать программы «внутри» ее, а затем сделать так, чтобы она была мгновенно доступна, когда мы закончим с ними.

Итак, наша оболочка разветвляется, а затем отправляет несколько вещей в исполняемый процесс. Первое, что он отправляет, это полный путь к исполняемому файлу. Второй — это полный массив аргументов командной строки для интерпретации исполняемым файлом. Наконец, это массив среды, который исполняемый файл может использовать для различных целей (например, как правильно отображать вывод в вашей системе). Исполняемый файл теперь запускается внутри нашего форка.

Что происходит с нашим «родительским» процессом оболочки? Он просто ждет окончания нашего форка. Скажем, мы открываем текстовый редактор с исполняемым файлом. Когда мы закроем наш текстовый редактор, наша вилка завершится, потому что, хотя вилка является дублирующим процессом, сам код имеет разные условия для процесса вилки по сравнению с родительским процессом. Разветвленный процесс просто завершается сразу после закрытия исполняемого файла. Наш родительский процесс все это время ждал в фоновом режиме, пока наша вилка не сигнализирует о том, что она закрыта.

Зачем создавать два процесса, работающих одновременно? Итак, оболочка — это то, как мы взаимодействуем с нашим ядром. Если бы он не работал в фоновом режиме, нам пришлось бы находить его и запускать каждый раз, когда мы запускаем ЛЮБОЙ исполняемый файл, каким бы незначительным он ни был. Это было бы не очень удобно для пользователя и не особенно эффективно.

Вывод

Оболочка - уникальная программа. По большей части он просто работает в бесконечном цикле: выполнение описанных ниже шагов, проверка ошибок, освобождение памяти и запуск заново с запросом нового ввода… снова… и снова (пока пользователь не выйдет с помощью встроенной команды «выход» , или нажав ctrl-d). В «неинтерактивном» режиме, когда наша оболочка читает со стандартного ввода, который не является клавиатурой… она будет продолжать цикл, обрабатывая каждую строку ввода (строки, заканчивающиеся символом новой строки (кнопка ввода или достигающие конца файла). ).После того, как все строки введены и обработаны, оболочка