Я часто набираю такие вещи, как ls и cat [filename], чтобы понять, где я нахожусь и что делаю, но пока я не закодировал свою собственную оболочку, я не переставал думать о том, как компьютер находит и выполняет эти команды. В этом посте я расскажу, что и где находятся команды, и как ваш компьютер их выполняет. Встроенные команды — это особый случай, который я не буду рассматривать.

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

Когда мы вводим ./ls, мы получаем сообщение об ошибке, поэтому очевидно, что ls не находится в текущем каталоге.

Это связано с тем, что большинство пользовательских команд, таких как ls, находятся в каталоге /bin. Если мы перейдем к /bin и введем ./ls, команда сработает.

Так что же делает встроенные команды особенными? Почему отсутствие ./ позволяет нам запускать их?

Ответ кроется в переменной окружения PATH. Переменная PATH представляет собой строку каталогов, разделенных двоеточием. Когда вы вводите что-то в терминал, оболочка интерпретирует это как команду и ищет эту программу, проверяя каждую директорию в вашей переменной PATH одну за другой, пока не найдет программу, чье имя совпадает с тем, что вы ввели. Чтобы увидеть свой путь переменная, введите $PATH или env, чтобы просмотреть все переменные окружения. Если мы вручную ищем программу, такую ​​как ls, в каждом каталоге по пути, мы видим, что в конечном итоге каталог /bin будет содержать ls.

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

Системные вызовы — это функции, предоставляемые ядром операционной системы. Когда оболочке необходимо выполнить команду, системный вызов fork используется для создания дочернего процесса, которому присваивается идентификатор процесса или PID. Помимо PID, этот дочерний процесс является дубликатом родительского процесса, которым в данном случае является программа оболочки. Затем дочерний процесс выполняет системный вызов execve. execve принимает имя файла программы, аргументы программы и среду. При успешном запуске исполняемая программа перезапишет текст, данные и вызывающий процесс. Программа наследует PID дочернего процесса.

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

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

Надеюсь, это демистифицирует механику оболочки. Удачного кодирования!

Первоначально опубликовано на medium.com 19 марта 2018 г.